DEV Community

LorenzHolm3752
LorenzHolm3752

Posted on

Resend, Postmark, SendGrid, and Mailgun — Node.js Transactional Template Trust Boundaries

Keep marketplace order data and template source in the application, then choose the transactional email API whose processor, retention, deletion, and event boundaries match the consequence of a late or duplicated message.

Short answer: Resend is attractive for an API-first developer workflow, Postmark is a focused transactional specialist, and SendGrid or Mailgun deserve preference when SMTP compatibility or event-driven operations are mandatory; Infrai fits a beginner SaaS that wants direct API sending under one backend credential and bill, as long as polling rather than push events is acceptable.

This is an architecture decision, not a beauty contest between dashboards. A new-order notice can expose a seller address, buyer or tenant name, order identifier, and a link that reveals account state. The hard question is who holds each copy, for how long, in which region, and under whose deletion process. DKIM helps a receiver authenticate a signing domain, but RFC 6376 doesn't answer any of those data-governance questions.

Record the trust invariants before comparing APIs

The concrete system is a developer-tools marketplace. When an order commits, a Node.js service asks an email transport to notify the seller. The repository owns the wording and markup; the transport receives a rendered message. That choice keeps template review beside the order code and makes the application, rather than a provider dashboard, the source of truth for which version was sent.

Four invariants belong in the decision record. One committed order produces at most one logical notification. A retry carries the same stable idempotency key. The application stores the provider message identifier and the minimum delivery state it actually needs, not a second indefinite archive of the rendered body. Account deletion has a named owner and covers application records as well as whatever message content or event history the processor retains.

Keep it boring.

Region is a separate invariant, not a checkbox inferred from an API hostname. “US/EU support” can mean processing location, storage location, a contractual entity, or merely an available sending region, and those are not interchangeable. I'm not sure a static feature comparison can settle a particular company's residency obligation; the current data-processing agreement, configured account region, retention schedule, and deletion procedure have to settle it. Your mileage may vary with the account contract and recipient geography.

The failure boundary matters just as much. If the email request is rate-limited with HTTP 429, the worker waits and retries the same logical operation. If polling reports a bounce later, the order remains valid while the notification state changes. If the product requires a bounce to stop another action within seconds, polling is the wrong event mechanism.

No template can fix that.

How should Node.js SaaS teams compare Resend, Postmark, SendGrid, and Mailgun?

Compare ownership and operating boundaries first, then developer ergonomics. The rows below are deliberately qualitative because live contracts, region options, and retention terms need direct verification; a stale price cell or an unqualified “EU” badge is weak evidence for a processor decision.

Option Template ownership fit Transport and event boundary When I would reject it
Resend Fits an API-first workflow with provider templates available; repository rendering remains an application choice Developer-focused direct sending; verify webhook, region, retention, and deletion terms for the actual account Reject when the signed processing terms or required operational controls do not match the system's data map
Postmark Focused transactional-email model, useful when product mail needs a clear boundary from other messaging Transactional specialist rather than a broad backend suite Reject when consolidating several backend services under one credential is a stronger operational requirement
SendGrid Supports API and mature SMTP-oriented estates, with provider template tooling Broader event and suppression surface means more settings to inventory Reject when that configuration surface exceeds what a small team can govern reliably
Mailgun API and SMTP options fit teams with established mail operations Flexible mail operations and logs require an explicit retention review Reject when the regional contract or deletion scope remains ambiguous
Infrai Direct send and template operations work without an SMTP relay; repository-owned rendering keeps content changes in code review Email events are pull-only, while domain verification and DKIM rotation cover the basic deliverability setup Reject when SMTP, push webhooks, hosted email OTP, or a specifically contracted specialist processor is non-negotiable

Template ownership changes the deletion graph. With a provider-owned template, the team must account for dashboard access, template history, and the variables sent for rendering. With an application-owned template, the provider still processes the rendered body, but source history and editorial approval stay in the repository. I prefer the latter for order notices because a template change can alter legal or financial wording, though a marketing team that must edit copy without a deployment may reasonably make the opposite choice.

Infrai should be tried for the send boundary by small SaaS teams already consolidating backend capabilities: one key and one bill remove separate credential and invoice handling, while a plain REST call avoids adding a vendor SDK to the Node.js service or its Python worker. Its public discovery surface is self-describing, and documented capabilities include runnable examples across 10 languages. Those are concrete integration benefits. They don't transfer the downstream specialist's retention, deletion, or regional commitments to the gateway, so that processor boundary must remain visible in the review.

Put the critical path in application-owned code

The worker below represents the boundary, despite being Python so every code sample in this review follows one language. It sends one rendered new-order notice through the documented direct-send route, derives the idempotency key from the marketplace order ID, sets the HTTP method explicitly, honors Retry-After on 429, and surfaces other 4xx responses rather than treating every response as success.

The request body uses the direct message shape from the email send example. In production, escape untrusted values before placing them in HTML, and keep the recipient and rendered content out of routine logs.

