Skip to content

Email configuration & templates

Each project configures its own outbound email — either a generic SMTP server or the Resend API — plus its own overridable email templates, used by the end-user auth flows (magic link, password reset, email verification, email change). Everything here is platform-owner-only; end users never see these routes.

Provider configuration

Method Route Description
GET /projects/{id}/auth/email-config Show the current config (secrets never included)
PUT /projects/{id}/auth/email-config Create/update the config
POST /projects/{id}/auth/email-config/test Send a test email through the configured provider ({ to })

PUT body:

{
  "provider": "smtp",
  "from_address": "noreply@yourapp.com",
  "from_name": "Your App",
  "smtp_host": "smtp.yourapp.com",
  "smtp_port": 587,
  "smtp_username": "apikey",
  "smtp_encryption": "tls",
  "smtp_password": "••••••••",
  "require_email_confirmation": false
}

For "provider": "resend", send resend_api_key instead of the smtp_* fields. smtp_password / resend_api_key are encrypted at rest (AES-256-GCM, keyed by APP_KEY — see Configuration) and never echoed back by GET/PUT; the response instead reports has_smtp_password / has_resend_api_key booleans. Omitting the secret field on an update keeps the existing value — send an empty string to clear it.

smtp_host must be a public mail server: a host name (or public IP) — never localhost, a single-label name or a private/reserved address. smtp_port must be one of 25, 465, 587 or 2525, and smtp_encryption one of none, ssl, tls. The host is resolved and checked again every time an email is sent, and the connection is pinned to the checked address (the TLS certificate is still verified against the host name), so it can't be pointed at an internal service through DNS either.

require_email_confirmation gates end-user login until their email is verified — see End users.

Templates

Method Route Description
GET /projects/{id}/auth/templates List all 4 templates (custom or default)
GET /projects/{id}/auth/templates/{key} Show one template
PUT /projects/{id}/auth/templates/{key} Set a custom { subject, body }
DELETE /projects/{id}/auth/templates/{key} Reset back to the built-in default
POST /projects/{id}/auth/templates/preview Render { subject, body } with sample data (no send, no save)

key is one of magic_link, password_reset, email_verification, email_change. Every response includes is_custom: true|false so a client can distinguish an override from the built-in default.

Placeholders

Placeholder Available in
{{link}} all four templates
{{email}} magic_link, password_reset, email_verification
{{new_email}} email_change
{{project_name}} all four templates

An unrecognized placeholder is left untouched; recognized ones are HTML-escaped before substitution.

Delivery is best-effort

If a project has no project_email_configs row yet, the auth flows in End users still work — a token is still issued — they just have no provider to actually send through. Configure a provider here (and send a test email) before relying on these flows in production.