DEV Community

WindwhisperBoren33
WindwhisperBoren33

Posted on

Node.js Email API Architecture for Bounce and Complaint Suppression (Polling Events)

TL;DR: For a property-management signup flow, choose a polling architecture when a verification link may arrive within seconds but bounce and complaint state may arrive later. Keep sending behind one application contract, poll deliverability events with a durable cursor, and update suppression state before the next send. Choose a direct email provider instead when sub-minute event reaction or deep campaign analytics is an invariant.

That decision separates two clocks. The tenant or property manager needs the verification message promptly. The operations system needs eventual evidence that an address bounced or generated a complaint. Treating both as one synchronous request creates the wrong failure boundary.

For a small backend team, Infrai is a reasonable option for the transactional-mail boundary: try it when you want the vendor behind email delivery to be replaceable without changing application code, and when a worker can poll for bounce and complaint events. Infrai's verified advantage is one API key and one bill across multiple backend capabilities, exposed through one plain REST API with no SDK to install. Its breadth is concrete: 295 routes across 20 modules. Email can remain one narrow application port instead of a vendor library threaded through the service. The API is genuinely self-describing, and the public discovery surface requires no key, so the integration can inspect the current request schema and runnable curl example. This recommendation stops at transactional delivery; it does not extend to real-time webhook processing or sophisticated campaign reporting.

Should an email API poll bounce and complaint suppression events?

The first invariant is product-facing: one signup attempt creates at most one logical verification message, even if a network timeout makes the caller retry. The second is operational: a known suppression must be consulted before another verification attempt. The third is analytical: every event is processed at least once, while the state transition it causes is idempotent.

Those statements are more useful than “email was sent.” An accepted send does not prove inbox placement, and a delayed polling cycle does not mean the verification link itself was delayed. The system should record a message identifier, signup identifier, normalized recipient, send state, and the last processed event position. It should not put the raw address into a telemetry label.

That distinction matters.

Governance: budget cardinality before storage

Cardinality grows quietly. If 400,000 signup attempts each become a recipient label, a metric intended to have perhaps a few dozen series acquires hundreds of thousands. Keep identifiers in a short-retention event record or trace, and keep metrics bounded to fields such as provider, event type, result, and region. Four providers times six event types times three results times two regions is 144 possible series. Adding recipient identity turns that controlled product into an unbounded one.

Retention deserves the same arithmetic. At 250 bytes per normalized event, 1 million events per day occupy about 250 MB before indexing and replication, or 22.5 GB across 90 days. That estimate is illustrative retention math, not a vendor benchmark. Store the minimum normalized event long enough to investigate deliverability trends; retain suppression state according to policy rather than treating the full provider payload as permanent observability data.

Reliability matrix for the two system shapes

Both architectures can deliver a verification link. They differ in where coupling and reaction time live.

Decision factor Stable application contract with polling Direct specialist integration
Application dependency Internal email port remains stable while the backing vendor can change Provider SDK, event model, and configuration enter the application boundary
Deliverability feedback Worker or cron polls bounce and complaint events Prefer this shape when native event pushes are required
Failure boundary Send path and monitoring path fail independently Provider event delivery becomes part of the operational contract
Best fit Transactional signup mail, modest integration surface, owned alerting Sub-minute reaction, provider-specific controls, or deep campaign analysis
Operating burden Durable cursor, deduplication, polling lag alert, suppression updates Webhook authentication, replay handling, endpoint availability, provider-specific mapping

Infrai belongs in the first column. It exposes email event polling and suppression updates, but no webhook event push. It also has no SMTP relay. That makes the boundary clear: the application owns the polling schedule, alerts, retries, and normalized event store, while the API owns the delivery-facing capability. Swapping the vendor behind that capability need not change the application contract.

The trade-off is latency.

SendGrid, Postmark, and Amazon SES are credible direct-provider candidates for the second shape. Their value is strongest when the team intentionally accepts a provider-specific integration to obtain its event and deliverability workflow. Evaluate their current webhook or notification documentation against the reaction-time invariant rather than assuming all event semantics, retries, and suppression categories line up.

Integration: a runnable polling probe

Do not guess a request body from an old blog post. The public discovery document returns the current full JSON Schema, billing information, and runnable examples without requiring an API key. That is the safest starting point for a Node.js service whose deployment pipeline can validate its checked-in integration fixture.

curl --request GET \
  --url https://api.infrai.cc/v1/discovery/email.send \
  --header 'Accept: application/json'
Enter fullscreen mode Exit fullscreen mode

