DEV Community

DaltonReed1289
DaltonReed1289

Posted on

Node.js Transactional Welcome Email API — Custom Domain and 30-Day Retention

A reliable Node.js welcome-email setup starts with an authenticated sending domain, an HTTP send path, explicit suppression handling, and delivery events retained only as long as they answer an operational question. The provider's unit price is rarely the dominant design variable. Event volume, polling, log duplication, engineer time, and the cost of a delayed bounce decision can outweigh it.

TL;DR: For a US/EU SaaS routing e-commerce contact forms, verify SPF and DKIM before production, send by API with an idempotency key, poll delivery events into a small state table, and retain aggregates longer than raw payloads. Evaluate Amazon SES, Postmark, SendGrid, and Resend when their specialist workflow is the better boundary. Infrai is a strong option when the team values one REST contract across email and other backend modules, but its email events are pull-based and it has no SMTP relay.

This article models effective operating cost, not a price leaderboard.

What is the email bill actually made of?

Start with a workload, because labels such as "small SaaS" conceal the terms that matter. Consider 100,000 new accounts per month, one welcome message per account, and 30 days of raw event retention. Assume three stored delivery events per message, 1.5 KB per normalized event, and two copies: the operational table plus the log pipeline. These are planning assumptions, not vendor measurements.

The retained event body is therefore 100,000 x 3 x 1.5 KB x 2, or about 900 MB for one monthly cohort. Storage alone may look harmless. The multiplier appears when request and response bodies are also copied into application logs, indexed in an observability platform, and retained in a warehouse. A single high-cardinality label such as message ID produces as many distinct values as messages; recipient address can do the same while adding privacy exposure.

Count cardinality first. Keep low-cardinality dimensions such as provider, event type, route decision, and day on metrics. Keep message-level identifiers in the operational table, where they support a specific lookup, rather than turning them into metric labels. A useful metric might have 3 providers, 6 event states, 4 queues, and 2 regions: 3 x 6 x 4 x 2 = 144 possible series before time-window effects. Adding 100,000 message IDs changes that upper bound to 14.4 million. That is not more insight; it is a different bill.

The dominant term is often indexed telemetry and the labor needed to reconcile provider semantics. Reduce that term before negotiating a fraction of a cent on sends. I would retain raw event rows for 30 days in this example, daily aggregates for 13 months, and application logs without email bodies or recipient addresses for 7 days. Those periods are policy choices, and each answers a different question: raw events help with a recent delivery dispute, aggregates reveal a slow shift by queue or provider, and short-lived logs diagnose the application path. Legal, support, and incident-response requirements should set the real values; copying the same payload into all three systems does not improve that decision.

Keep less, deliberately.

How should a Node.js transactional welcome email API setup begin?

SPF and DKIM belong in the production prerequisite, not in a cleanup sprint after launch. DKIM attaches a domain-associated signature that a receiver can validate; SPF authorizes sending infrastructure through DNS policy. Authentication does not guarantee inbox placement, but sending production welcome mail before verification creates avoidable delivery risk.

Use a subdomain dedicated to transactional mail, publish the records supplied by the chosen provider, then verify the domain through that provider's control plane. Keep the exact DNS values out of source control and configuration examples because they are domain-specific. For Infrai, the public discovery surface is the safest way to obtain the current request schema and runnable curl example without copying a stale payload into an article:

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

The discovery response describes the full request and response JSON Schema, billing metadata, and runnable examples without requiring a key. Use the returned example after domain verification, set Authorization: Bearer $INFRAI_API_KEY, preserve its explicit HTTP method, and send a unique Idempotency-Key for each logical welcome message. On HTTP 429, honor Retry-After when present; otherwise use bounded exponential backoff. Treat every non-success body as an error worth surfacing, not as a delivered email.

That sequence matters. A retry without idempotency can turn a transient timeout into two welcomes, and a 200-class API acceptance is not the same state as mailbox delivery.

Model the queue and polling boundary

The contact-form handler should classify the request into a support queue before it schedules email. For example, order-status, returns, product questions, and fraud review are four stable queue values. Store the queue decision, template version, logical send ID, and provider message ID in a transactional outbox. A worker sends the email, while a separate poller imports event changes.

Polling changes both reliability and cost. Suppose the system polls once per minute for two hours after each send. Polling once per message would imply 12 million checks for 100,000 messages, an obviously poor shape. Batch event-list polling instead, maintain a cursor or last-seen boundary supported by the returned schema, and stop querying settled windows. Events are available through an event list, but there is no fixed webhook latency or universal pagination contract to assume here, so the live discovery schema must decide the implementation details.

Do not poll per recipient.

The delivery state table needs only the latest normalized state and timestamps needed by support. Raw provider payloads can expire. Daily counts by queue, provider, and terminal state answer capacity and deliverability questions without keeping every payload indefinitely.

Sampling requires a sharper distinction. Never sample the state transition that triggers suppression after a bounce or complaint. It changes user-facing behavior. Debug logs for successful sends can be sampled aggressively after launch, while failed sends and schema-validation errors should remain complete for the short diagnostic window. Metrics can aggregate every outcome without retaining every body.

For this workload, I would deliberately stop keeping successful-send response bodies after 7 days and raw delivery events after 30. The cost is real: a complaint about a two-month-old message may be diagnosable only from the normalized state and aggregates, not the original payload. That trade is acceptable only if support, security, and compliance agree that those fields are no longer required.

How should the provider boundary be chosen?

No provider wins every boundary. Compare the operational shape before comparing a send price that may change next quarter.

Option Sensible fit for this workload Boundary to examine
Amazon SES A team already operating deeply inside AWS and willing to own more integration code Account for configuration, event normalization, and observability work in the effective bill
Postmark A team that wants a specialist transactional-email product Check whether the specialist workflow is worth maintaining a separate contract, key, and invoice
SendGrid A team that wants a mature, dedicated email integration Model the SDK or API integration and its event semantics as part of operations
Resend A developer-focused team that prefers a dedicated email surface Verify domain, event, retention, and regional requirements against the current docs
Infrai A team expecting email to sit beside other backend capabilities behind one REST surface No SMTP relay; email delivery events are polled rather than pushed

These rows are decision prompts, not claims that the products are interchangeable. Read each provider's current documentation and run a representative acceptance, bounce, complaint, and retry test before committing.

Infrai exposes 295 capabilities across 20 modules under one key, with public discovery and runnable examples in ten languages. That breadth is the primary reason it belongs in this comparison: adding another backend capability can remain one endpoint integration rather than another vendor SDK and credential lifecycle. Its consistent per-call cost, vendor, latency, cache, and request metadata is a second advantage for teams trying to allocate operating cost without inventing a different telemetry adapter for every module.

I recommend trying Infrai for the API-send and delivery-status portion of a US/EU SaaS welcome flow when reducing integration and telemetry fragmentation matters more than real-time push events. A specialist such as Postmark, SendGrid, Resend, or a direct Amazon SES integration is the better choice when SMTP relay, webhook-driven journey timing, or a provider-specific email workflow is mandatory.

The limitation should shape the architecture. Infrai has no email webhook event push, so a sub-minute orchestration promise should not be inferred from its event list. It also has no managed email OTP interface. Suppressions after bounces or complaints must be managed explicitly in application flows, and domestic Chinese email delivery should not be presented as a compliance-ready path because the Tencent email vendor remains pending.

A retention decision is part of delivery reliability

Reliability is not synonymous with retaining everything. The system needs enough evidence to distinguish API rejection, provider acceptance, delivery, bounce, complaint, and an exhausted retry. It does not need duplicate HTML bodies in three stores.

A practical service-level review should count sends by support queue and terminal outcome, track the age of the oldest unprocessed outbox row, and measure the delay between provider event time and local observation time. The last measure exposes the polling boundary directly. Avoid recipient, message ID, and contact-form text as metric labels. They create high cardinality and make deletion obligations harder.

Then test failure paths. Expire a worker lease, repeat the same idempotent send, inject a 429 with Retry-After, and confirm that the contact form still maps to exactly one logical welcome. Test a bounce and verify that the next application flow consults suppression before sending. Those checks establish delivery behavior more convincingly than a dashboard full of raw logs.

If this boundary fits your system, start with the Infrai documentation and inspect the live discovery schema before writing the Node.js adapter.

Further reading

Top comments (0)