Concepts
Concepts
Section titled “Concepts”Reading the rest of these docs is easier once the core vocabulary is locked in. Each term below maps to a specific type or module in the codebase.
Template
Section titled “Template”A template is a directory keyed by an id under MAILIFY_TEMPLATES__PATH. Inside:
<id>/ <locale>.html # pre-rendered React Email HTML with minijinja spans subject.<locale>.txt # optional, minijinja-rendered at send text.<locale>.txt # optional plaintext alternativeTemplates are loaded into an in-memory TemplateRegistry at boot. Editing a .tsx in templates-parser/ requires re-running make build-templates before restart for changes to appear.
See Template contract.
The theme is a set of branding tokens (colors, fonts, radius, logo URL, footer text, social links, arbitrary extras) declared once in config and injected into every render via the minijinja {{ theme.* }} namespace.
Re-branding your entire email output = change config, restart. No template edit needed.
[theme]brand_name = "Acme Corp"brand_logo_url = "https://cdn.acme.com/logo.png"footer_text = "© Acme. All rights reserved."
[theme.colors]primary = "#FF4500"primary_foreground = "#ffffff"See Configure theme.
MailJob
Section titled “MailJob”Every send becomes a MailJob — a struct pushed onto the durable Postgres queue. Key fields:
id— UUID for caller-side tracking.kind— eitherRegistered { template_id }(render from the registry) orCustom { html, subject, text? }(one-shot raw HTML).priority— weighted scheduling.Low/Normal/High/Critical.smtp_override— per-job SMTP credentials, memory-only, never logged. Enables multi-tenant fan-out.vars— arbitrary JSON passed to the minijinja renderer.locale— selects which<id>/<locale>.htmlto render.
Queue / worker
Section titled “Queue / worker”Mailify uses apalis with PostgresStorage<MailJob>. The queue table is apalis.jobs. On boot, PostgresStorage::setup() runs the apalis migrations automatically.
The worker loop pulls jobs concurrently (MAILIFY_QUEUE__WORKER_CONCURRENCY, default 4), retries on failure up to MAX_RETRIES with RETRY_BACKOFF_SECS, and persists last_error on the job row so the GET /mail/jobs/:id endpoint can surface it.
Jobs survive server restarts.
SMTP override
Section titled “SMTP override”By default every job goes through the process-wide default SMTP sender built from MAILIFY_SMTP__* config.
When a job carries smtp_override: { host, port, username?, password?, tls }, the worker builds a one-off SmtpSender for that job. The credentials never hit disk, logs, or the queue table — they live in the in-flight MailJob and are wiped when the job finishes.
Primary use case: a multi-tenant SaaS where each tenant brings their own SMTP provider.
Authentication
Section titled “Authentication”Two layers:
- API key — long-lived secret, stored as an argon2 hash in
MAILIFY_AUTH__API_KEYS__<ID>. Generated viamake hash-keyor auto-provisioned at boot in bootstrap mode. - JWT — short-lived bearer token obtained by
POST /auth/tokenwith the API key. Used on all protected routes.
The split lets you rotate JWTs without rotating API keys, and lets clients cache tokens instead of re-hashing on every request.
See Auth & tokens.
Bootstrap mode
Section titled “Bootstrap mode”MAILIFY_AUTH__BOOTSTRAP=true (default). If no API keys are configured at boot, Mailify:
- Generates a random key in memory.
- Hashes it with argon2 in memory.
- Logs the plaintext + the
MAILIFY_AUTH__API_KEYS__<ID>=<hash>env line you should copy into a.envor config file for persistence. - Accepts requests using that key for the lifetime of the process.
This is the zero-friction first-boot path. Disable by setting MAILIFY_AUTH__BOOTSTRAP=false in production once you’ve persisted a real key.
Config precedence
Section titled “Config precedence”Lowest → highest:
- Compile-time defaults (see
AppConfig::default). - Auto-discovered TOML file (see Config reference).
- Dotenv chain (
.env.<MAILIFY_ENV>.local, etc — does not override existing env vars). - Process environment variables prefixed
MAILIFY_, nested via__.
So: MAILIFY_SMTP__HOST=foo beats [smtp] host = "bar" in Mailify.toml, which beats the compile-time default of localhost.