Skip to content

Issue certificates in the image and correct silent mail failures - #19

Merged
Baker merged 3 commits into
mainfrom
fix/startup-auth-and-certs
Nov 7, 2026
Merged

Baker merged 3 commits into
mainfrom
fix/startup-auth-and-certs

Conversation

@Baker

@Baker Baker commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Summary

The published image should obtain a certificate and start mail without a wrapper script, and a failure along the way should stay a failure instead of being reported as success.

  • The entrypoint requests a Let's Encrypt certificate when Cloudflare DNS credentials are set, using devnull@localhost when no account email is provided. Issuance is skipped or allowed to fail without stopping SMTP. Empty or unreadable certificate files are not passed to the process. The Cloudflare token is removed from the environment before SMTP starts, and the process drops to the unprivileged user after writing /cert.
  • SMTP shutdown and listeners: SIGTERM is handled before Serve blocks, a failed bind closes both listeners, and direct clients no longer wait out the PROXY header timeout for the 220 banner. A second MAIL FROM clears recipients from the aborted transaction. The recipient cap stays on the session, because go-smtp's own counter is not cleared when MAIL restarts a transaction.
  • Authentication and delivery: a webhook redirect is not a successful delivery. Permanent SPF DNS errors stay permanent. DMARC relaxed alignment and policy lookup use the public suffix list, and only the From domain plus the organizational domain are queried. A DNS error during that lookup does not inherit a parent policy. Multiple From domains are a permanent error. MESSAGE_PROCESSING_TIMEOUT bounds SPF, DKIM, and DMARC. pct=0 is included in the DMARC JSON. Webhook metrics label the status with the numeric code.

Test plan

  • go test -count=1 ./...
  • Boot the image with CF_Token and CF_Zone_ID unset and confirm SMTP starts without STARTTLS
  • Boot with a non-empty garbage file in /cert and confirm the process stays up without STARTTLS
  • Boot with valid DNS credentials and confirm a certificate is issued, the token is absent from the SMTP process environment, and the process is not root
  • Send SIGTERM to a running server and confirm it exits after the shutdown log
  • Point the webhook at an endpoint that returns 302 and confirm SMTP does not answer 250
  • Send a message whose From domain is a subdomain of a multi-label public suffix and confirm DMARC uses that organizational domain

Made with Cursor

Baker and others added 3 commits November 7, 2026 21:18
Operators should get STARTTLS from the published image alone, and a failed certificate, a redirect, or a DNS error should not be reported as a successful delivery.

Co-authored-by: Cursor <cursoragent@cursor.com>
golangci-lint v2.15 rejects Listener.Policy, and the organizational-domain helper was unused after alignment moved to the public suffix list.

Co-authored-by: Cursor <cursoragent@cursor.com>
Go 1.26.3 and golang.org/x/net v0.54.0 are in the vulnerable range for GO-2026-6809 and GO-2026-6617.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Baker
Baker merged commit 9b2fa2e into main Nov 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant