Skip to Content
InfrastructureMail & SMTP

Mail & SMTP

Nanokit ships a self-hosted mail stack built on Stalwart  (SMTP / IMAP / JMAP) so transactional email — verification, password reset, team invites — is delivered from your own domain. This page covers the SMTP environment variables the platform reads, the Stalwart service, the DNS records that keep mail out of spam, and the one TLS pitfall that has bitten production before.


SMTP environment variables

The mailer reads its connection settings from the process environment. Set these in the relevant environment’s .env (or via nkapp secrets set … -e <env>):

VariableDefaultPurpose
SMTP_HOSTSMTP server hostname or IP. Required for real delivery; if unset the mailer falls back to a mock (logs only).
SMTP_PORT587Connection port (587 STARTTLS submission, 465 implicit TLS, 25 MX).
SMTP_USERSMTP auth username (the mailbox you send as, e.g. info@yourdomain).
SMTP_PASSSMTP auth password.
SMTP_SECUREfalsetrue forces an implicit-TLS connection (use with port 465).
SMTP_TLS_SERVERNAMESNI / certificate-validation hostname. See the pitfall below.
SMTP_TLS_REJECT_UNAUTHORIZEDtruefalse disables certificate verification (avoid in production).
SMTP_FROMNanokit <noreply@nanokit.dev>Default sender (display name + address).

[!NOTE] When SMTP_HOST is absent the mailer silently degrades to a mock: messages are logged, not sent. If “email isn’t arriving” but no errors show, confirm SMTP_HOST is actually set in that environment.


The Stalwart mail service

Mail runs as a Docker service (stalwartlabs/stalwart) inside the project, typically hosted at mail.<your-domain>. It exposes the standard mail ports:

PortProtocol
25SMTP (MX / server-to-server)
587SMTP submission (STARTTLS)
465SMTPS (implicit TLS)
993IMAPS
995 / 110POP3S / POP3

Stalwart imports the Caddy-issued TLS certificates (mounted read-only) so it serves the same trusted cert as the rest of your gateway.

Provision the sending mailbox

Do not send as postmaster — it is an RFC-reserved system name and Stalwart refuses outbound submission from it (501 5.5.4 You are not allowed to send from this address.). Use a normal directory user instead (e.g. info@<your-domain>) with full submission permissions. Nanokit can provision it for you:

nkapp host mail-setup <env> # alias: email-setup

This registers the domain over JMAP, creates/updates the sending user, and sets its password in Stalwart’s store.


DNS records (SPF / DKIM / DMARC)

Deliverability depends on publishing the right DNS records for your sending domain. Replace the example IP / domain with your own; DKIM must use the public key generated by your Stalwart instance.

RecordNameValue (example)
Amail<your-mail-server-IP> (DNS-only, not proxied)
MX@mail.<your-domain> (priority 10)
SPF (TXT)@v=spf1 ip4:<your-mail-server-IP> -all
DKIM (TXT)default._domainkeyv=DKIM1; k=rsa; p=<STALWART_PUBLIC_KEY>
DMARC (TXT)_dmarcv=DMARC1; p=quarantine; pct=100; aspf=r; adkim=r; rua=mailto:dmarc-reports@<your-domain>

Retrieve the DKIM public key from the Stalwart admin console (https://mail.<your-domain>/admin → Management → Domains → DKIM keys). Until DKIM is published, well-configured receivers will mark your mail as spam.

[!TIP] Optional records — MTA-STS (_mta-sts TXT) and BIMI (default._bimi TXT, requires an SVG Tiny PS logo < 32 KB) — further improve trust and brand presentation but are not required for delivery.


The TLS-servername pitfall

This is the single most common mail failure on Nanokit, and the reason SMTP_TLS_SERVERNAME exists.

When a service connects to the mail container by its internal Docker hostname (nk-<project>-<env>-mail) but the certificate is issued for the public domain (mail.<your-domain>), Node’s TLS layer validates the cert against the connection host, mismatches, and throws ERR_TLS_CERT_ALTNAME_INVALID. The mailer catches this and silently degrades every message to a mock — so mail “works” in logs but nothing is actually delivered.

Fix: set the SNI / validation name to the certificate’s real domain so verification stays on while you connect over the internal network:

SMTP_TLS_SERVERNAME=mail.yourdomain.com

[!CAUTION] Prefer SMTP_TLS_SERVERNAME over disabling verification. Setting SMTP_TLS_REJECT_UNAUTHORIZED=false “works” but turns off certificate checking entirely and should be a last resort.

Repairing certificates on a VPS

If the gateway or mail container can’t read its certificate after a deploy (a permissions drift), repair it and restart the affected services:

nkapp host certs-fix <env> # alias: certificates-fix

This fixes permissions on the Caddy certificate directory on the remote host and restarts the gateway and mail containers so they reload the certs. It does not set SMTP_TLS_SERVERNAME for you — that remains a config value in .env or nanokit.yml.


Quick checklist

  1. Set SMTP_HOST, SMTP_USER, SMTP_PASS (and SMTP_FROM) for the environment.
  2. Provision a non-postmaster sending mailbox (nkapp host mail-setup <env>).
  3. Publish A / MX / SPF / DKIM / DMARC for the sending domain.
  4. Set SMTP_TLS_SERVERNAME to the cert’s public domain to avoid the altname trap.
  5. If certs misbehave after a deploy, run nkapp host certs-fix <env>.