b3c22c261e
Add explicit TLS modes, context-aware delivery, and typed transport errors. Preserve raw MIME messages and cover the new delivery paths with local SMTP tests.
107 lines
4.0 KiB
Markdown
107 lines
4.0 KiB
Markdown
# Email Sender
|
|
|
|
`email-sender` is a focused SMTP transport for caller-built RFC/MIME messages,
|
|
with a secondary convenience API for HTML messages and attachments. Network
|
|
operations are context-aware and bounded, and TLS uses Go's standard
|
|
certificate-chain and hostname verification.
|
|
|
|
## Canonical raw API
|
|
|
|
```go
|
|
service, err := email.New(email.Config{
|
|
Auth: smtp.PlainAuth("", username, password, "smtp.example.com"),
|
|
Host: "smtp.example.com",
|
|
Port: "465",
|
|
From: "bounce@example.com",
|
|
TLSMode: email.TLSModeImplicit,
|
|
ConnectTimeout: 10 * time.Second,
|
|
OperationTimeout: 30 * time.Second,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = service.SendRawContext(ctx, email.RawMessage{
|
|
EnvelopeFrom: "bounce@example.com",
|
|
EnvelopeRecipients: []string{"recipient@example.com"},
|
|
Data: rawMIMEBytes,
|
|
})
|
|
```
|
|
|
|
`Data` is transmitted as the caller supplied it. The transport does not
|
|
rebuild MIME, replace multipart boundaries, or generate a second `Message-ID`.
|
|
SMTP envelope addresses are explicit and are not inferred from arbitrary MIME
|
|
headers.
|
|
|
|
## TLS modes
|
|
|
|
- `TLSModeImplicit`: TLS is established and verified before the SMTP greeting.
|
|
- `TLSModeSTARTTLS`: the server must advertise STARTTLS; absence is an error and
|
|
the transport never downgrades to plaintext.
|
|
- `TLSModeNone`: explicit plaintext SMTP for trusted local development systems
|
|
such as Mailpit. Authentication is rejected in this mode.
|
|
|
|
TLS 1.2 or later is required. The configured SMTP hostname becomes
|
|
`tls.Config.ServerName`; standard certificate-chain and hostname validation are
|
|
always enabled. There is no `InsecureSkipVerify` option.
|
|
|
|
By default Go uses the operating-system certificate pool. `Config.RootCAs`,
|
|
when non-nil, deliberately replaces that pool. To add a private CA while
|
|
retaining public roots, start with `x509.SystemCertPool()` and append the CA.
|
|
|
|
## Contexts and timeouts
|
|
|
|
`SendRawContext` and `SendEmailContext` are canonical. TCP establishment uses
|
|
`net.Dialer.DialContext` and `ConnectTimeout`. Every greeting, SMTP command,
|
|
TLS upgrade, DATA write, and response is bounded by the earlier of the context
|
|
deadline and `OperationTimeout`. Context cancellation closes the underlying
|
|
connection to interrupt blocked SMTP I/O; no detached send goroutine remains.
|
|
|
|
Legacy `SendRaw` and `SendEmail` methods remain as compatibility wrappers. They
|
|
use an explicit bounded context derived from the configured timeouts. Legacy
|
|
`NewSecure` and `NewSecure465` now perform normal verified TLS. The legacy
|
|
`NewInsecure` name is retained for source compatibility but also requires
|
|
verified STARTTLS; insecure certificate behavior was intentionally removed.
|
|
|
|
## Error classification
|
|
|
|
Transport failures wrap `*email.Error`:
|
|
|
|
```go
|
|
var transportError *email.Error
|
|
if errors.As(err, &transportError) && transportError.Temporary() {
|
|
// Let the caller's job system retry.
|
|
}
|
|
```
|
|
|
|
SMTP 4xx responses and ordinary network failures are generally transient.
|
|
SMTP 5xx responses, invalid addresses/configuration, missing required STARTTLS,
|
|
and certificate failures are permanent. Cancellation preserves
|
|
`errors.Is(err, context.Canceled)` and
|
|
`errors.Is(err, context.DeadlineExceeded)`.
|
|
|
|
Errors include a bounded operation-stage description but never message bodies,
|
|
SMTP passwords, or authentication data.
|
|
|
|
## SMTP delivery ambiguity
|
|
|
|
SMTP is not an exactly-once protocol. A server can accept the final DATA while
|
|
the connection fails before the client receives the acknowledgement. A retry
|
|
may then deliver a duplicate. A deterministic caller-supplied `Message-ID`
|
|
helps downstream clients deduplicate presentation, but cannot provide
|
|
mathematical exactly-once delivery.
|
|
|
|
## Development and tests
|
|
|
|
Plain local SMTP must be selected deliberately with `TLSModeNone` and no
|
|
authentication. Automated tests use local deterministic SMTP/TLS servers and
|
|
never connect to internet SMTP.
|
|
|
|
```bash
|
|
gofmt -w .
|
|
go vet ./...
|
|
go test ./... -count=1
|
|
go test -race ./... -count=1
|
|
git diff --check
|
|
```
|