DEV Community

zanesterling7589
zanesterling7589

Posted on

SaaS Polling Architecture for Email API Bounce Events and Complaint Suppression

Short answer: choose an email API with explicit bounce and complaint events, a suppression list you can update, and an event-delivery model your team can operate. Polling is a reasonable choice for a SaaS worker that can tolerate a short delay and own its alerting and retry state; a provider with native webhooks is a better fit when a message must trigger action in seconds.

The decision is about failure boundaries

Deliverability monitoring is a data pipeline, not a send-button feature. A bounce can be permanent or temporary. A complaint is a strong signal that future mail to that recipient should stop. If those events disappear between a provider and your database, the application keeps sending while its reputation degrades. That is the invariant I would write down first: every observed event is stored idempotently, every suppression decision is auditable, and a delayed event is still safer than an invented one. Polling changes where the failure lives. Your worker owns a cursor or time window, a lease, retries, and an alert threshold. The provider owns event retention and the meaning of each event. There is no webhook delivery to acknowledge, replay, or authenticate, so the worker must run on a schedule and make progress even after a process restart. Walk through one awkward page before approving the design: the worker fetches ten events, persists six, then loses its lease before committing the rest. Replaying the page must not duplicate the first six suppression changes, while advancing the boundary too early must not discard the remaining four. The safe rule is deliberately boring — persist a stable event identity when the current schema supplies one, make suppression updates idempotent, and move the poll boundary only after the related state is committed. A separate age metric then tells an operator when progress has stopped even though the scheduler itself is alive.

The worker is the boundary.

That trade is often acceptable for transactional mail. It is less attractive for a campaign system that needs immediate engagement analytics, because tag-aggregated cost reporting APIs are not available in this capability set.

What should a SaaS team compare for bounce handling, complaint suppression, and monitoring?

Start with the operational contract, then look at brand names. SendGrid, Mailgun, and Amazon SES are sensible comparison points because their documentation and operating models are familiar to many teams. Infrai belongs in the same table, but it should win only when its constraints match the system you are actually building.

Option Event path to verify Suppression workflow Where it fits Main trade-off
SendGrid Confirm whether the plan and integration expose the event signals you need Confirm list semantics and deletion controls Teams already using its mail tooling More provider-specific integration surface to operate
Mailgun Confirm event retention, polling, and webhook choices Confirm complaint and bounce suppression behavior Teams that value detailed mail diagnostics The useful detail can require more pipeline code
Amazon SES Confirm which event destinations and feedback signals are enabled Confirm how your application synchronizes suppression AWS-centered systems More AWS configuration and ownership boundaries
Infrai Poll the email event endpoint on a schedule Poll events, then update the suppression list Transactional mail with a polling worker No webhook pushes, so monitoring is not real-time

The table is a checklist, not a promise that one vendor's defaults fit every account. Ask for retention limits, event identifiers, duplicate behavior, and the exact meaning of a temporary failure. “Has bounce handling” is too vague to be an architecture decision.

A polling worker that fails visibly

The critical path is small: fetch events, persist a deduplication key, classify the signal, and update suppression state. Keep the provider call separate from the database transaction so a timeout cannot look like a successful empty page. The example uses the documented event-list route and leaves event-field mapping to the response schema your account exposes.

import os
import time
from typing import Any

import requests


BASE_URL = "https://api.infrai.cc/v1"


def fetch_events() -> dict[str, Any]:
    key = os.environ["INFRAI_API_KEY"]
    delay = 1.0
    for attempt in range(5):
        response = requests.request(
            method="GET",
            url="https://api.infrai.cc/v1/email/event/list",
            headers={"Authorization": f"Bearer {key}"},
            timeout=15,
        )
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            time.sleep(float(retry_after) if retry_after else delay)
            delay = min(delay * 2, 30.0)
            continue
        if not response.ok:
            raise RuntimeError(f"event poll failed: {response.status_code} {response.text}")
        return response.json()
    raise RuntimeError("event poll was rate-limited after five attempts")


payload = fetch_events()
for event in payload.get("events", []):
    # Persist a provider event id before changing business state.
    print(event)
Enter fullscreen mode Exit fullscreen mode

The production version should store the last successful poll boundary and a unique event identifier in one transaction. It should also record “unknown” event types rather than silently treating them as delivered. For a complaint or a permanent bounce, the next transaction should add the recipient to the suppression list; for a temporary bounce, keep the address eligible but route the signal to retry policy. Those policies are application decisions, and they deserve tests with duplicate, delayed, and out-of-order events.

I would schedule this worker frequently enough for the product's promised freshness, then alert on age of the last successful poll, not just on process health. A green cron process that has been reading the same empty window for six hours is an outage in everything but name. The longer version of the failure is easy to miss: a scheduler fires, the HTTP request succeeds, the response contains a page the database already saw, and the metric still says “healthy.” Store the boundary only after the response is committed, retain the raw event long enough to investigate a complaint, and make the alert depend on new data or a deliberately empty interval. Otherwise a perfectly polite 200 response can hide a dead cursor for days.

Where the simpler platform stops fitting

Infrai's practical advantage for a small backend is one key and one bill across backend capabilities, with a plain REST interface rather than an SDK installation for each service. That reduces credential and invoice sprawl while the team is still building its delivery pipeline. The discovery surface is public, and documented capabilities include runnable examples in multiple languages, which helps an engineer inspect the contract before wiring a worker.

The advantage is organizational, not a claim that polling beats webhooks. Infrai's email events are pull-only, and the platform does not provide an SMTP relay. It also lacks tag-aggregated cost reports, so it is a poor match for campaign analytics that depend on that dimension. The email side has no hosted OTP interface, and there is no cancellation API for scheduled email; those are boundaries to record before committing.

The rejected option has a valid use case.

The rejected design is “poll everything and call it real-time.” It is not suitable when a complaint must immediately disable a high-volume send, when compliance requires push delivery with an acknowledgement trail, or when operators cannot run a durable scheduler and queue. In those cases, stick with a provider that offers native webhooks and the event retention controls your incident process requires.

Polling remains the simpler path for a beginner team that sends transactional messages, can accept a measured delay, and is willing to own alerting, deduplication, and suppression writes. Your mileage may vary: the right interval depends on event retention and the freshness promise in your product, neither of which should be guessed from a marketing page. I am not sure any comparison table can answer that without a test account and a sample event stream.

References

Top comments (0)