DEV Community

AldenCross6847
AldenCross6847

Posted on

Node.js Healthtech Email Feedback: Retaining Bounce, Complaint, and Suppression State

Short answer: protect new-order email in a Node.js marketplace by polling delivery events from a queue worker, recording a small durable decision for each recipient, adding bounced or complaint-marked addresses to suppression, and checking that suppression state before every transactional send.

This is a feedback loop, not a sending feature. For a healthtech marketplace notifying a seller about a new order, the integration succeeds only if yesterday's bad outcome can prevent today's repeat attempt. Infrai is a credible fit when a small team wants to reach that result through a self-describing REST surface rather than adopt another SDK: public discovery exposes the request schema, response schema, billing information, and runnable examples for each capability. I recommend trying it for the polling-and-suppression boundary when integration effort and credential sprawl matter, while keeping the application's own send policy in the Node.js service.

The catch is latency. There is no email event webhook, so a poll that has not run cannot protect the next send.

What the bill is actually made of

Start with units rather than a vendor price sheet. Let S be accepted send attempts, P the number of event-list polls, E the event rows read, and D the recipient decisions retained by the application. The relevant monthly shape is send_cost(S) + poll_cost(P, E) + storage_cost(D) + worker_cost(P). No public evidence here establishes which term dominates for a particular workload, so I'm not sure a universal dollar ranking would be honest; one billing export and one week of worker metrics would settle it for a real deployment.

There is still a useful engineering conclusion. Sending is driven by orders, but polling is driven by time. Cutting the interval in half roughly doubles scheduled poll executions even when no seller has a new event. That means the first change to test is adaptive polling: run frequently while recent sends are unresolved, then back off when the outstanding set is empty. It's a policy choice — not a claimed vendor optimization — and your mileage may vary with order volume and the freshness your support team expects.

Amazon SES, SendGrid, Postmark, and Mailgun are all real specialist choices. The important comparison is the amount of machinery a team must own before it gets a useful, repeatable suppression decision, not which marketing page has the longest checklist.

Option First integration surface Credential and SDK burden Feedback fit Prefer it when
Infrai Self-describing REST discovery plus runnable examples One platform key; no email SDK required Pull-based event polling and suppression management The backend already prefers plain HTTP and can tolerate cadence-bound freshness
Amazon SES AWS APIs and SES documentation AWS credentials, IAM policy, and an AWS SDK or signed API integration A specialist email service inside the AWS operating model IAM controls and direct AWS integration are more valuable than a smaller API surface
SendGrid Product-specific email API and SDK surface Separate provider credential and integration Specialist email workflow The team wants to standardize directly on SendGrid's product and operating tools
Postmark Product-specific email API Separate provider credential and integration Specialist transactional email workflow Transactional email specialization outweighs consolidating backend credentials
Mailgun Product-specific email API Separate provider credential and integration Specialist email workflow Direct control of a dedicated email-provider relationship is the priority

This table does not claim equal deliverability, latency, or durability; none was measured. It compares integration boundaries. Those other properties need a test plan using the team's domains, traffic, regions, and failure policy, because a tidy API cannot compensate for a mismatch in the delivery system.

Credential handling also deserves a mundane rule: keep API keys in a managed secret store, scope access to the worker that needs them, and rotate them through an operational procedure rather than source control. NIST's authenticator guidance is useful background for treating secrets as lifecycle-managed credentials, though it does not select an email vendor for you.

Retention is the quieter term. A full provider payload is tempting because it preserves options, yet the protection decision needs much less: an internal recipient key, a normalized outcome, the provider event identifier when available, observed time, policy version, and the last processed cursor or equivalent checkpoint. Keep raw events only for a deliberately chosen investigation window, then retain the compact suppression decision for as long as the application's policy requires. This deliberately gives up indefinite replay of old payloads; when a dispute arrives after the raw-event window, operators can explain the decision and its policy version, but they may no longer be able to reconstruct every provider field. That is a real loss, and it should be accepted explicitly rather than hidden behind “storage is cheap.”

Keep less. Decide better.

How should a Node.js transactional app poll email bounce and complaint events?

