Deliverability
Polite sending, bounces, complaints and reputation on autopilot.
Polite sending
Bunzzle keeps a speed per destination (per provider for Gmail, Microsoft, Yahoo and others), starting from conservative limits. When a receiver says “too many connections”, the speed is halved and held; when it says your IP is blocked, sending there pauses, then resumes with one probe email and climbs back slowly. Connections are reused. Emails sent with "lane": "bulk" never delay transactional ones: a password reset goes ahead of any queued newsletter, while bulk still gets a small guaranteed share. GET /paths (or bunzzle paths) shows each destination’s current speed, its limits and every change with the reason.
Bounces, complaints and unsubscribes
Receivers report back, and Bunzzle acts on it:
- Signed return path. Once
bounce.your.domainhas the MX and SPF recordsdomains addprints, mail is sent withMAIL FROM:<b-…@bounce.your.domain>. The token is signed, so every bounce maps to the exact email, and forged bounces are refused. SPF then also aligns with your domain. - An inbound listener (
BUNZZLE_INBOUND_PORT, 25 in production) accepts bounce, unsubscribe and feedback addresses, and addresses with a route. It reads standard bounces (DSN), non-standard bounces, complaint reports (ARF, tofbl@bounce.your.domain) and ignores auto-replies. With a certificate (BUNZZLE_INBOUND_TLS_CERT_FILE,BUNZZLE_INBOUND_TLS_KEY_FILE) it offers STARTTLS, so bounces and complaints, which quote your recipients’ addresses, arrive encrypted. An address that keeps guessing recipients, sending unknown commands or idling is refused for 15 minutes; the submission ports do the same. - Inbound routes.
bunzzle routes add support@your.domain https://app.your.domain/inbound(orPOST /routes) posts every message for that address, or for*@your.domain, to your app as JSON: envelope, headers, text, HTML and attachments, or the raw message with--raw. SPF, DKIM, DMARC and blocklist results are inauth, andBUNZZLE_INBOUND_REFUSErefuses failing mail.bunzzle hooks add inbound URL --max-score Nsends each message for a route to a spam or virus filter before it is accepted, the same way as ascanhook, with the client’s IP inIpand its HELO name inHelo. The score is inspam_score; at--max-scoreor above the message is refused with 550, and a filter that does not answer gets 451 with--on-error refuse. Requests are signed in the Standard Webhooks format with the secret shown once. 429 and 5xx answers are retried, withRetry-Afterhonored, for about a day; any other 4xx stops at once, andbunzzle routes retry IDsends those again. Point the domain’s MX at this server. - A suppression list, checked for every email: hard bounces stop all mail for a year, a domain that doesn’t exist for 30 days; complaints and unsubscribes stop bulk mail only, for good. Password resets still reach someone who unsubscribed from a newsletter.
GET/POST/DELETE /suppressionsorbunzzle suppressions. - One-click unsubscribe (required by Gmail and Yahoo for bulk senders) is added to every
"lane": "bulk"email, signed into DKIM, hosted atBUNZZLE_PUBLIC_URL/u/…. - Webhooks:
email.delivered,email.delivery_delayed,email.bounced,email.complained,email.unsubscribed,email.suppressed,email.failed,email.canceled,email.opened,email.clicked,project.limit_approaching. Signed in the Standard Webhooks format. Retried for about a day. Webhook URLs that point to private networks are refused.bunzzle webhooks deliveries ID(orGET /webhooks/:id/deliveries) lists each delivery with every attempt, andbunzzle webhooks resendsends one delivery again with the samewebhook-id, or queues a time range (POST /webhooks/:id/resend) that goes out in small batches at the pace the endpoint accepts. - Event log. With
BUNZZLE_EVENT_LOG=trueevery event also goes to local files, one JSON object per line, rotated daily and gzipped, for log shippers andgrep(seedocs/configuration.md).
Reputation
- IPs and warm-up. List your sending IPs in
BUNZZLE_SOURCE_IPS(iporip=bulkfor a bulk pool). Each IP’s reverse DNS is checked and its PTR name used as EHLO. A new IP warms up by itself: a daily budget per provider that doubles in week one and grows by half after, but only when yesterday was healthy (few deferrals, bounces, complaints, no blocks); trouble steps it back and alerts you.bunzzle ipsshows “warming, day 6 of ~14, today’s budget 1600”. IPs inBUNZZLE_ESTABLISHED_IPSskip warm-up. - Guards. Bulk mail for a domain is held before Gmail’s 0.3% complaint line, counted from complaint reports and, once Postmaster Tools is connected, from Gmail’s own numbers (or at 5% hard bounces), and bulk mail from an API key is held when it suddenly sends ten times its normal volume, as a leaked key would. Transactional mail keeps flowing.
bunzzle holdsshows why;bunzzle holds release IDreopens. - TLS reports. Domains that ask for them (an
_smtp._tlsrecord) get a daily report (RFC 8460) on how TLS went with their mail servers: sessions, certificate problems, missing STARTTLS, per MTA-STS policy. HTTPS reports go out by themselves; emailed reports needBUNZZLE_TLSRPT_FROM. - DMARC reports. With the inbound listener set up, the DMARC record
domains addprints asks mailbox providers to send their daily aggregate reports todmarc@bounce.your.domain. Bunzzle reads them (zip, gzip or plain XML) andGET /domains/:name/dmarcorbunzzle domains reports NAMEshows, per day, which IPs sent mail as your domain and whether it passed DKIM and SPF. A server you don’t know in that list is someone sending as you. - DANE. Recipient domains that sign their DNS and publish TLSA records for their MX hosts get TLS checked against those records, even without a public certificate. Bunzzle checks the DNSSEC signatures itself, from the root down, so no extra resolver is needed; a DNSSEC-validating resolver on the same machine (for example on
127.0.0.1) is used instead when there is one. Answers whose signatures do not check out are not used: their email waits and is retried. A server whose certificate matches none of them is never used.GET /emails/:idshowsdaneas the TLS status when it applied. - MTA-STS. Recipient domains that publish an MTA-STS policy get exactly what they ask for: only their listed MX hosts, only over TLS with a valid certificate.
- REQUIRETLS. An email sent with
require_tls: true(--require-tls, orREQUIRETLSonMAIL FROMover SMTP submission) goes only to a mail server found through DNSSEC or an enforced MTA-STS policy, over TLS with a verified certificate, and only if that server supports REQUIRETLS too, so the requirement travels with the email (RFC 8689). Otherwise it bounces right away (classrequiretls-unsupported, the address is not suppressed); few mailbox providers support REQUIRETLS yet.BUNZZLE_REQUIRETLS=relaxedstill requires TLS with a verified certificate but drops the other two checks. ATLS-Required: Noheader does the opposite: the email ignores the recipient’s MTA-STS and DANE policies, for reaching a server with a broken certificate. - DKIM rotation. Keys rotate every 90 days (
BUNZZLE_DKIM_ROTATE_DAYS): new keys are created and published, Bunzzle switches only after their DNS records verify and a real signature checks out against live DNS, and the old keys stay published for 7 days. WithBUNZZLE_CLOUDFLARE_API_TOKEN(DNS edit for your zone), every record Bunzzle owns is published for you. bunzzle doctorchecks the configuration, every IP’s reverse DNS and blocklist listings, every DNS record of every domain (and what to change), signs a test message and verifies it against live DNS, and lists holds, paused destinations and failing webhooks.bunzzle dns-test DOMAINpublishes a test record through the configured DNS provider, checks that every name server of the zone serves it, and removes it.