v0.1.0

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.

中文