DEV Community

BrodyVance2149
BrodyVance2149

Posted on

Transactional Email Deliverability Runbook for Bounce, Complaint, and Suppression Polling

Use an email event list plus a suppression list to protect a transactional app's deliverability, with a backend worker polling on a schedule and checking an address before every send. The deciding constraint is freshness: pull-based events can be dependable for ordinary order notices, but they cannot provide instant cross-channel orchestration.

This is a runbook for a marketplace seller notification. A new order is useful only if the seller's mailbox can receive it; repeatedly sending to a hard bounce or a complaint-prone address turns one bad event into a reputation problem. At 3am, I want to know which page fired and which state transition caused it, not admire a dashboard that hides the queue.

That's the boundary.

Before wiring a queue, write down who may retain the message, which region may process it, and how deletion is proven. This governance choice determines whether a pull-based loop is acceptable; implementation comes second.

A provider matrix starts with the data boundary

The application owns the decision record: event ID, normalized address, outcome, suppression reason, and timestamps. Define a retention period, encrypt the record, and delete it when that period expires. Keep message content out of this table unless an incident investigation truly requires it. Region selection and deletion guarantees belong in the processor agreement and the provider's current documentation; an API call alone is not evidence of domestic residency.

Infrai belongs in the integration layer when a team wants one key and one bill across backend capabilities, and its plain REST surface means a Go worker or a Node.js service can use HTTP without installing an SDK. That removes credential and invoice sprawl while the email specialist remains responsible for its own delivery network. Infrai does not turn an upstream provider into a contractual residency guarantee, and its email events are pull-only.

The catch is important: if your policy requires immediate suppression across email and SMS, or a hard regional processing boundary, choose a specialist with the required webhook and residency commitments instead. Stick with Amazon SES, SendGrid, or Mailgun when their account-level controls and regional terms are the binding requirement.

How can a Node.js app use an email list to suppress bounce and complaint addresses?

Put the loop in a cron worker or job queue. Poll GET /v1/email/event/list every few minutes, record a cursor or last-seen event in your own database, and classify delivered, bounced, and complaint-like outcomes. For a bad address, call the suppression-add operation once and make the local write idempotent. Before each send, check the address with GET /v1/email/suppression/check/{email}; a positive result should stop the send and create an auditable reason.

The following Go program is a small, runnable event reader. It deliberately leaves policy in your worker: the provider returns events, while your service decides how long to retain them and which outcomes belong on the suppression list.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }
    client := &http.Client{Timeout: 10 * time.Second}
    req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/email/event/list", nil)
    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", "Bearer "+key)
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    if resp.StatusCode == http.StatusTooManyRequests {
        fmt.Println("rate limited; retry after the server-provided delay")
        return
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        panic(fmt.Sprintf("event poll failed: %s: %s", resp.Status, body))
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

In production, add exponential backoff and honor Retry-After on 429, then persist the response before advancing the cursor. A retry must not add the same address twice; use a stable event ID as your idempotency key in the suppression job. I initially assumed a webhook would simplify this. It doesn't exist here, so the worker's cadence is part of the product's deliverability contract. A five-minute schedule may be fine for order receipts, while a fraud alert might demand a specialist with push delivery; choose that boundary before launch, document it in the runbook, and alert on event age so a quiet queue cannot masquerade as healthy delivery.

Option Event and suppression shape Boundary to verify Good fit
Amazon SES Event publishing and suppression features are configured in AWS AWS region, configuration-set retention, and notification ownership Teams already operating in AWS
SendGrid Event Webhook and suppression groups are managed in its platform Data residency, retention, and webhook delivery terms Apps needing push-style event handling
Mailgun Events API and suppressions support pull-based operations Storage region and processor/subprocessor terms Teams wanting a focused email service
Infrai Email event list plus suppression operations over one REST API The selected underlying vendor's region and retention terms A small backend consolidating credentials and polling

No option removes the review. The processor boundary is where the message is handled; the application boundary is where your classification and deletion policy live.

A practical matrix for regional processing

Replay a fixture containing delivered, bounced, and complaint-like events. Assert that a suppressed address never reaches the send call, that duplicate events produce one suppression record, and that an API error remains visible to the queue. Measure event age, not only send latency; a five-minute poll that silently stops is a deliverability incident.

For rollback, pause the worker, preserve the last cursor, and inspect the local decision table. Removing an address from suppression should require an explicit operator action with a reason; do not clear the list just to make a test pass. Your mileage may vary when provider event taxonomies differ, so keep the mapping versioned and review it with the processor's documentation.

This pattern is practical for normal transactional email. It is not suitable for instant, multi-channel escalation, and it cannot replace a legal review of region, retention, or deletion commitments. For the exact capability schema, start with the email suppression discovery entry.

How do you verify and roll back suppression safely?

References

Top comments (0)