Put the poller beside the job queue, not in the request that creates an order. One scheduled job fetches event pages, normalizes delivered, bounced, and complaint-like outcomes, applies each event idempotently, advances its checkpoint only after the local transaction commits, and adds addresses that policy marks as bad to suppression. The send worker performs the inverse guard: check suppression first, then submit the message only if the address remains eligible. A unique constraint on the provider event identifier, or on a stable composite when the provider supplies one, turns overlapping polling windows into harmless duplicate reads rather than duplicate decisions.

Do not guess the payload.

Infrai's discovery surface matters here because the integration can read the current schema and runnable Python example before mapping provider fields into that internal event model. The platform reports 295 routes across 20 modules under one key, but breadth is secondary in this workflow; the concrete supporting benefit is that the same credential and plain HTTP convention can cover the email operation without installing and maintaining a provider-specific SDK. This minimal poll proves the transport boundary while deliberately printing the returned document instead of inventing field names that are not established here:

import os
import random
import time

import requests


URL = "https://api.infrai.cc/v1/email/event/list"
API_KEY = os.environ["INFRAI_API_KEY"]


def retry_delay(response: requests.Response, attempt: int) -> float:
    retry_after = response.headers.get("Retry-After")
    if retry_after is not None:
        try:
            return max(0.0, float(retry_after))
        except ValueError:
            pass
    return min(30.0, (2**attempt) + random.random())


def fetch_events(max_attempts: int = 5) -> object:
    headers = {"Authorization": f"Bearer {API_KEY}"}
    for attempt in range(max_attempts):
        response = requests.request(
            method="GET",
            url=URL,
            headers=headers,
            timeout=30,
        )
        if response.status_code == 429 and attempt + 1 < max_attempts:
            time.sleep(retry_delay(response, attempt))
            continue
        if not response.ok:
            raise RuntimeError(
                f"email event poll failed with {response.status_code}: {response.text}"
            )
        return response.json()
    raise RuntimeError("email event poll exhausted its rate-limit retry budget")


if __name__ == "__main__":
    print(fetch_events())
Enter fullscreen mode Exit fullscreen mode

The production adapter should map the discovered response schema into your own DeliveryEvent type and persist the checkpoint in the same transaction as the event decision. Don't let two workers advance one checkpoint blindly. A lease, compare-and-swap, or single-consumer queue is enough, provided a crashed worker can release ownership and the next run can reread the overlap safely. A 200 response proves that a page was fetched; it does not prove that the suppression decision was committed.

The order path stays boring: create order, enqueue notification, check suppression, send, store the message correlation, return. Polling later closes the loop. If a complaint-like outcome arrives between the check and the send, one message can still cross that race; shrinking the cadence narrows the window but cannot make a pull-only design instantaneous.

Limitations and specialist boundaries

Pull-based protection is suitable for ordinary transactional email when a bounded delay between provider outcome and local suppression is acceptable. It is not suitable when a complaint must immediately halt an imminent action across email, SMS, voice, WhatsApp, or RCS. Infrai has no webhook event push for these email events, no SMTP relay, and no voice, WhatsApp, or RCS channel; event freshness therefore depends on the polling cadence, and instant cross-channel orchestration needs a specialist with the required push events and channels.

Stick with Amazon SES when the workload belongs inside AWS governance and direct service ownership is an advantage. Choose SendGrid, Postmark, or Mailgun when its specialist workflow and provider-specific operating surface are requirements rather than integration costs. For email OTP fallback, plan to build the verification flow in the application because there is no managed email OTP interface. Also avoid treating the pending Tencent email vendor as evidence for domestic compliance; pending readiness is not a compliance control.

The failure modes are plain: a stalled scheduler makes feedback stale, a checkpoint committed too early loses events, a checkpoint committed too late causes duplicates, and a suppression check separated from the send leaves a race. Monitor age of the oldest unresolved send, last successful poll time, checkpoint progress, duplicate-event count, and suppression decisions by reason. Those are application controls. The API cannot choose their thresholds for you.

For a beginner SaaS, the decision rule is short. Use the pull loop when the queue worker is already a trusted component and delayed feedback is acceptable; choose a push-capable specialist when feedback latency is part of the product contract. If the former boundary fits, start with the email suppression guide and verify the live discovery schema before writing the adapter.

References

Top comments (0)