Send email with mail.Sender and an explicit mail.Transport, such as SMTP.
Delivery is synchronous and context-aware, and creating a sender makes no network
calls.
sender := mail.Sender{Transport: mail.SMTP{
Address: "smtp.example.com:587",
Username: smtpUsername,
Password: smtpPassword,
}}
err := sender.Send(ctx, mail.Message{
From: "App <app@example.com>",
To: []string{"recipient@example.com"},
Subject: "Account invitation",
Text: "Your account is ready.",
HTML: "<p>Your account is ready.</p>",
})mail.Messageholds the envelope and the text and HTML content.mail.Sendervalidates each message before handing it to the transport.- Credentials and recipients come from application configuration or trusted data.
The accounts starter wires recovery email in project/accounts.go. The
authentication guide lists its environment settings.
#Ask your agent
Read https://cicadel.fauzaanu.com/en/0.2.7/topics/email/index.md.
Add email delivery for [event] using Cicadel mail.Sender.
Read SMTP credentials from secret configuration, send after successful persistence,
and test recipients and message content with an in-memory transport.#Build a message
Messages support To, Cc, Bcc, ReplyTo, plain text, HTML alternatives,
attachments and extension headers:
message.Attachments = []mail.Attachment{{
Filename: "report.csv",
ContentType: "text/csv",
Data: csvBytes,
}}
message.Headers = map[string]string{"X-Application": "reports"}#Attachments
- Attachment names cannot include path separators.
- You supply the bytes. The API does not read arbitrary filesystem paths.
- You own attachment size limits.
#Headers
- Extra headers accept
X-*headers,Message-ID,In-Reply-ToandReferences. - They cannot override envelope, MIME or Bcc fields.
- Duplicate header names, compared case-insensitively, are rejected.
- CR, LF and NUL header injection is rejected.
Good to know
Bcc addresses are used only for the SMTP envelope. They never appear in the serialized MIME.
Do not change a message while it is being sent.
#Render the body
Use mail.RenderComponent(ctx, message, content) to render a compiled typed
component into the HTML body. Set message.Text yourself for the text
alternative.
mail.Render remains a compatibility helper. It uses separate Go text and
escaped HTML templates.
- Neither helper turns arbitrary user HTML into trusted markup.
- Email clients support less CSS than browsers. Tailor email components to the clients you target.
#MIME output
Message.Bytes() validates and serializes the message. The output has:
- Date and Message-ID headers, and UTF-8 headers;
- quoted-printable bodies;
- multipart alternatives and base64 attachments;
- CRLF line endings and standard line wrapping.
The sender and recipient mailboxes are parsed and validated.
#SMTP
SMTP.Encryption controls the connection:
| Mode | Behavior |
|---|---|
starttls (default) | Requires STARTTLS and certificate verification. |
tls | Implicit TLS, commonly on port 465. |
plain | Only to an actual loopback address, and refuses credentials. For local mail capture tools. |
- A cloned
TLSConfigcan supply a private certificate pool. - Disabling verification is rejected. The minimum TLS version is 1.2.
- Credentials are sent only after TLS.
#Send in batches
Sender.SendMany validates the whole input before delivering anything. It reuses
one SMTP connection when the server supports it, and returns the number of
messages accepted before an error.
- The default timeout is 30 seconds for the whole batch.
- Context cancellation closes the connection, even while waiting for an SMTP response.
- A failed QUIT after every DATA command was accepted does not mark those messages as failed.
- A connection failure during DATA acknowledgement can leave delivery uncertain.
Warning
The framework never silently retries or swallows delivery errors. You own the retry and idempotency policy.
#Development backends
| Backend | Behavior |
|---|---|
mail.Capture | Thread-safe in-memory outbox with deep copies on write and read |
&mail.Console{Writer: writer} | Serialized MIME to an explicit writer, with synchronized writes |
mail.File{Directory: path} | One mode-0600 .eml file per message in a private directory |
mail.Discard{} | Explicitly validate without delivery |
Custom mail.Transport | Implement Send(context.Context, mail.Message) error |
Console and file messages include private bodies and reset links. Select them explicitly for development. The accounts starter uses SMTP. Writers, filesystems, credentials and provider accounts stay application-owned.
#Testing
Tests send no real email. The SMTP tests use isolated loopback servers with locally created certificates, and account tests inject a capture callback.
mail/delivery_test.go covers MIME parsing, attachment decoding, Bcc privacy,
header rejection, outbox isolation, file permissions, STARTTLS and implicit TLS,
connection reuse, partial delivery, failed upgrades and cancellation. Run the
package under the race detector alongside the framework CI suite.