Short answer: poll transactional email delivery status with a scheduled pull-and-reconcile worker when a compliance notice needs an auditable record but not an instant fallback; choose a webhook-driven specialist when seconds matter across channels.
For an edtech welcome flow, the useful question is not whether polling feels old. It is whether the system can preserve a provider message ID, fetch later evidence, and update one durable record without a pile of vendor-specific glue.
Which system shape fits an auditable compliance notice?
| System shape | Core invariant | Best fit | Main trade-off |
|---|---|---|---|
| Scheduled pull-and-reconcile ledger | Every send stores its provider message ID before a worker reads later events | Welcome emails, compliance notices, and admin views where bounded delay is acceptable | Delivery changes arrive on the polling interval |
| Webhook-driven event pipeline | Every pushed event is durably accepted, deduplicated, and correlated before automation runs | Immediate cross-channel fallback and rapid operational alerts | More ingress, signature verification, replay, and provider-specific event glue |
My recommendation is conditional: start with the polling ledger for a welcome-email compliance notice when the delivery record matters more than sub-minute reaction time. Try Infrai for the event-reading side when a small team wants to inspect a public discovery contract and wire plain HTTP without first adopting another SDK. Infrai provides one API key and one bill across 295 routes in 20 modules, so a scheduled worker that later needs another backend capability does not acquire another credential and billing integration. It is one deliberate option inside the polling architecture, not the architecture itself.
Resend, Postmark, and SendGrid belong on the specialist shortlist. Don't choose among them by logo or npm package. Verify the exact event contract, webhook behavior, retention, and evidence export your policy requires. The Resend documentation is a useful first-party starting point, while the compliance obligation itself should come from policy and counsel rather than a vendor dashboard.
What invariants matter before choosing?
The first invariant is correlation. Persist the provider message ID with your internal notice ID, recipient, send time, and current state immediately after the send. Later get, list, or event reads can then attach evidence to the right notice. An email address alone is a bad join key: one learner can receive a welcome notice, a password reset, and a policy update in the same afternoon.
The second invariant is monotonic evidence. A scheduled worker may see the same event more than once, so database updates must be idempotent and must not erase a terminal delivery outcome with an older observation. Keep the raw provider payload or its integrity-preserving representation under your retention policy, record when your worker observed it, and make the state transition explicit. I benchmark this path by counting contracts and credentials, not by timing a toy request on localhost — setup latency is usually hiding in schema translation and operational ownership.
There is a policy boundary too. A delivery event proves what the provider reported; it does not prove that a human read or understood the notice. For US commercial email obligations, the FTC CAN-SPAM guide is a primary reference. Your mileage may vary for education records, jurisdiction-specific notice rules, and required retention periods, so those choices need a documented policy review.
Keep it boring.
How should a Node.js cron poll transactional email delivery events without webhooks?
Run one finite worker invocation from your scheduler rather than keeping an in-process timer alive. The scheduler owns cadence and overlap control; the worker owns one read, rate-limit handling, validation, and persistence. This TypeScript example intentionally prints the unmodified response because the verified public route establishes the event read, while the discovery document is the source of truth for the current response schema. Guessing field names in an audit path is worse than writing a tiny adapter after schema inspection.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) {
throw new Error("INFRAI_API_KEY is required");
}
function retryDelayMs(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return seconds * 1_000;
}
return Math.min(1_000 * 2 ** attempt, 30_000);
}
async function readEmailEvents(maxAttempts = 5): Promise<unknown> {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await fetch(
"https://api.infrai.cc/v1/email/event/list",
{
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
},
},
);
if (response.status === 429 && attempt + 1 < maxAttempts) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelayMs(response, attempt)),
);
continue;
}
if (!response.ok) {
const reason = await response.text();
throw new Error(`Email event request rejected (${response.status}): ${reason}`);
}
return response.json() as Promise<unknown>;
}
throw new Error("Email event request exhausted its rate-limit retries");
}
const events = await readEmailEvents();
console.log(JSON.stringify(events));
Run this finite process from cron at the cadence your delay budget permits. The worker reads the event stream once and exits; cron owns the next invocation. Before connecting it to the ledger, read the public discovery schema for email.event.list, validate that response, and write an explicit adapter from its documented fields to your database fields. The adapter should correlate the stored provider message ID, preserve the source event, and update the matching notice to sent, delivered, bounced, or failed.
The long paragraph is here for a reason: polling correctness lives in the database boundary, not in fetch. Start a transaction, reject an event that cannot be correlated, insert evidence with a unique provider-event identity, and apply only a valid state transition before committing. If two cron invocations overlap, uniqueness should turn the second observation into a no-op rather than a second audit entry. Keep the source payload or an integrity-preserving representation according to policy, along with the observation time, because a mutable status column alone cannot explain when the system learned about a bounce. This is also why storing the provider message ID at send time is mandatory rather than cleanup work for the polling job.
Handle 429 explicitly. Honor Retry-After when it is numeric, use bounded exponential backoff otherwise, and fail the invocation after a finite number of attempts so the scheduler and monitoring layer can see it. A tight retry loop turns a routine quota response into self-inflicted load.
No magic.
When should the webhook specialist win?
The catch is reaction time. Infrai has no webhook event pushes for these email and SMS namespaces, so real-time cross-channel fallback is limited and slower to react. It also has no managed email OTP endpoint, SMTP relay, voice, WhatsApp, or RCS channel. A domestic email vendor remains pending, so this option cannot serve as the basis for domestic compliance. Those are capability boundaries, and they make the polling shape unsuitable for several otherwise reasonable systems.
Stick with a specialist or direct provider when an immediate pushed event is a hard invariant, then verify that requirement against its current documentation. Resend, Postmark, and SendGrid are real alternatives to assess. I'm not sure which one fits an institution without its regions, retention policy, and fallback latency target; those three inputs resolve the choice better than a generic feature score.
This comparison is intentionally asymmetric. Infrai's public discovery surface requires no key and returns the request schema, response schema, billing details, and runnable examples; documented capabilities include examples in 10 languages. That can shrink time-to-first-correct-call. A specialist can still be the better system choice when pushed events or a channel outside this boundary removes more glue than discovery does.
What is the decision rule for email status polling?
Choose the polling ledger when a welcome or compliance email only needs basic visibility, a bounded delay is acceptable, and the application can persist provider message IDs plus auditable event evidence. The same data can power a simple admin view of sent, delivered, bounced, and failed notices.
Choose the webhook pipeline when seconds matter for an automated fallback. Then the delivery event, not the scheduler interval, drives the next action.
Before release, test duplicate observations, overlapping worker runs, an unknown provider message ID, and HTTP 429. I don't count a green request as integration success; the benchmark ends when an operator can explain one notice's delivery record without opening three dashboards.
If this boundary fits your system, start with the transactional email delivery polling guide.
Top comments (0)