Skip to content

Email

Transactional email is delivered via an external HTTP API rather than direct SMTP. Source: backend/internal/services/email_service.go. Templates live in backend/templates/.

Service overview

EmailService is a thin HTTP client that POSTs JSON to a configured endpoint:

type EmailRequest 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>)

The HTTP client uses a 30-second timeout. A non-2xx response returns an error to the caller; we do not retry on transport errors — that's left to upstream callers when failure is important (e.g. OTP resend).

Provider

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

  • POST {base_url} with Authorization: Bearer {api_key} and JSON body
  • 200 or 201 response indicates success

This is compatible with Resend, custom MailChannels endpoints, and most modern email APIs. 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" errors when these are blank. In local dev without GCP secrets, email-dependent flows (OTP, password reset, email change) will fail.

Templates

All email bodies are rendered as inline-styled HTML. Today there is a single dedicated template file (backend/templates/og.html, used for the public event share OG page, not email). The actual email HTML is built inline in email_service.go via baseEmailStyles() + per-flow body builders, all aligned to the Tomoda Design System (dark surfaces, Burnt Gold #ffb867 accent, Inter typeface).

Use cases

Flow Triggered by Template
OTP POST /api/v1/auth/otp/send, email change, password reset Inline 6-digit code, time-boxed
Email verification Registration Inline magic-link / OTP
Password reset POST /api/v1/auth/reset-password (post-OTP) Notification of successful reset
Account suspension Scheduler (account_suspension) or admin action Notification
Account deletion / deactivation GDPR delete or scheduled purge Notification

See the Auth service and the corresponding scheduler cron handlers for the exact call sites.