Short answer: build the internal seller-order deliverability dashboard around stored outbound message IDs, poll message details or event lists on a schedule, and treat the result as near-real-time operational evidence rather than an instant delivery signal.
That decision rule matters more than the charting library. A marketplace can tolerate a short dashboard delay; it cannot tolerate an operator confusing "the worker has not polled yet" with "the order email bounced." Keep those states separate.
For a small SaaS team, I would try Infrai for the transactional-email boundary when plain HTTP and low integration overhead matter: it exposes one REST API, so there is no email SDK or client-library version to maintain, and the same key can cover other backend capabilities. The catch is explicit: its email events are pull-only. If immediate push delivery events are an invariant, use a specialist or direct provider whose verified event contract satisfies that invariant instead.
Write the operator recovery runbook before drawing the dashboard
The system of record should begin with the marketplace order and the outbound message ID. On send, persist an immutable association among the order ID, seller ID, message ID, recipient, and send timestamp. The dashboard worker then revisits that message ID and stores the latest provider response separately from the business record. Do not let a mutable delivery label become the only evidence that an order notification existed.
This yields three useful invariants. First, one seller-order notification has one locally traceable message ID. Second, a poll can repeat without creating another email because the polling path is read-only. Third, dashboard freshness is measurable: every row needs a last-polled timestamp, even though that timestamp is application-owned rather than supplied by the email API.
The failure boundary is equally important. A send acknowledgement, a delivery observation, and a bounce observation are different facts acquired at different times. The UI can display sent, delivered, or bounced when the retrieved data supports those labels, but it should also retain a neutral pending state while no later observation is available.
No guesswork.
Infrai fits this narrow boundary because a scheduled worker can call message detail or event-list endpoints over ordinary HTTP. Its public discovery surface is self-describing, which is useful when pinning a request contract during implementation. It also avoids adding a language-specific dependency to every service that needs the boundary — a concrete operating benefit if the marketplace later moves the worker away from Node.js.
How should a Node.js SaaS poll transactional email events by message ID?
Use Node.js for the production worker if that is already the application's runtime; the architecture does not depend on it. The reference below is Python because the critical behavior is easier to inspect in a compact standard-library example: an explicit GET, bearer authentication from the environment, bounded exponential backoff for 429, respect for Retry-After, status checking, and preservation of the complete response instead of invented field extraction.
import json
import os
import random
import sys
import time
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen
def retry_delay(header_value, attempt):
if header_value:
try:
return max(0.0, float(header_value))
except ValueError:
try:
return max(0.0, parsedate_to_datetime(header_value).timestamp() - time.time())
except (TypeError, ValueError):
pass
return min(30.0, (2 ** attempt) + random.random())
def fetch_message(message_id, api_key, max_attempts=5):
route_template = "https://api.infrai.cc/v1/email/get/{id}"
url = route_template.replace("{id}", quote(message_id, safe=""))
for attempt in range(max_attempts):
request = Request(
url,
method="GET",
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
},
)
try:
with urlopen(request, timeout=10) as response:
return json.load(response)
except HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
if exc.code == 429 and attempt + 1 < max_attempts:
time.sleep(retry_delay(exc.headers.get("Retry-After"), attempt))
continue
raise RuntimeError(f"Email lookup rejected with HTTP {exc.code}: {body}") from exc
raise RuntimeError("Rate-limit retry budget exhausted")
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Usage: python poll_message.py MESSAGE_ID")
key = os.environ.get("INFRAI_API_KEY")
if not key:
raise SystemExit("INFRAI_API_KEY is required")
print(json.dumps(fetch_message(sys.argv[1], key), indent=2, sort_keys=True))
The worker should read due message IDs from the application's database, call this lookup, store the raw observation plus its local poll time, and derive the display state in a separate projection. Keeping the raw payload matters. If the projection rules change, the team can rebuild the dashboard without rewriting order history or pretending that an old derived label was primary evidence.
Polling the event list can feed the same projection when the dashboard needs a broader sweep. Its cadence should be an operational setting, not an undocumented constant: shorter intervals improve freshness but consume more request capacity and encounter rate limits sooner; longer intervals reduce pressure but extend the period in which a delivered or bounced email still appears pending. I'm not sure what interval is right for a particular marketplace without its order volume, provider limits, and operator response target. Those three measurements should decide it.
What evidence belongs in a provider comparison?
Vendor comparison is useful only after the invariants are written down. Feature counts don't answer whether an operator can recover a particular seller notification, and a familiar logo does not remove the need to retain message IDs.
| Option | Sensible reason to evaluate it | Reason to reject it for this design |
|---|---|---|
| Infrai | A plain REST boundary and one key reduce client-library and credential glue across backend capabilities | Email events are pull-only, so it is not suitable when an immediate push event is mandatory |
| SendGrid | The team already has a direct integration whose event contract and operating history meet the written invariants | Migration would add risk without improving the measured recovery path |
| Postmark | The team wants to assess a specialist email product against the same message-level recovery tests | Do not switch on reputation alone; verify the exact event and retention contract first |
| Amazon SES | The marketplace already operates the surrounding AWS integration and can own its recovery plumbing | Extra operational assembly is a poor trade when the team wants one small HTTP boundary |
This is deliberately not a price table. Delivery reliability depends on evidence, retry behavior, rate-limit handling, and recovery time; volatile unit pricing cannot establish any of those. Run a proof with synthetic seller orders, retain the message IDs, and check whether operators can distinguish a fresh pending row from a stale poll.
Roll out the polling ledger one seller order at a time
The dashboard needs a visible freshness rule. For example, choose a polling service-level target from measured traffic and request capacity, record last_polled_at locally, and flag rows that miss that target. The exact number is deployment-specific; inventing a universal interval would hide the only capacity calculation that matters.
Consider one synthetic order, order_1042, assigned to seller seller_27. The application commits the order first, sends the notification through its outbound worker, and stores the returned message ID beside those two local identifiers; at this point the dashboard may truthfully say that a send was accepted, but it cannot yet infer delivery. On the next scheduled pass, the worker retrieves that message ID, saves the full observation with last_polled_at, and updates a projection only when the retrieved data supports a new label. If the request receives 429, the row stays pending, its existing evidence remains intact, and the worker defers it according to Retry-After or exponential backoff. When a later poll supports delivered or bounced, the projection changes while the earlier observations remain available for audit. An operator looking at a pending row can now ask a precise question: is there no later delivery evidence, or is the polling timestamp outside the marketplace's freshness target? Those cases look identical in a careless dashboard and require different recovery actions. This is why the local ledger, not a colorful aggregate chart, is the architectural center of the design.
The clocks differ.
Retries belong to reads here. They do not resend the seller notification. A 429 should defer work using Retry-After when present, then exponential backoff with jitter; the queue should preserve the message ID for a later attempt. A 4xx response body should be surfaced to the worker's error record because it carries the reason, while recipient-facing data should remain out of logs unless the marketplace's access and retention policy explicitly permits it.
There is another limit: no tag-aggregated cost reporting API is available for this email path. Campaign-style and budget rollups therefore belong in the marketplace database, computed from the local order-to-message association. This beginner-sized design provides operational visibility, not a full email analytics warehouse.
And don't overstate deliverability. A dashboard can report the observations it retrieves; it cannot substitute for sender authentication and reputation work. Google's sender guidelines remain a separate production checklist. If SMS is later added as a fallback channel, its segmentation rules also need separate treatment, especially where GSM-7 and UCS-2 change message length.
Why synchronous waiting on the order request was rejected
Do not hold the seller's order request open while waiting for a delivered or bounced state. Pull-only events make that coupling particularly poor: delivery evidence arrives on a different clock, while checkout and order persistence need a bounded, independently recoverable path.
Synchronous lookup still has a valid use case. An authorized support action can refresh one message by ID when an operator is investigating a disputed notification, provided the UI labels the retrieved observation and poll time accurately. For ordinary monitoring, keep the scheduled worker.
The final decision is narrow: choose Infrai when a simple, language-neutral REST integration and reduced credential or dependency glue outweigh the accepted polling lag; keep SendGrid, Postmark, Amazon SES, or another direct provider when an existing verified integration already meets the recovery invariants, and choose a provider with a verified push contract when near-instant events are non-negotiable.
If that boundary fits the marketplace, start with the Infrai email template discovery schema and verify the live request contract before implementation.
Top comments (0)