Skip to main content

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_methodnone) — created as a module credential of type smtp_password.
  • For PKCS#12 payload mode: a second credential of type smtp_pkcs12_passphrase holding 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)

  1. Settings → Distribution → CredentialsNew.
  2. Type: SMTP password (smtp_password). Secret: the raw relay AUTH password. Save.
  3. 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

  1. Settings → Distribution → TargetsNew. Target type: SMTP (Email).
  2. 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_password credential 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.
  3. 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.
  4. Recipients:
    • TO — required, at least one. Max 50 entries.
    • CC / BCC — optional. Use BCC for privacy (recipients don't see each other).
  5. 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 .p12 attachment encrypted with the passphrase from the smtp_pkcs12_passphrase credential. 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.
  6. Subject prefix (optional) — prepended to the default subject, e.g. [PROD].
  7. 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 (EHLO then QUIT) — no AUTH, no NOOP, no message. Choosing plaintext shows a red (error) banner in the form because credentials and the payload would travel unencrypted.

Execution flow

  1. Worker parses + validates the target's SMTP spec (relay, recipients, payload mode, credential slots).
  2. 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).
  3. Dials the relay with the configured TLS mode + AUTH if applicable.
  4. Sends one email per target containing the full TO / CC / BCC recipient set.
  5. 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

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_address domain must include: 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.
  • DMARCp=reject with neither SPF nor DKIM aligned means silent delivery failure. Start at p=none while 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

CodeClassRetry?Meaning
SMTP_CONNECTnetworkyesTCP dial / DNS failure. Check host, port, firewall.
SMTP_TLSauthnoTLS handshake rejected or cert unverified. Fix trust chain or set tls_skip_verify.
SMTP_AUTHauthno530 / 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_TRANSIENTio_transientyes421 / 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_PERMANENTio_permanentno550 / 552 / 554 or any other 5xx — relay denied, quota, or final reject.
SMTP_VALIDATIONvalidationno500 / 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_PAYLOADvalidationnoPEM 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: plaintextSMTP_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 + bcc combined may not exceed 150 recipients; subject_prefix is capped at 200 characters.
  • payload_mode: pkcs12 requires pkcs12_passphrase_credential_id at target save, not at deploy time; auth_method: plain|login likewise requires an smtp_password target credential at save.
  • Targets are emailed with a concurrency of 2 by default (module concurrency setting, capped at 50).
The health check does not test the password

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.