SMTP module
Emails the issued certificate artefact to one or more recipients after every successful renewal. Useful for key-ceremony hand-off, compliance archive mailboxes, and ops DLs that don't have an API integration. Not a notification — the SMTP module ships the certificate bytes themselves (PEM + chain, optionally private key or a passphrase-protected PKCS#12 bundle), the same way SSH ships PEM files to a box.
Overview
- Transport: SMTP over STARTTLS (port 587), SMTPS / implicit TLS (465), or plaintext (25, dev only).
- Auth: PLAIN, LOGIN, or none (open relay / internal MTA).
- Payload: PEM (cert + chain), PEM + unencrypted private key, or a passphrase-protected PKCS#12 bundle (AES-256 + SHA-256 HMAC via go-pkcs12 Modern2023).
- Recipients: TO / CC / BCC lists, RFC 5322 validated. One email per target carries all three lists.
- Rollback: re-send. Rollback re-emails the previous retained version as a fresh email — it cannot recall an already-sent one.
- Self-contained targets: each target owns its own relay coordinates + sender identity + recipient set. Matches the IIS / Vault pattern.
Prerequisites
- Network egress from the backend to the relay host on the chosen port.
- SMTP AUTH credentials (when
auth_method≠none) — created as a module credential of typesmtp_password. - For PKCS#12 payload mode: a second credential of type
smtp_pkcs12_passphraseholding the bundle's passphrase. - Correct SPF / DKIM / DMARC on the sender's domain (see Deliverability). Without alignment, receivers silently spam-mark the message.
Create the module credential(s)
- Settings → Distribution → Credentials → New.
- Type: SMTP password (
smtp_password). Secret: the raw relay AUTH password. Save. - If a target will use PKCS#12 payload mode, add a second credential of type SMTP PKCS#12 passphrase (
smtp_pkcs12_passphrase) — its secret is the passphrase you'll share out-of-band with the recipient.
Both credential secrets are stored as envelope-encrypted blobs, never in plaintext on disk or in audit logs.
Create an SMTP target
- Settings → Distribution → Targets → New. Target type: SMTP (Email).
- Relay section:
- Host — relay hostname (e.g.
smtp.sendgrid.net,mail.example.com). - Port — 587 for STARTTLS (default), 465 for SMTPS, 25 for plaintext.
- TLS mode — STARTTLS requires the upgrade (blocks downgrade), SMTPS wraps from handshake, plaintext warns loudly and is dev-only.
- Skip TLS verify — operator opt-in for self-signed internal relays. Leave off for anything external.
- Auth method — PLAIN / LOGIN / none. CRAM-MD5, NTLM, OAuth2 / XOAUTH2 are out of scope in v1.
- Username — required when auth_method ≠ none; the password comes from the target's Credential picker, which must hold an
smtp_passwordcredential of this project. With PLAIN or LOGIN the save is refused until one is attached; a PKCS#12 passphrase credential in that slot is refused too. - Timeout (s) — per-send; default 30, max 600.
- Host — relay hostname (e.g.
- Message identity:
- From address — a bare RFC 5322 mailbox such as
certs@example.com. Its domain becomes the EHLO/HELO name, so a display name, angle brackets, quotes or surrounding spaces (Ops <ops@example.com>) are rejected at save. Domain must align with SPF / DKIM / DMARC. - From name — display name in the
From:header. Optional, max 128 chars.
- From address — a bare RFC 5322 mailbox such as
- Recipients:
- TO — required, at least one. Max 50 entries.
- CC / BCC — optional. Use BCC for privacy (recipients don't see each other).
- Payload:
pem(default) — fullchain PEM only. No private key on the wire. The safe default.pem_and_key— fullchain + the private key, both plaintext. The UI shows a warning banner. Only pick when you fully trust the SMTP path.pkcs12— single.p12attachment encrypted with the passphrase from thesmtp_pkcs12_passphrasecredential. The passphrase credential must be of that type and belong to this project — checked at save (create and update) and again when a deploy resolves it. Share the passphrase out-of-band.
- Subject prefix (optional) — prepended to the default subject, e.g.
[PROD]. - Save. Nothing is dialled on save. A health check is a separate, admin-only action run on demand from the module or target page (no scheduler runs it): it dials the relay, walks the TLS policy, and closes the connection (
EHLOthenQUIT) — no AUTH, noNOOP, no message. Choosing plaintext shows a red (error) banner in the form because credentials and the payload would travel unencrypted.
Execution flow
- Worker parses + validates the target's SMTP spec (relay, recipients, payload mode, credential slots).
- Builds the attachments: PEM, PEM + key, or PKCS#12 bundle. The subject is
[{prefix}] CertAutoPilot: certificate for {CN} issued, expires {NotAfter}; body is a fixed English template (CN, SANs, issuer, validity, serial, SHA-256 fingerprint, attachment list). - Dials the relay with the configured TLS mode + AUTH if applicable.
- Sends one email per target containing the full TO / CC / BCC recipient set.
- Emits a success or a classified error (
SMTP_CONNECT,SMTP_TLS,SMTP_AUTH,SMTP_TRANSIENT,SMTP_PERMANENT,SMTP_VALIDATION,SMTP_PAYLOAD).
pem_and_key sends the private key UNENCRYPTED
Every SMTP hop (the relay, any forwarder, the recipient's IMAP server) can read the plaintext key. Use pkcs12 for recipients who need the key — it encrypts the bundle with a passphrase you hand over separately. Reserve pem_and_key for scenarios where you completely trust the delivery path (e.g. a loopback MTA into an air-gapped archive box).
Deliverability — SPF / DKIM / DMARC
CertAutoPilot only sets the SMTP envelope. Whether the mail lands in the recipient's inbox depends entirely on the sender domain's email policy.
- SPF — the TXT record for the
from_addressdomain mustinclude:the relay's SPF or list the sending IP. Mismatch → spam / reject. - DKIM — if the relay signs (SendGrid, Mailgun, Postmark, Google Workspace), publish the DKIM public-key TXT record at
selector._domainkey.<domain>. If the relay doesn't sign, rely on SPF alignment alone. - DMARC —
p=rejectwith neither SPF nor DKIM aligned means silent delivery failure. Start atp=nonewhile validating, move to quarantine/reject once DMARC reports come back clean. - Bounces — the SMTP module is fire-and-forget. Bounce / deferred delivery only surfaces in the relay's audit log, never in the distribution status. Use the relay's dashboard for observability.
Rollback
Rollback re-emails the previous retained version to the recipients (a fresh email of the older cert) through the module's normal Execute path. It does not recall an already-sent email — the earlier message stays in inboxes. RollbackAvailable is true when an eligible previous version exists, and the version picker offers the retained versions. If a certificate was sent in error, manual revocation on the CA is still the remediation for the already-delivered copy.
Error codes
| Code | Class | Retry? | Meaning |
|---|---|---|---|
SMTP_CONNECT | network | yes | TCP dial / DNS failure. Check host, port, firewall. |
SMTP_TLS | auth | no | TLS handshake rejected or cert unverified. Fix trust chain or set tls_skip_verify. |
SMTP_AUTH | auth | no | 530 / 534 / 535 / 538 — authentication required, mechanism too weak, or credentials rejected. Rotate the smtp_password credential + verify username. Also raised before dialling when auth is on but no password credential resolved. |
SMTP_TRANSIENT | io_transient | yes | 421 / 450 / 451 / 452 / 454 or any other 4xx — relay throttled or temp error. Also errors without a reply code that are not a recognised negotiation failure (e.g. a job cancelled mid-send). |
SMTP_PERMANENT | io_permanent | no | 550 / 552 / 554 or any other 5xx — relay denied, quota, or final reject. |
SMTP_VALIDATION | validation | no | 500 / 501 / 503 / 553 — malformed message or recipient. Also the deterministic target-vs-relay mismatches: STARTTLS required but not advertised, AUTH or the chosen PLAIN/LOGIN mechanism not offered, password refused over plaintext. Change tls_mode/auth_method — retrying cannot change the relay's EHLO reply. |
SMTP_PAYLOAD | validation | no | PEM decode or PKCS#12 encode failed before dial. |
Troubleshooting
"dial tcp: connection refused" — SMTP_CONNECT
The backend host can't reach the relay on the configured port. Verify network / firewall (nc -vz host 587). Some cloud providers block outbound 25 by default — use 587 or 465 instead, or a relay that explicitly allows port 25 egress.
"tls: handshake failure" or "x509: certificate signed by unknown authority" — SMTP_TLS
Relay's TLS certificate isn't trusted by the system CA bundle. Install the relay's CA, or set tls_skip_verify=true if it's a self-signed internal relay. For SMTPS (port 465) double-check you're not targeting a STARTTLS-only port.
"535 5.7.8 authentication failed" — SMTP_AUTH
Username or password wrong. Some relays (Google Workspace legacy, M365 pre-OAuth) also reject AUTH unless app-specific passwords are enabled. v1 doesn't support OAuth2 / XOAUTH2 — use a service account with a dedicated app password, or choose a relay that accepts PLAIN / LOGIN.
"unencrypted connection" with tls_mode: plaintext — SMTP_VALIDATION
PLAIN and LOGIN send the password in the clear, so the mail library refuses to authenticate over a plaintext connection unless the host is literally localhost, 127.0.0.1 or ::1. The target still saves; the deploy (and a dry run) fails with SMTP_VALIDATION and is not retried. Use starttls or smtps, or auth_method: none against a relay that allow-lists the CertAutoPilot host IP.
"550 5.7.1 relay access denied" — SMTP_PERMANENT
The relay refuses to forward mail from this sender / to this recipient. Check the relay's accepted domains + SPF alignment. Hosted providers (SendGrid, Mailgun) require explicit sender-domain verification before they'll relay external recipients.
Mail delivers but lands in spam
Almost always a deliverability issue, not an SMTP problem. Check the DMARC aggregate report (rua=) for the sender domain; confirm SPF include: matches the relay, DKIM signature passes, and the DMARC policy isn't p=reject with broken alignment.
See also
Limits and one health-check caveat
to + cc + bcccombined may not exceed 150 recipients;subject_prefixis capped at 200 characters.payload_mode: pkcs12requirespkcs12_passphrase_credential_idat target save, not at deploy time;auth_method: plain|loginlikewise requires ansmtp_passwordtarget credential at save.- Targets are emailed with a concurrency of 2 by default (module
concurrencysetting, capped at 50).
It runs only on demand (admin action on the module or target page) and deliberately connects without authenticating, so a target with a wrong password still reports healthy. A dry run does authenticate — use that to verify credentials.