Kyrux
Back

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.