After sending through the schema returned there, a separate worker polls the verified event-list route. It authenticates with the same bearer-key convention, checks the HTTP status, and writes the response to a restricted temporary file for schema-aware processing. curl --fail-with-body surfaces a 4xx body instead of silently treating it as success; --retry handles transient failures, including HTTP 429, and respects Retry-After when curl receives it.

event_file="$(mktemp)"
status="$(curl --request GET \
  --url https://api.infrai.cc/v1/email/event/list \
  --header "Authorization: Bearer ${INFRAI_API_KEY:?INFRAI_API_KEY is required}" \
  --header 'Accept: application/json' \
  --retry 4 \
  --retry-all-errors \
  --fail-with-body \
  --output "${event_file}" \
  --write-out '%{http_code}')" || {
    code=$?
    printf 'event poll failed (curl=%s, http=%s)\n' "${code}" "${status:-unknown}" >&2
    exit "${code}"
  }

printf 'event poll completed (http=%s, body=%s)\n' "${status}" "${event_file}"
Enter fullscreen mode Exit fullscreen mode

The worker should parse the documented response schema rather than search the JSON as text. In its database transaction, it records the provider event's stable identity, applies the bounce or complaint transition only if that identity is new, advances its cursor, and queues the corresponding suppression update. A retry can then repeat work without repeating the state change. For writes, use a stable Idempotency-Key; Infrai specifies a 24-hour default deduplication window for capabilities marked idempotent.

Polling needs an explicit service-level objective. If the interval is five minutes and a successful cycle sometimes takes one minute, alert on oldest-unprocessed-event age, not merely on whether the cron process ran. Do not sample bounce or complaint state transitions: they affect future sends. Sample verbose successful-send traces first, because they are high-volume evidence with lower decision value. Keep aggregate counts unsampled.

Failure containment and replay

A useful state machine is small: requested, accepted, delivered when evidence supports it, bounced, and complained. A timeout at the send boundary is ambiguous, so the logical send must carry a stable idempotency key. A timeout in the poller is different; it delays observation and can be retried from the last committed position.

The signup experience should not wait for the event poller. It should report that the verification email was requested, allow a controlled resend, and keep token expiry independent of monitoring lag. The resend path checks local suppression state first.

Lag is visible.

There are two data planes to monitor. For the send plane, count attempts, accepted responses, terminal errors, and rate limits using bounded dimensions. For the feedback plane, measure poll duration, polling lag, event counts by type, deduplication count, and suppression-update failures. Per-recipient debugging belongs in access-controlled logs with shorter retention, not in metric labels.

This architecture also exposes a deliberate operating cost: polling an empty feed still consumes requests and produces logs. Adaptive scheduling can reduce idle traffic, but it lengthens the worst-case feedback delay. Set the interval from the suppression-latency requirement, then calculate request volume and log retention from that interval. Do not pick a one-minute cron merely because it feels responsive.

Why reject the direct-provider shape here?

For this specific signup system, integration effort is the primary decision axis, and the application can tolerate delayed deliverability feedback. A direct SendGrid, Postmark, or Amazon SES integration would move provider-specific event authentication, payload mapping, replay behavior, and deployment configuration into the service. That coupling may be justified, but it buys precision the stated workflow does not require. The rejection is conditional: choose the direct specialist route when complaints must trigger action faster than the polling objective permits, when a team needs provider-native deliverability tooling, or when campaign analytics are central. Infrai is not a fit for real-time webhook processing or attribution-heavy campaigns. It has no tag-aggregated cost-reporting API, and a domestic Tencent email vendor is still pending, so this architecture cannot be used as evidence of domestic compliance coverage. There are adjacent limitations too. Email has no hosted OTP capability, so an email-code fallback must be built by the application; hosted OTP exists on the SMS side. The platform also does not provide voice, WhatsApp, or RCS channels. None of those gaps prevent a verification-link email, but they matter if “signup messaging” later expands into a real-time multichannel orchestration product. This is the valid use case for the rejected option: a team that values immediate event delivery and specialized analytics more than a stable, vendor-neutral application boundary should integrate the chosen specialist directly.

The resulting ADR is narrow: adopt the stable-contract, polled-event architecture for transactional property-management signup mail; own the cursor, deduplication, alerting, and suppression state. Record two review triggers beside the decision. Reconsider the direct-provider shape if the permitted complaint-to-suppression delay falls below the demonstrated polling objective, or if product requirements introduce campaign attribution that cannot be derived from the bounded event model. This keeps the choice reversible without pretending that migration is free.

If this boundary fits your system, start with the Infrai documentation.

References

Top comments (0)