Skip to content

Configuration

Custom Mail is configured through config/mail.json plus drop-in files under plugins/. The Worker compiles those files at build/deploy time; change them before npm run deploy. See the plugins guide for the folder layout.

Empty optional fields (and false feature/layout flags) omit that chrome. Nothing is required beyond host, app.title, and mail.fromEmail / fromNameDefault.

Runtime overrides (no rebuild): MAIL_PROVIDER, MAIL_THEME, MAIL_LAYOUT, MAIL_LOGO, MAIL_CONFIG_JSON.

File overview

jsonc
{
  "host": "mail.example.com",
  "plugins": {
    "provider": "brevo",      // plugins/providers/*.json
    "theme": "forest",        // plugins/themes/*.json
    "layout": "banner",       // plugins/layouts/*.json
    "logo": "image"           // auto | image | monogram | none
  },
  "features": { "attachments": true, "history": true, "addressBook": true, "markdown": true, "syntaxHelp": true },
  "layout": { "showHeader": true, "showLogo": true, "showSubject": true, "showFrom": true, "showFooterContact": true, "showFooterSite": true },
  "app": { /* console copy */ },
  "mail": { /* sender */ },
  "site": { /* logo, favicon, footer link */ },
  "brand": { /* color overrides on top of the theme */ },
  "i18n": { /* console labels; omitted keys use English defaults */ },
  "syntax": { /* markdown helper chips */ },
  "addressBook": [ /* preset recipients */ ]
}

config/overlays/*.json is deep-merged at compile time. GET /api/health lists the compiled catalog under available.

host

Public hostname users open in the browser. Must equal the custom domain in wrangler.jsonc routes.

plugins

plugins.provider

IdSecret(s)
brevoBREVO_API_KEY
resendRESEND_API_KEY
sendgridSENDGRID_API_KEY
mailgunMAILGUN_API_KEY + MAILGUN_DOMAIN or mail.providerDomain
postmarkPOSTMARK_SERVER_TOKEN
mailersendMAILERSEND_API_KEY
smtp2goSMTP2GO_API_KEY
sparkpostSPARKPOST_API_KEY

MAIL_API_KEY is used when the provider-specific secret is empty. fromEmail must be authorized on the chosen provider. mail.tag (brevoTag still accepted) is sent as a campaign/tag when the API supports it. New ESP metadata goes in plugins/providers/; send logic stays in Rust.

plugins.theme

Drop a palette JSON in plugins/themes/. Bundled: forest · midnight · ocean · paper · rose · slate · aurora · sunset · nord.

Set any brand.* color to override a theme token. Header / top-bar colors are brand.heroFrom, brand.heroTo, and brand.headerText (legacy tile / tileEdge still map onto the header gradient).

plugins.layout

Drop JSON in plugins/layouts/. Bundled: card · minimal · banner · digest · compact.

auto (image if configured, else monogram, else omit) · image · monogram · none. Files in plugins/logos/ are served from /plugins/logos/.

features / layout flags

false or empty config hides that block (attachments UI, history, address book, markdown helper, email header, logo, footer contact, footer site).

app — login / title copy

FieldUsed for
titleApp name in header, login, and browser tab
subtitleHeader subtitle (omitted when empty)
localehtml lang and date formatting
loginTagline / loginHeadlineBefore / loginHeadlineEm / loginLead / loginPointsLogin hero
loginFormTitle / loginFormSubSign-in card

mail — sending

FieldDescription
fromEmailLocked From address
fromNameDefaultDefault display name
contactEmailFooter contact; empty hides it
tagProvider campaign/tag
providerDomainMailgun sending domain

site — branding chrome

FieldDescription
url / labelFooter site link; empty url hides it
brandNameOrganization name (falls back to app.title)
logoPath / logoUrlLogo image. Empty = bundled plugin file, then monogram.
faviconPathBrowser tab icon. Empty = logoPath, then generated /favicon.svg.

Replace public/images/logo.svg or drop a file in plugins/logos/ when you fork.

brand — color overrides

tile, tileEdge, heroFrom, heroTo, headerText, accent, accentDeep, accentSoft, cream, paper, ink, muted, line, siteBlue. Empty strings keep the theme default.

i18n / syntax / addressBook

Console labels default to English. Override any key under i18n. Syntax chips live under syntax.chips. Address-book entries are { "address", "note" }.

Environment secrets (not in mail.json)

SecretPurpose
ADMIN_PASSWORDLogin password
Provider key (see table)Outbound API
MAIL_API_KEYFallback API key
MAIL_PROVIDER / MAIL_THEME / MAIL_LAYOUT / MAIL_LOGORuntime plugin slot overrides
MAIL_CONFIG_JSONRuntime JSON overlay
MAILGUN_DOMAINMailgun domain
ALLOW_ANY_HOST=1Local dev: skip Host header check

Never commit secrets into git.

After changing config

bash
npm run typecheck
npm run deploy

Open Source · MIT License