Mail (fw.Mail)
A first-class mail service, just like fw.DB/fw.Cache/fw.Queue —
connected automatically at boot from MAIL_* in .env, available in any
app with no extra configuration.
Enable
MAIL_ENABLED=true
MAIL_HOST=smtp.example.com
MAIL_PORT=465 # 465 = implicit TLS (SMTPS) automatically; any other port = STARTTLS
[email protected]
MAIL_PASSWORD=your-password
fw.Mail is nil if MAIL_ENABLED=false, if MAIL_HOST/MAIL_USER are
empty, or if the SMTP server is unreachable at boot — unlike Cache/Queue
(which always fall back to an in-memory implementation), there's no
"e-mail sending" that works without a real server. Always check
fw.Mail != nil before using it.
Send
import "kyrux/core/mail"
err := fw.Mail.Send(ctx.Request.Context(), mail.Message{
From: "[email protected]",
ReplyTo: "[email protected]", // optional
To: []string{"[email protected]"},
Cc: []string{"[email protected]"}, // optional
Bcc: []string{"[email protected]"}, // optional
Subject: "Subject",
Text: "Plain text body",
HTML: "<p>HTML body</p>", // optional — if empty, sends Text only
Attachments: []mail.Attachment{
{Filename: "invoice.pdf", Content: pdfBytes, ContentType: "application/pdf"},
},
})
Asynchronous via Queue
If QUEUE_ENABLED=true, fw.Mail.Send() enqueues the message on
fw.Queue and returns control almost immediately — actual delivery
happens later, in a worker (pool, automatic retry with backoff on
transient failure, graceful drain on shutdown). Without this, the HTTP
request would be stuck waiting for a full SMTP session round trip, which
can take seconds.
Without QUEUE_ENABLED=true, Send is synchronous — the returned error
is already the real delivery error. With a queue, the error returned by
Send is only about enqueueing (full queue, closed queue); delivery
failures happen asynchronously and only show up in the logs and the
queue's automatic retry, never back to the caller.
Implemented in core/mail.Queued (core/mail/queued.go) — reuses
core/queue with no new dependency; also works with QUEUE_DRIVER=redis
(payload serialized as JSON across replicas).
Switching providers
fw.Mail is built on top of core/adapters/smtp in bootstrap. For
SendGrid/SES (no automatic sugar — third-party SDK), wire it yourself with
core.UseModule and mail.NewQueued(sender, fw.Queue) — see
Integrations.