Skip to content

Email

Transactional email is delivered via an external HTTP API rather than direct SMTP. Source: backend/internal/platform/email/email.go. The public event-share OG page template lives in backend/templates/og.html; email bodies are built inline.

Service overview

email.Service is a thin HTTP client that POSTs JSON to a configured endpoint:

type Request struct {
    From    string   `json:"from"`
    To      []string `json:"to"`
    Subject string   `json:"subject"`
    HTML    string   `json:"html"`
}

The endpoint URL and auth key come from config.Email:

Field Source Notes
Email.APIKey EMAIL_APIKEY env var Bearer token for the email provider
Email.BaseURL YAML (email.base_url) Full POST URL of the provider
Email.From YAML (email.from) Default From address (e.g. Tomoda <hi@tomoda.life>)
Email.AllowPrivate YAML (email.allow_private) Permits private-range hosts, needed for local dev / self-hosted relays

The client is built by httpx.NewSafeClient with a 30-second timeout and SSRF guards (private ranges blocked unless AllowPrivate is set). A non-2xx response returns an error to the caller; there is no transport-level retry, that is left to callers where failure matters (e.g. OTP resend).

Provider

The provider is whatever email.base_url points at. The implementation is provider-agnostic and only requires:

  • POST {base_url} with Authorization: Bearer {api_key} and a JSON body
  • a 2xx response for success

Check config.{ENV}.yaml for the active provider per environment.

Required config

SendEmail returns "email API key not configured" or "email base URL not configured" when these are blank. In local dev without secrets, email-dependent flows (OTP, password reset, email change) fail.

Templates

Email bodies are inline-styled HTML built in email.go via baseEmailStyles() plus per-flow body builders, aligned to the Tomoda design system (dark surfaces, Burnt Gold #ffb867 accent, Inter typeface). No per-email template files.

Use cases

Flow Triggered by
OTP POST /api/v1/auth/otp/send, email change, password reset
Email verification Registration
Password reset POST /api/v1/auth/reset-password (post-OTP)
Account suspension Scheduler (cron:account_suspension) or admin action
Account deletion / deactivation GDPR delete or scheduled purge

email.Service is a platform singleton wired in ServiceSet; the auth domain and the async cron handlers are its callers. See the Auth service and Async.