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.