SMTP Connection
This page explains how SMTP.SendEmail() picks a connection mode by port, when it authenticates, and the message format it sends.
Preflight Checks
SendEmail() (internal/service/smtp.go:28) runs these checks in order before connecting and returns an error on the first failure:
| Check | Error message |
|---|---|
enabled is true and host is non-empty |
SMTP not Enabled or Host is not set |
to has at least one recipient |
no user email configured |
A TCP connection to host:port succeeds within 5 seconds |
failed to connect TCP (...) |
Connection Modes
graph TB
Probe[TCP probe 5s] --> Plain[smtp.Dial plain connection]
Plain -->|succeeds| Port587{port = 587?}
Plain -->|fails and port = 465| Implicit[tls.Dial implicit TLS]
Plain -->|fails on other ports| Fail[Return error]
Port587 -->|yes and server supports it| StartTLS[STARTTLS upgrade]
Port587 -->|no| Auth
StartTLS --> Auth{username and password both set?}
Implicit --> Auth
Auth -->|yes| Login[PLAIN auth]
Auth -->|no| Send
Login --> Send[MAIL FROM / RCPT TO / DATA]
| Port | Actual behavior |
|---|---|
465 |
Tries plain SMTP first; an implicit-TLS server never sends a plaintext greeting, so the plain attempt fails only after the server closes the connection, and only then does tls.Dial open an implicit TLS connection, which can delay delivery |
587 |
After the plain connection, upgrades to TLS when the server advertises the STARTTLS extension; continues in plaintext when it does not |
Others (e.g. 25) |
Plain connection only, no TLS upgrade |
TLS always verifies the server certificate with host as the ServerName.
Authentication
smtp.PlainAuth runs only when both username and password are non-empty; if either is empty, authentication is skipped, which suits internal relays that need no login. Go's standard-library PLAIN auth sends credentials only over TLS or to localhost, so setting credentials on an unencrypted port such as 25 fails authentication.
Message Format
| Header | Value |
|---|---|
| From | config.from |
| To | config.to joined with commas |
| Cc | config.cc; config.from when empty |
| Subject | Alert or test subject |
| Content-Type | text/html; charset=UTF-8 |
RCPT TO goes only to addresses in the to list, while Cc exists only in the header; see Known Limitations.