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}withAuthorization: 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.