Files
stack/infra/mail/maddy.conf.tpl
kert 16f3b43974
Some checks failed
CI / skinny-install (aco) (push) Successful in 1m12s
CI / skinny-install (api) (push) Successful in 30s
CI / skinny-install (bcda) (push) Successful in 36s
CI / skinny-install (bib) (push) Successful in 35s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 41s
CI / skinny-install (cms) (push) Successful in 37s
CI / skinny-install (conf) (push) Successful in 38s
CI / skinny-install (opps) (push) Successful in 33s
CI / skinny-install (perf) (push) Successful in 38s
CI / skinny-install (pfs) (push) Successful in 38s
CI / skinny-install (rex) (push) Successful in 34s
Deploy / build-scan-report (push) Failing after 46s
Infra CI / notebooks (push) Failing after 25s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Failing after 16s
CI / lint-test (push) Failing after 11m2s
Infra CI / mc (push) Successful in 21s
Infra CI / api (push) Successful in 29s
Package Supply Chain / pkg-supply-chain (push) Failing after 41s
feat: full session — mail servers, comment pipeline, PRISMA fetch, email ingest
Mail: Maddy on DO (corwins.media+Resend, fhirworx.io+Postmark),
touchless/stateless/idempotent. Gitea SMTP via env_file. CMS inbox
at cmsupdates@mail.fhirworx.io with IMAP→bib poller.

Bib: regulations.gov v4 client, Federal Register discovery, 164K
comment backfill (running), IMAP email ingest, Zotero sync routing.

PRISMA: altcha PoW solver, CrossRef DOI resolution, 83/129 PDFs.
Zotero: schema parity, ops module, CLI, fail-fast guard.
CI: docs.Dockerfile COPY glob fix (tracks #341).
Infra: Gitea+marimo fhirworx themes, IOM/OIG modules.
2026-04-16 09:04:38 -04:00

148 lines
4.3 KiB
Smarty

# Maddy mail server config — minimal single-domain setup with DKIM
# autosigning. Tracks the shape of the upstream reference config so
# upgrades track cleanly.
#
# Placeholders (substituted by mail-setup.sh via envsubst):
# $HOSTNAME -> mail.corwins.media
# $PRIMARY_DOMAIN -> corwins.media
hostname $HOSTNAME
$(primary_domain) = $PRIMARY_DOMAIN
# Accept mail at both the apex (e.g. git@fhirworx.io for outbound
# SMTP submission and DKIM signing) and the host subdomain (e.g.
# cmsupdates@mail.fhirworx.io, which is what actually MX-routes to
# this droplet when another provider holds the apex MX).
$(local_domains) = $(primary_domain) $HOSTNAME
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
# ── Authentication / storage ─────────────────────────────────────
#
# Accounts live in credentials.db; IMAP mailboxes live in imapsql.db.
# Both are SQLite files under /data and survive container restarts.
auth.pass_table local_authdb {
table sql_table {
driver sqlite3
dsn credentials.db
table_name passwords
}
}
storage.imapsql local_mailboxes {
driver sqlite3
dsn imapsql.db
}
# ── SMTP receive (port 25, from the world) ───────────────────────
#
# Plain SMTP inbound, STARTTLS opportunistically, no auth (inbound
# MTAs shouldn't authenticate). DMARC + DKIM + SPF checks gate the
# inbound stream; anything forging our domains is rejected.
smtp tcp://0.0.0.0:25 {
limits {
all rate 20 1s
all concurrency 10
}
dmarc yes
check {
require_mx_record
dkim
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission (port 587) to send as us"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User not found"
}
}
}
# ── SMTP submission (ports 465 TLS, 587 STARTTLS, authenticated) ──
#
# Our own outbound send path. Clients (Gitea, Thunderbird, mail-merge
# scripts) authenticate against local_authdb, and outbound mail is
# DKIM-signed before hitting the remote queue.
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
limits {
all rate 50 1s
}
auth &local_authdb
source $(local_domains) {
check {
authorize_sender {
prepare_email identity
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Unknown sender domain"
}
}
# Outbound goes through an external SMTP relay (smarthost), not
# direct MX — DigitalOcean silently blocks outbound port 25 on new
# droplets. The real `target.smtp outbound_smarthost { }` block is
# injected at the top of this file by `stack mail attach-smarthost`.
# Until then, the stub below lets maddy start cleanly so inbound,
# IMAP, and DKIM key generation all work — outbound just queues
# locally and never delivers (intentional).
target.smtp outbound_smarthost {
targets tcp://127.0.0.1:9
auth plain "stub" "stub"
}
target.queue remote_queue {
target &outbound_smarthost
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
# ── IMAP (port 993 TLS, 143 STARTTLS) ────────────────────────────
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
auth &local_authdb
storage &local_mailboxes
}
# ── Local delivery pipeline ──────────────────────────────────────
msgpipeline local_routing {
destination postmaster $(local_domains) {
deliver_to &local_mailboxes
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}