cicadel

Email

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.

Go
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.Message holds the envelope and the text and HTML content.
  • mail.Sender validates 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

Text
Read https://cicadel.fauzaanu.com/en/dev/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:

Go
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-To and References.
  • 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:

ModeBehavior
starttls (default)Requires STARTTLS and certificate verification.
tlsImplicit TLS, commonly on port 465.
plainOnly to an actual loopback address, and refuses credentials. For local mail capture tools.
  • A cloned TLSConfig can 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

BackendBehavior
mail.CaptureThread-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.TransportImplement 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.

Type to search this version.