# 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 ```