AnnouncementsIntroducing Arkoren

    Menu

    Mail Overview

    Send transactional email through a portable facade and distinguish submission acceptance from recipient delivery.

    What's on this page
    1. Build a welcome message from typed account data and submit it through a configured mailer.
    2. Interpret opaque receipts as submission outcomes, not evidence of inbox delivery.
    3. Classify failures without exposing provider diagnostics or blindly retrying unknown acceptance.
    On this page

    A welcome email or receipt starts with application data and ends with a request to submit mail. Arkoren separates those responsibilities: an immutable mail.Message describes the email, and a *mail.Mailer applies sender defaults, validates it, and passes it to a driver.

    The mailer sends one message synchronously per call. It does not render templates, queue work, retry, or track inbox delivery. Choose and construct your backend on Drivers; use a queue job when submission should happen outside the request that triggered it.

    Sending Mail

    Assume a configured *mail.Mailer named mailer with a default sender, and a nonnil request or job context named ctx. Import arkoren.dev/core/service/mail. For a newly created account, use the recipient data your application has already validated and authorized:

    type WelcomeEmail struct {
    	Name  string
    	Email string
    }
    
    welcome := WelcomeEmail{Name: "Ada", Email: "ada@example.com"}

    Construct the recipient list and body together, then add the optional subject:

    message := mail.NewMessage(mail.Recipients{
    	To: []mail.Address{{Name: welcome.Name, Email: welcome.Email}},
    }, mail.Body{Text: "Your account is ready."}, func(letter *mail.MessageBuilder) {
    	letter.Subject("Welcome to Acme")
    })

    At least one address across To, CC, and BCC, and at least one nonempty text or HTML body, are required. The omitted sender comes from the mailer. The zero mail.Message is invalid; use NewMessage rather than a struct literal.

    Submit that value through the facade:

    receipt, err := mailer.Send(ctx, message)

    On success, receipt describes driver acceptance. Send fills missing sender and reply-to defaults on a local copy; your original message stays unchanged. Focused examples leave error results visible and omit routine propagation; handle those errors in your application before using successful results. For HTML, recipient groups, and files, continue with Composing Messages.

    Submission Outcomes

    receipt.ID is an opaque submission identifier, not a delivery status or retry idempotency key. SMTP and Log return generated message IDs; API drivers return provider identifiers. The portable contract permits an empty ID. Store an available identifier with your application’s mail record when you need support correlation, under an appropriate access and retention policy.

    A sending server can accept mail that later bounces, is deferred, or lands in spam. The Log driver simulates acceptance without sending anything. Optional provider notifications can report later outcomes, but constructing an outbound mailer does not register a receiver. Even a delivery notification is not proof that a person read the message.

    Handling Failures

    Invalid messages fail before driver submission with an error wrapping mail.ErrInvalidMessage. A driver’s preflight also rejects an already canceled or expired context before I/O. Native failures retain their wrapped causes; use errors.Is or errors.As, not diagnostic-text matching. Returned errors can contain recipients or provider diagnostics: redact them before logging or forwarding them.

    With standard-library imports context and errors, distinguish malformed input from an interrupted submission:

    _, err := mailer.Send(ctx, message)
    invalid := errors.Is(err, mail.ErrInvalidMessage)
    interrupted := errors.Is(err, context.Canceled) ||
    	errors.Is(err, context.DeadlineExceeded)

    If invalid is true, correct the message or sender configuration rather than retrying unchanged input. If interrupted is true, stop this attempt; after I/O starts, acceptance can remain unknown. Other failures need provider-specific classification, including credential and recipient rejections.

    Neither the facade nor its drivers add a retry loop, although native SDK or transport policies can retry. A timeout can follow remote acceptance, so another attempt can send duplicate mail. There is no portable idempotency key or exactly-once guarantee; an application record alone cannot atomically commit both a remote submission and a local update.

    A driver panic propagates unchanged without a mail.Sent or mail.Failed event. Absence of an outcome event does not prove nonacceptance. See Events and Telemetry for the signals that remain. To assert application behavior without sending mail, use the mail fake and failure injection.

    For every function and option, see the mail API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive