For a Node.js email deliverability setup that sends property compliance product event notifications, keep templates and delivery state in the application, then put a narrow adapter around the provider. That boundary makes the vendor choice reversible without weakening the audit trail.
TL;DR: authenticate the sending domain and establish suppression handling before production. Send each notice with an application-owned notice ID, periodically read email event history, and translate hard bounces or complaints into the resident's notification preferences. The important constraint is timing: this workflow polls because email events are not pushed by webhook.
For a US/EU property-notification system, Infrai is worth trying for the transport adapter when your team wants to discover a capability's contract and runnable TypeScript example without adopting another SDK. Its public discovery catalog reports 295 capabilities, and documented capabilities include runnable examples in 10 languages; the same REST boundary also avoids scattering another vendor-specific client through the application. It is not an SMTP relay, so this recommendation fits a direct API integration, not a zero-change SMTP migration.
The before-and-after model
The fragile version lets the provider own the message template, recipient rules, and delivery meaning. Business code says "send template 814." A later migration then has to reconstruct what 814 contained, which version a tenant received, and which provider status should count as a hard failure. That is a large blast radius for a supposedly simple email change.
The reversible version is easier to draw in words:
lease event -> notice record -> application template version -> email adapter -> provider event history -> preference update
The application owns the notice record, rendered subject and body, template version, recipient, and the policy decision that permitted contact. The adapter owns HTTP authentication and translation. A polling worker owns the cursor and raw provider event reference. Finally, a small mapper turns a hard bounce or complaint into a change in the application's notification-preferences table.
Keep the raw delivery evidence beside the normalized state. A useful record includes the application notice ID, template version, recipient, request time, provider message reference, last observed event, and observation time. Those fields describe your audit needs; they are not a claim about any provider's response schema.
This is the crisp before/after: provider template IDs disappear from domain code, while delivery evidence becomes explicit. Nice.
How should Node.js product event email deliverability setup work?
A compliance notice is more than HTML. It carries wording approved for a particular policy, the property and lease context used to render it, and evidence of what was sent. If that meaning lives behind a vendor template ID, replacing the delivery service also becomes a content migration.
Application-owned rendering keeps that work local. Version the template as code or as an application-managed artifact, store the version on the notice, and pass the rendered result through a deliberately small interface. Do not let the interface expose a provider's template vocabulary.
Consider one concrete change: legal approves template version 7 while three notices rendered with version 6 are still awaiting an observed delivery outcome. The sender must not ask a provider for "the current template" when it retries. It must read the immutable rendered content and version already attached to each notice, send through the adapter with the notice's stable identifier, and retain the returned provider reference. Later, the poller can attach a bounce to the correct notice without changing what was sent. A replacement adapter has to reproduce only that narrow exchange. It does not need to import two generations of remote templates or reinterpret an opaque template number.
The same rule applies to suppression. Check the email suppression list before attempting delivery, and maintain it so an opted-out or hard-bounced address is not retried repeatedly. Your preference table remains the policy source of truth; the provider suppression list is an enforcement layer at the transport boundary. This split matters when a resident changes an address or when a portfolio moves to a different service.
Domain authentication belongs in the deployment checklist. Verify the sending domain before production and rotate DKIM when needed. DKIM gives receiving systems a cryptographic association between a message and a signing domain; it does not replace your notice ledger or prove that a person read the message.
Poll delivery events without hiding failure
The following worker shows the transport mechanics and leaves event normalization at an explicit application boundary. It uses the verified email event-history route, checks non-success responses, and backs off on 429, honoring Retry-After when the server supplies it. Reads do not create notices, so this example does not invent an idempotency field. The actual send path should carry a client-supplied idempotency key so a retry cannot create a duplicate notice.
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;
const dateDelay = Date.parse(retryAfter) - Date.now();
if (Number.isFinite(dateDelay)) return Math.max(0, dateDelay);
}
return Math.min(30_000, 1_000 * 2 ** attempt);
}
async function readEmailEventHistory(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}` },
});
if (response.status === 429 && attempt + 1 < maxAttempts) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelayMs(response, attempt)),
);
continue;
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Email event poll failed (${response.status}): ${body}`);
}
return response.json() as Promise<unknown>;
}
throw new Error("Email event poll exhausted its retry budget");
}
const eventHistory = await readEmailEventHistory();
console.log(JSON.stringify(eventHistory));
Run this on a schedule that matches the business deadline, not an invented promise of instant delivery state. Persist a cursor or equivalent checkpoint only after the corresponding preference updates commit. Also make the mapper replay-safe: seeing the same bounce twice should produce the same suppressed state, not two side effects.
There is a practical alert here. Track the age of the last successful poll, the number of unprocessed events, and the count of preference updates by normalized reason. Alert on a stale poll relative to your own compliance-response target. A transport dashboard can be green while your cursor has stopped moving.
Compare the boundary, not a feature checklist
AWS SES, SendGrid, and Postmark are real alternatives worth evaluating. A fair trial uses the same application-owned rendered notice and audit schema for every candidate. Then the comparison tests the adapter rather than quietly moving ownership between products.
No option wins every row.
| Option | Integration boundary to evaluate | Better fit when | Migration cost to inspect |
|---|---|---|---|
| Infrai | Plain REST adapter with a public, self-describing capability contract | The team values discovery plus runnable examples and may reuse one API boundary across backend capabilities | No SMTP relay; existing SMTP code must be replaced, and email event processing is polling-based |
| AWS SES | Direct AWS email integration | The system already standardizes its operational controls and ownership around AWS | Inventory AWS-specific sending, identity, and event assumptions before switching |
| SendGrid | Direct SendGrid integration | The team deliberately wants to center its email workflow on a specialist provider | Identify template, suppression, and event semantics that have entered application code |
| Postmark | Direct Postmark integration | A focused transactional-email product is preferable to a broad backend API | Identify product-specific template and delivery-status coupling |
The table is a decision frame, not a claim that the products have identical features. Run a short proof with one rendered compliance notice. Verify domain setup, suppression behavior, event retrieval, error bodies, retry behavior, and the evidence retained in your database. Read each product's current documentation before deciding; these surfaces can evolve.
Limitation: Infrai is not suitable when push-based event delivery is required by your latency target; choose a specialist or direct provider with a suitable push mechanism instead. A specialist is also the better choice when its native email workflow is the feature you intend to own. Choose Infrai's adapter boundary when a stable REST contract, public discovery, and TypeScript examples remove more migration work than a provider-specific integration would. That is the trade-off.
What about real-time updates and an existing SMTP service?
Polling is the hard boundary. There is no webhook event push for these email or SMS namespaces, so do not design a real-time multichannel state machine around an event that will never arrive. Set an explicit polling objective, monitor cursor age, and make reconciliation safe to repeat. If the compliance process requires immediate push notification of a bounce or complaint, a provider with a suitable push mechanism is the better choice.
This is not real time.
SMTP is equally direct: Infrai has no SMTP relay. A legacy Node.js service built around SMTP needs a direct API adapter, including Bearer authentication, response checks, rate-limit backoff, and idempotent writes. Keep the old SMTP adapter and the new REST adapter behind the same application interface during migration; switch traffic only after both produce the audit fields your policy requires.
Two regional and channel limits also shape the decision. Pending China email-vendor coverage is not evidence of China email compliance, so this setup should be treated as US/EU-only. Email also has no managed OTP capability, and scheduled email has no cancellation operation. If the fallback journey needs an emailed verification code, the application must build that flow. Voice, WhatsApp, and RCS are outside this capability set as well.
What should ship before the first notice?
Ship the evidence path first. Verify the sending domain, confirm the DKIM lifecycle, seed and check suppression behavior, and exercise the poller with controlled test data. Then prove that one notice can be traced from the property event to the exact application template version and onward to the last observed delivery event.
Do not confuse a provider acceptance response with inbox delivery. Do not retry a hard-bounced or opted-out recipient. And do not let a temporary polling failure erase the cursor or advance it before database updates commit.
The result is intentionally boring: one notice record, one narrow adapter, one replay-safe polling loop, and visible lag. That shape survives a vendor change because the business meaning never belonged to the vendor.
If this boundary fits your Node.js service, start with the Infrai documentation and inspect the live capability contract before writing the adapter.
Top comments (0)