Coldfeet can terminate inbound SMTP itself (coldfeet_mx) or let an ESP
(Amazon SES, Mailgun, SendGrid, Resend, Postmark, MailerSend) accept mail and replay it into
the pipeline over an authenticated webhook (provider_mx).
Functionality only requires the domain’s MX change. Legitimate senders deliver to the ESP; Coldfeet filters/quarantines and relays clean mail to the final destination (Microsoft 365, Google Workspace, Exchange, etc.). Restricting the backend connector / firewall to gateway IPs is optional hardening — not a prerequisite.
Internet Sender
│
▼
MX Record ──────────────► ESP (SES / SendGrid / Mailgun / Resend / Postmark / MailerSend)
│
▼
Authenticated webhook ──► Coldfeet (scan + quarantine)
│
▼
Relay clean mail ───────► Final mailboxes
Provider MX keeps Coldfeet’s IP off public DNS. The ESP absorbs abuse and connection load; Coldfeet still scans, applies policy, and delivers to the tenant’s smart host.
Because the ESP has already accepted the message before Coldfeet sees it:
550 for unknown recipients (SES recovers this via recipient-scoped rules)Unknown recipients on SendGrid/Mailgun/Resend/Postmark/MailerSend are dropped and recorded in
Rejections (source=webhook). We never generate a bounce to a forged sender
(backscatter). On SES, mail for domains that have not been promoted is rejected
at the SES edge with 550 because receipt-rule recipients are scoped to promoted
FQDNs only.
Rspamd still scores RBL/reputation against the recovered origin IP from the
Received: chain.
provider_mx cannot be enabled without a configured delivery destination
(relay host/port, or local mailbox hosting). Without that, scanned mail would
MX-lookup the ESP and loop back into the webhook ingest.
Outbound ESP relay is also blocked when any recipient domain is hosted here with
provider_mx, for the same reason.
Webhook ingest also refuses recipients whose domain is still in coldfeet_mx
mode, even if the signature verifies.
Platform ceiling is 35 MB (nginx client_max_body_size 40m, Nest parser
scoped to /api/v1/public/inbound and /api/v1/public/relay-events). Larger
messages are rejected with the provider’s stop-retry status.
MTA-STS and TLS-RPT are suppressed for provider-MX domains. A Coldfeet policy naming our mail host would cause enforcing senders to refuse delivery to the ESP.
If the domain already publishes _mta-sts naming Coldfeet:
_mta-sts TXT recordmax_age (default 7 days)| Provider | MX | Outbound SMTP | Size | Auth | stopRetryStatus | Inbound automation |
|---|---|---|---|---|---|---|
| Mailgun | mxa.mailgun.org / mxb.mailgun.org | smtp.mailgun.org / smtp.eu.mailgun.org | 35 MB | HMAC over timestamp+token | 406 | Routes created via API |
| SES | inbound-smtp.<region>.amazonaws.com | email-smtp.<region>.amazonaws.com | 35 MB | SNS signature + topic ARN / bucket pin | 200 | S3 + SNS + receipt rule provisioned from IAM keys |
| SendGrid | mx.sendgrid.net | smtp.sendgrid.net | 35 MB | ECDSA via security policy; send_raw=true | 200 | Inbound Parse + security policy via API |
| Resend | inbound-smtp.<region>.amazonaws.com | smtp.resend.com (user resend) | 35 MB | Svix HMAC-SHA256 on ${id}.${timestamp}.${rawBody} | 406 | email.received webhook + Receiving API fetch |
| Postmark | inbound.postmarkapp.com | smtp.postmarkapp.com | 35 MB | URL token + Postmark webhook IP allowlist (no HMAC) | 406 | Server InboundHookUrl + InboundDomain via API |
| MailerSend | inbound.mailersend.net | smtp.mailersend.net | 35 MB | HMAC-SHA256 Signature header over raw body | 406 | Inbound route webhook via API |
Webhook endpoints:
POST /api/v1/public/inbound/:providerPOST /api/v1/public/relay-events/:providerPostmark inbound and bounce URLs include ?token=<high-entropy>; both token and
source IP (Postmark webhook IPs below) must match.
PUBLIC_API_URL must be set (install sets it to WEB_URL/api). Coldfeet
normalizes /api → /api/v1 when registering notify URLs so they match the
reverse-proxy routes.
inbound-smtp.<region>.amazonaws.com (priority 10). Regions:
us-east-1, eu-west-1, sa-east-1, ap-northeast-1.smtp.resend.com:587, username resend, password = API key.email.received. Payload is metadata only.GET /emails/receiving/{email_id} → raw.download_url.svix-id, svix-timestamp, svix-signature
(v1,<base64>). HMAC-SHA256 over ${id}.${timestamp}.${rawBody} with the
webhook signing secret (whsec_…).inbound.postmarkapp.com (priority 10).smtp.postmarkapp.com:587; username and password are both
the server token.apiKeyEnc.3.134.147.250, 50.31.156.6, 50.31.156.77,
18.217.206.57 (source).
Coldfeet caches and refreshes this list daily.RawEmail / raw-email field on the inbound JSON POST.inbound.mailersend.net (priority 10).smtp.mailersend.net:587; per-domain SMTP users created via
POST /v1/domains/{id}/smtp-users (Mailgun shape — no account-level fallback).POST /v1/domains.POST /v1/inbound with webhook forward; route returns an
HMAC signing secret in forwards[].secret.Signature header = HMAC-SHA256 hex of raw request body.data.raw on inbound.message webhook payload.Relay accounts need only three credentials: AWS access key ID, secret access key, and region. Coldfeet:
/coldfeet/ when the pasted key can iam:CreateUserinbound/ (transient spool — not the quarantine), and a bucket policy allowing ses.amazonaws.com s3:PutObjectTlsPolicy: Require, S3 + SNS actions)provider_mx, adds the FQDN to the rule’s Recipients (spilling to a second rule past 100 recipients)Required IAM actions are available from Admin → Workers → Relay accounts →
AWS policy JSON (GET /api/v1/admin/egress/relay-accounts/aws/policy).
Sandbox is detected during preflight/test — production accounts are assumed.
Quota (Max24HourSend / MaxSendRate) is stored and used to prefill caps and
pace sends.
Multiple relay accounts can coexist. Outbound selection is sticky per domain with priority failover and hourly/daily (plus provider-quota) headroom checks.