Skip to content

SMTP presets

z4j sends invitation and password-reset emails through the project's notification channels, not through global env vars. Each email channel is a record in the brain's notification_channels table with the SMTP config inline. Create one per project and z4j picks the first active email channel when it needs to send.

An email channel's config JSON looks like this:

{
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"smtp_user": "[email protected]",
"smtp_pass": "xxxx-xxxx-xxxx-xxxx",
"smtp_tls": true,
"from_addr": "z4j <[email protected]>",
"to_addrs": ["[email protected]"]
}
Field Meaning
smtp_host SMTP hostname. Resolution is checked during validation and again for delivery; loopback, private, link-local, and other blocked address classes are rejected. There is no private-network opt-in for SMTP channels.
smtp_port One of the allow-listed SMTP ports (25, 465, 587, 2525).
smtp_user Username.
smtp_pass Password.
smtp_tls true for STARTTLS on 587 / implicit TLS on 465.
from_addr From: header. RFC 5322 mailbox or display <addr@host> format.
to_addrs Default recipient list. For invitation and reset emails the brain overrides this with the recipient address.

Create the channel via API (POST /api/v1/projects/{slug}/notifications/channels) or via the dashboard's Notifications page.

Gmail requires an app password (not your account password). Enable 2FA, mint an app password at myaccount.google.com/apppasswords, then create an email channel with:

{
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"smtp_user": "[email protected]",
"smtp_pass": "xxxx-xxxx-xxxx-xxxx",
"smtp_tls": true,
"from_addr": "z4j <[email protected]>"
}

OAuth2 against Gmail is not supported; SMTP + app password only.

{
"smtp_host": "smtp.mailgun.org",
"smtp_port": 587,
"smtp_user": "[email protected]",
"smtp_pass": "<mailgun smtp password>",
"smtp_tls": true,
"from_addr": "z4j <noreply@yourdomain>"
}
{
"smtp_host": "smtp-relay.brevo.com",
"smtp_port": 587,
"smtp_user": "<your brevo smtp login>",
"smtp_pass": "<your smtp key>",
"smtp_tls": true,
"from_addr": "z4j <noreply@yourdomain>"
}
{
"smtp_host": "email-smtp.us-east-1.amazonaws.com",
"smtp_port": 587,
"smtp_user": "<SES SMTP username>",
"smtp_pass": "<SES SMTP password>",
"smtp_tls": true,
"from_addr": "z4j <noreply@verified-domain>"
}

SES requires a verified sender domain.

Without an active email channel, invitation creation still returns its single-use accept URL, so an administrator can deliver that link out of band.

Password-reset requests are different: the public endpoint always returns the same generic accepted response and never exposes the reset token or URL. Without an email channel, self-service password reset cannot deliver its link. Use the fresh-MFA admin password-reset route or z4j changepassword [email protected] --password-stdin for operator-assisted recovery.