go-notificationgo-notification
Channels/Email

SMTP

The SMTP driver — when to use it, what ports work, and the Gmail app-password walkthrough.

Cloud providers block SMTP ports

  • GCP — Port 25 blocked permanently, cannot request unblock.
  • AWS EC2 — Port 25 blocked by default, unblock requests often denied.
  • DigitalOcean — Port 25 blocked for all new accounts.
  • Azure — Port 25 blocked.

Port 587 (STARTTLS) usually works, but some providers restrict it too.

If you're deploying on cloud, use Mailgun, SendGrid, or SES instead of SMTP.

When to use SMTP

  • On-premise / bare-metal servers.
  • Corporate mail servers (Exchange, Postfix).
  • Self-hosted mail (Mailcow, iRedMail, Mail-in-a-Box).
  • Local development with Mailhog or Mailpit.
  • Non-restrictive hosts (Hetzner, OVH).

Setup

main.go
import (
    "github.com/gopackx/go-notification/channel/mail"
    "github.com/gopackx/go-notification/channel/mail/smtp"
)

notifier.RegisterChannel(smtp.New(smtp.Config{
    Host:     "smtp.gmail.com",
    Port:     587, // STARTTLS — leave TLS false; the driver upgrades automatically
    Username: "user@gmail.com",
    Password: os.Getenv("SMTP_PASSWORD"),
    From:     mail.Address{Name: "My App", Address: "noreply@example.com"},
}))

TLS field = implicit TLS (port 465)

Set TLS: true only for implicit TLS on port 465. For STARTTLS on port 587, leave TLS: false — the driver upgrades the connection automatically when the server advertises STARTTLS. Setting TLS: true on port 587 will fail the handshake.

Gmail app password

Gmail won't accept your regular password for SMTP. You need an app password:

  1. Enable 2-Step Verification on your Google account.
  2. Go to https://myaccount.google.com/apppasswords.
  3. Create a new app password, label it e.g. "go-notification".
  4. Paste the 16-character password into SMTP_PASSWORD.

Configuration reference

FieldTypeRequiredDescription
HoststringyesSMTP server hostname (e.g. smtp.gmail.com).
Portintyes587 for STARTTLS, 465 for implicit TLS, 25 for plain.
UsernamestringnoSMTP auth username. Leave empty for no auth.
PasswordstringnoSMTP auth password or app password.
AuthMechanismstringno"plain" (default) or "login".
Frommail.AddressyesDefault sender. A struct: mail.Address{Name, Address}.
TLSboolnoImplicit TLS (port 465). Leave false for STARTTLS on 587.
InsecureSkipVerifyboolnoDisable TLS cert verification. Never use in production.
Timeouttime.DurationnoBounds the whole SMTP session — connect, TLS, every command through QUIT. Default: 30s.
NamestringnoOverride the channel name (default "mail").

Timeouts and retries

  • Timeout bounds the entire session, not just the TCP dial: connect, TLS handshake (implicit or STARTTLS), EHLO, AUTH, MAIL FROM, RCPT TO, DATA and QUIT. A server that accepts the connection and then stalls cannot hold a worker indefinitely. If the ctx deadline is earlier, it wins. With implicit TLS (TLS: true) the dial honours ctx cancellation as well.
  • 5xx replies are permanent. 550 mailbox unavailable, 535 authentication failed and friends are wrapped in middleware.ErrPermanent: the notifier does not retry them and fires OnError right away.
  • 4xx replies are transient. 421 service not available, 450 mailbox busy, 451 local error (greylisting, rate limits) go through the normal retry schedule.

This is the reverse of the HTTP convention, where 4xx is permanent and 5xx is retried. SMTP's codes mean different things: 4xx is "try again later", 5xx is "don't bother".

Message format

A message with text, HTML and attachments is sent as nested MIME: a multipart/mixed envelope whose first part is a multipart/alternative (plain text + HTML), followed by one part per attachment. Mail clients render the HTML with the text fallback and list the attachments, instead of showing the body as a download. Text-only, HTML-only, and text+HTML without attachments use the simpler flat structure.

Troubleshooting

  • connection refused — wrong host/port, or firewall / cloud provider blocking.
  • handshake error on port 587 — you set TLS: true. Leave it false; STARTTLS upgrades automatically.
  • 535 5.7.8 — bad credentials (permanent, not retried). For Gmail, you need an app password, not your account password.
  • i/o timeout mid-session — the server stalled after accepting the connection and Timeout (default 30s) expired. Raise it if your relay is genuinely slow.
  • Works locally, fails in prod — your cloud provider is almost certainly blocking port 25/587. Switch to an API driver.