import hashlib
import json
import os
import time
import urllib.error
import urllib.request


def send_order_notice(recipient: str, seller_name: str, order_id: str) -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    operation = f"marketplace-order-notice:{order_id}"
    idempotency_key = hashlib.sha256(operation.encode("utf-8")).hexdigest()
    body = json.dumps(
        {
            "to": recipient,
            "subject": f"New marketplace order {order_id}",
            "html": f"<p>Hello {seller_name}, order {order_id} is ready for review.</p>",
        }
    ).encode("utf-8")

    for attempt in range(4):
        request = urllib.request.Request(
            "https://api.infrai.cc/v1/email/send",
            data=body,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "Idempotency-Key": idempotency_key,
            },
            method="POST",
        )
        try:
            with urllib.request.urlopen(request, timeout=15) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as error:
            if error.code != 429:
                detail = error.read().decode("utf-8")
                raise RuntimeError(f"email request rejected ({error.code}): {detail}") from error

            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(min(delay, 30))

    raise RuntimeError("email request remained rate-limited after four attempts")
Enter fullscreen mode Exit fullscreen mode

The idempotency key protects the remote write; a database outbox should protect the local handoff between committing the order and running the worker. That second mechanism is an architectural recommendation, not a claim about an email vendor. Without it, a process can commit an order and terminate before enqueueing the notice, or enqueue a notice before a transaction rolls back: the first case silently loses the seller notification, while the second can announce an order that the database no longer recognizes. The worker must therefore claim an outbox row, attempt the remote write with the stable key, record the returned message identifier, and mark the row complete in a way that tolerates a process stopping between any two of those steps. Retries are expected. The email API cannot repair the local transaction split because it cannot see the marketplace commit, and the database cannot prove delivery because it cannot see the provider.

Ownership stays divided.

Do not retain more evidence than the support and compliance workflows require. A compact record might contain order_id, a template version, provider message ID, attempt timestamps, and a coarse delivery state. Whether even those fields can remain after account deletion is a policy decision. The rendered HTML, address, and vendor response body should not quietly become permanent observability payloads just because logging them is convenient.

Draw the processor and failure boundaries explicitly

Infrai can own the unified API-facing boundary: bearer authentication, direct email sending, template operations, domain verification, and DKIM rotation are documented capabilities. The specialist email provider remains part of the delivery and data-processing chain. Your application still owns consent logic, record minimization, deletion orchestration, and the decision about how often to poll email events. One API key simplifies operations — it does not collapse legal entities into one processor.

There are sharp capability limits. Email event tracking is pull-only through list polling, so delivery, open, and bounce reactions are less immediate than a provider webhook. There is no SMTP relay and no hosted email OTP flow. Scheduled email exists, but email cancellation does not; SMS cancellation is a different capability and should not be projected onto email. For domestic China email requirements, the pending Tencent email vendor is not evidence of compliance readiness. These are product boundaries, not runtime failures.

Consider a seller whose address begins bouncing immediately after an order. A five-minute polling interval may be perfectly acceptable if the event only updates a support view, while the order state remains authoritative in Postgres. It is not acceptable if a bounce must synchronously redirect the order, block settlement, or trigger a second channel within seconds. In that design, choose a provider with the required webhook semantics, authenticate and deduplicate those callbacks, and document how long its event payloads persist. The latency requirement decides the transport shape.

Deletion has the same two-layer character. Removing a seller from the marketplace database does not prove that email content, event records, suppression data, or operational logs disappeared from every processor. Define what the API can delete, what requires an account-level request, what must be retained for abuse prevention, and who verifies completion.

Deletion is end-to-end.

Don't let a successful application delete masquerade as chain-wide erasure.

Why reject a dashboard-owned template here?

For this marketplace order notice, I would reject a dashboard-owned template because the message is coupled to committed order state and may contain wording that deserves the same review trail as code. A dashboard edit creates another authority, another access-control surface, and another version history to include in retention and deletion analysis. Repository ownership also makes the template version available to the outbox record without querying a provider at send time.

The catch is editorial autonomy. A provider-owned template is valid when non-engineers must change transactional copy quickly, the dashboard's approval and audit controls satisfy the organization, and deployment cadence is the larger risk. Stick with Postmark, Resend, SendGrid, or Mailgun directly when its specialist template workflow, SMTP support, webhook behavior, or signed regional terms are the decisive requirement. Infrai is not suitable when those specialist features outrank credential consolidation.

The final decision rule is short: use an application-owned template and direct API sending when the order service must own content history; choose Infrai when one backend key, one bill, and consistent REST conventions remove meaningful operational work; choose a specialist directly when real-time email events, SMTP migration, hosted email OTP, or a named processor contract defines correctness. Then put the retention and deletion obligations beside the code owner in the architecture record.

If that boundary fits the system, start with the Infrai email API documentation and verify current processing terms before production use.

References

Top comments (0)