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. z4j tries the project's active email channels in order until a delivery succeeds; for a password reset it does that across every project the user belongs to.
Channel config schema
Section titled “Channel config schema”An email channel's config JSON looks like this:
{ "smtp_host": "smtp.gmail.com", "smtp_port": 587, "smtp_user": "you@gmail.com", "smtp_pass": "xxxx-xxxx-xxxx-xxxx", "smtp_tls": true, "from_addr": "z4j <you@gmail.com>", "to_addrs": ["ops@example.com"]}| 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 |
Defaults to true. Port 465 is implicit TLS whatever this says; on 25, 587 and 2525, true means STARTTLS and false sends plaintext. |
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": "you@gmail.com", "smtp_pass": "xxxx-xxxx-xxxx-xxxx", "smtp_tls": true, "from_addr": "z4j <you@gmail.com>"}OAuth2 against Gmail is not supported; SMTP + app password only.
Mailgun
Section titled “Mailgun”{ "smtp_host": "smtp.mailgun.org", "smtp_port": 587, "smtp_user": "postmaster@mg.yourdomain", "smtp_pass": "<mailgun smtp password>", "smtp_tls": true, "from_addr": "z4j <noreply@yourdomain>"}Brevo (formerly Sendinblue)
Section titled “Brevo (formerly Sendinblue)”{ "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>"}AWS SES
Section titled “AWS SES”{ "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.
No-email fallback
Section titled “No-email fallback”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 user@example.com --password-stdin for operator-assisted recovery.