DEV Community

ColeMitchell4991
ColeMitchell4991

Posted on

Choosing Email APIs for Evidence-Ready Password Resets in B2B SaaS

Short answer: choose the email API that leaves a clear trail for every password-reset decision, not the one with the longest template feature list. For a B2B SaaS product, I would separate template rendering, recipient safety, attachment handling, and delivery evidence. Infrai is a reasonable fit when one REST API, one key, and one bill across backend services reduce operational sprawl, but Amazon SES, SendGrid, and Postmark remain credible choices when their surrounding tooling matches your compliance process.

The useful boundary is simple: create one reviewed reset template, render it with a short-lived token, send it once, and retain the request ID plus the template version in your application evidence store. If the same workflow also emails a generated account report as an attachment, treat that attachment as a separate artifact with its own checksum. That prevents a password-reset event from becoming an accidental data-export event.

What evidence does a reset email need?

A reset email is a security action disguised as a notification. The record should answer five questions: which account requested it, which template revision rendered, which recipient policy allowed delivery, which artifact was attached, and which provider response identified the attempt. A provider event stream can help, but it cannot replace your application record. In this scenario, both relevant namespaces are pull-based rather than webhook-driven, so a compliance worker must poll for events and associate them with its own request ID.

I keep the application-side record deliberately boring. It contains no reset token and no full report body. It stores a salted account reference, a template revision, a report checksum when applicable, the provider request ID, and timestamps. SPF is part of the sender-domain story, not proof that a particular reset was authorized; RFC 7208 describes the mechanism for publishing and evaluating SPF policy, while the authorization decision belongs in the application.

A small evidence object is enough to make that boundary executable:

import json
import os
import time
import uuid
from urllib.error import HTTPError
from urllib.request import Request, urlopen


BASE_URL = os.environ["INFRAI_BASE_URL"].rstrip("/")
API_KEY = os.environ["INFRAI_API_KEY"]


def post_json(path: str, payload: dict, idempotency_key: str) -> dict:
    body = json.dumps(payload).encode("utf-8")
    for attempt in range(5):
        request = Request(
            f"{BASE_URL}{path}",
            data=body,
            method="POST",
            headers={
                "Authorization": f"Bearer {API_KEY}",
                "Content-Type": "application/json",
                "Idempotency-Key": idempotency_key,
            },
        )
        try:
            with urlopen(request, timeout=20) as response:
                if not 200 <= response.status < 300:
                    raise RuntimeError(f"email API returned HTTP {response.status}")
                return json.load(response)
        except HTTPError as error:
            if error.code != 429 or attempt == 4:
                detail = error.read().decode("utf-8", errors="replace")
                raise RuntimeError(f"email API returned HTTP {error.code}: {detail}") from error
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay)

    raise RuntimeError("email API retry budget exhausted")


template_payload = json.loads(os.environ["EMAIL_TEMPLATE_CREATE_JSON"])
template_result = post_json(
    "/v1/email/template/create", template_payload, f"template-{uuid.uuid4()}"
)
send_payload = json.loads(os.environ["EMAIL_SEND_JSON"])
send_result = post_json(
    "/v1/email/send", send_payload, f"send-{uuid.uuid4()}"
)
print({"template": template_result, "send": send_result})
Enter fullscreen mode Exit fullscreen mode

The request bodies come from environment variables because the exact provider schema should come from the selected API's documentation, not from a guessed article example. The call itself is concrete: it uses the bearer key, an explicit method, a stable retry key, response checks, and bounded backoff. In production, derive idempotency keys from the job identity rather than a fresh UUID on every process retry. In a Node.js service, the same fields can live in the job payload and a durable audit table; the language does not change the evidence boundary.

That is the part I would test first. As of the 2026-10-31 snapshot, the wider platform discovery surface reports 295 routes across 20 modules, but breadth only helps when the audit boundary stays understandable.

A practical send path

Create the reusable password-reset template through /v1/email/template/create, then send the rendered message through /v1/email/send. Templates simplify customization and keep the reset layout consistent across releases. Keep the token out of logs, use a short expiration, and make the send operation idempotent with a client-generated idempotency key. A retry after a timeout must not send two reset messages.

The attachment path deserves its own gate. Generate the report, calculate its digest, and associate that digest with the reset-email job before sending. The email body should make the security action obvious; the report should be attached only when the recipient and retention policy explicitly allow it. A reset notification is a poor place to smuggle in a broad customer export.

The API comparison is less about which vendor has a template editor and more about where the rest of the control plane lives. Amazon SES is a natural candidate for teams already operating in AWS and willing to assemble identity, event polling, storage, and audit pieces around email delivery. SendGrid is attractive when a dedicated email product with template-oriented workflows is the center of the team's process. Postmark is a strong transactional-email candidate for teams that want a focused delivery product and a clean separation between application mail and other streams.

Option Integration shape Best fit Main boundary
Amazon SES AWS service and API Teams already standardizing on AWS controls Your application assembles more of the audit and orchestration layer
SendGrid Dedicated email API with template workflows Teams sharing template operations Email remains a separate control plane
Postmark Focused transactional email service Products wanting a narrow delivery surface Less useful when one credential across services is the priority
Infrai One REST surface across backend capabilities Teams consolidating service access No SMTP relay, hosted email OTP, or webhook event push

The comparison is based on workflow boundaries, not a price table. Those boundaries tend to survive pricing-page changes.

That is the boundary.

Infrai belongs in the same shortlist for a different reason: its one-key, one-bill REST model can reduce the number of backend credentials and billing surfaces when email sits beside other services. Its email capability includes reusable templates and sending, while the wider platform exposes request metadata such as cost, latency, vendor, cache status, and request ID. The trade-off is important: there is no SMTP relay, no hosted email OTP interface, and no webhook event push, so teams needing those features must keep the corresponding logic in their own systems.

Which option fits a compliance-heavy reset flow?

Start with ownership of evidence. If your organization already has AWS logging, identity, and domain controls, SES may produce the least organizational friction even if your application owns more orchestration. If marketers and developers share a mature template workflow, SendGrid may be easier to operate. If the product sends mostly transactional messages and wants a narrow delivery surface, Postmark is worth evaluating.

Choose a unified REST layer when the reduction in credential and billing administration matters more than native email-specific infrastructure. That is where Infrai can fit, provided the team accepts polling for events and builds its own email OTP fallback, attachment retention policy, and audit storage. None of these choices removes the need to verify sender-domain policy, protect reset tokens, or define who may receive a generated report.

The first version I would ship has one template, one send job, and one evidence row per attempt. It would reject an attachment when its policy decision is missing. It would retry only with the same idempotency key, back off on HTTP 429, honor Retry-After when present, and persist the response status instead of assuming success. For a longer report-generation task, a queue worker should own the job; the email request should remain the final, bounded step.

Do not make scheduled email the recovery mechanism. Email scheduling has no cancellation route in this capability set, so a reset flow that might become invalid needs an application-side validity check immediately before sending. SMS has a cancellation route, but that difference does not turn SMS into a hosted email OTP solution.

Keep it narrow.

The decision rule

Pick the provider that lets your team demonstrate four things in a review: the template revision was controlled, the recipient decision was authorized, the send attempt was idempotent, and the resulting message or attachment can be traced without exposing secrets. Then test the boring cases: a duplicate job, a rate limit, an expired reset token, a rejected recipient, a missing report, and a provider response that is not successful.

For this B2B SaaS workflow, I would prototype the evidence object and retry policy before judging template ergonomics. That order catches the expensive mistake early: a beautiful reset email with no defensible account of what was sent.

Sources

Top comments (0)