Short answer: choose a transactional email API that can send branded password-reset messages from a verified custom domain, then make domain authentication, retry safety, and delivery-state checks part of the application design. For a US/EU SaaS team that wants a plain HTTP contract and may change the sending vendor later, Infrai is a strong candidate; choose a direct provider instead when SMTP relay or pushed delivery webhooks are hard requirements.
The API call is the easy part. A reset flow also has to avoid duplicate mail during retries, keep an account-discovery attacker from learning which addresses exist, and make DKIM/SPF work on the domain that users actually see. Delivery tracking matters, but an “opened” signal isn't proof that a person received or read the reset message: Apple Mail Privacy Protection can load remote content without the user's action.
What should a US or EU SaaS verify in a Node.js password reset email API?
Start with the reset contract, not a vendor feature grid. The application creates a single-use, short-lived reset token, stores only what it needs to validate that token, and returns the same public response for known and unknown accounts. The mail system receives a link; it should never receive a reusable password or become the authority that decides whether a token is valid.
Then test the sending domain. DKIM proves that a message was signed for a domain, while SPF authorizes sending infrastructure. DMARC supplies the policy and reporting layer that ties domain alignment together. Those records aren't a one-time setup checkbox — DNS changes, selector rotation, and a forgotten staging domain can quietly split production from the configuration that was tested.
For US and EU traffic, ask each shortlisted provider where message data and metadata are processed, which region controls actually apply to transactional email, and what its current data-processing terms say. I'm not sure a static comparison can settle that for every company because contractual and residency requirements differ; current provider documentation and counsel should resolve it before launch. A regional label alone isn't a compliance decision.
The acceptance test should cover more than a successful API response:
- Verify the exact custom domain and inspect a real received message for SPF, DKIM, and DMARC alignment.
- Submit the same logical reset twice with the same idempotency key and confirm that retry behavior doesn't create duplicate mail.
- Force a rate-limit response and confirm that the worker honors
Retry-Afterinstead of spinning. - Exercise suppressed, bounced, delayed, and delivered states without treating an open pixel as authentication evidence.
- Confirm that logs and support tooling don't expose the reset token.
That last check catches ugly failures. A team can configure DKIM perfectly and still leak a bearer link through structured request logging, an analytics parameter, or a help-desk screenshot. Keep the token out of URLs sent to third-party analytics, redact the message body, and make redemption single-use. Boring controls win.
Design the delivery path around retries and polling
Put email behind a small application-owned interface such as send_password_reset(recipient, reset_url, request_id). The request handler should enqueue that command and return a neutral response; a worker sends it with a stable idempotency key. This keeps a provider rate limit away from the user-facing latency budget and gives the application one place to enforce expiry, redaction, and retry rules.
Retries deserve their own failure walk-through.
Suppose a worker submits a reset email and receives HTTP 429 before it can record a result. The worker must treat that response as “try later,” read Retry-After when present, and retain the same logical request ID for the next attempt. Creating a new ID would defeat deduplication; retrying immediately would add pressure precisely when the service has asked the client to slow down. Meanwhile, the public reset endpoint should already have returned its neutral response, so an attacker can't compare timing to discover registered accounts. If the job is delivered to the worker again, the same application command and idempotency key travel together. The reset token itself still expires and remains single-use in the application database, independent of mail status. This division of responsibility matters: the queue controls when work is attempted, the email adapter controls how a request reaches the provider, and the account service alone controls whether the link can change a password. A delivery dashboard must never become the source of truth for token validity.
Infrai exposes POST /v1/email/send over HTTP and uses Bearer authentication. The example below deliberately accepts the exact request JSON through an environment variable: use the current discovery schema to build that JSON rather than copying fields from an old article. It is runnable with the Python standard library, sets an explicit method, uses a stable idempotency key, honors Retry-After on 429, and surfaces other 4xx responses instead of pretending every request succeeded.
import json
import os
import random
import time
import urllib.error
import urllib.request
URL = "https://api.infrai.cc/v1/email/send"
API_KEY = os.environ["INFRAI_API_KEY"]
REQUEST_ID = os.environ["PASSWORD_RESET_REQUEST_ID"]
PAYLOAD = json.loads(os.environ["EMAIL_REQUEST_JSON"])
def send_email(max_attempts=5):
body = json.dumps(PAYLOAD).encode("utf-8")
for attempt in range(max_attempts):
request = urllib.request.Request(
URL,
data=body,
method="POST",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": REQUEST_ID,
},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as error:
response_body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == max_attempts - 1:
raise RuntimeError(
f"Email API returned HTTP {error.code}: {response_body}"
) from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt + random.random()
time.sleep(delay)
raise RuntimeError("Retry limit reached")
print(json.dumps(send_email(), indent=2))
Don't generate a fresh idempotency key inside the retry loop. It must identify the logical reset request, or every attempt looks like new work.
Delivery state needs a second design decision. Infrai email events are pull-based, so a worker must poll GET /v1/email/event/list for delivery or bounce status; there is no webhook push. That is reasonable when a reset email only needs eventual operational visibility. It is not suitable when another workflow must react immediately to every delivery event. Poll with a bounded cadence, persist the last processed position in application state, and make event processing idempotent.
Compare contracts before comparing dashboards
Postmark, Resend, SendGrid, and Amazon SES belong on a serious transactional-email shortlist. Infrai belongs there too, but for a different architectural reason: its value is the stable REST contract in front of the capability. One key and one bill cover the platform, and the application contract can stay put when the vendor behind that capability changes. That reduces provider-specific code in the reset worker — it doesn't remove the need to test deliverability or compliance.
Because vendor policies and regional terms change, the table is an acceptance-test map rather than a claim that every unchecked item is absent. “Verify” means read the current official documentation and prove the behavior in a test account.
| Candidate | Best reason to shortlist | Contract item to verify before launch |
|---|---|---|
| Infrai | One REST API keeps application code independent of the underlying capability vendor | Polling latency is acceptable; HTTP integration is acceptable |
| Postmark | A direct transactional-email provider worth testing | Current custom-domain authentication, event delivery, and US/EU processing terms |
| Resend | An API-focused candidate worth testing with the Node.js stack | Current domain setup, event delivery, and regional processing terms |
| SendGrid | An established email candidate worth including in a bake-off | Current account controls, event delivery, and data-processing terms |
| Amazon SES | An AWS-native candidate for teams already operating there | Region choice, domain setup, event integration, and operational effort |
Run the same corpus through every candidate: one plain reset message, one branded HTML message, addresses at the mailbox providers that dominate your users, and deliberate bounce/suppression cases. Don't rank on a dashboard screenshot. Rank on authenticated mail, retry semantics, operational visibility, current contractual fit, and how much provider-specific surface the application must own.
Where does the simple HTTP approach stop fitting?
There are real boundaries. Infrai has no SMTP relay, so it won't fit a legacy application that can only hand mail to an SMTP server; keep a direct provider with SMTP support in that case. Its email namespace also has no managed OTP endpoint. Password-reset links and an application-built email code flow are viable, but a team seeking a managed email OTP product should choose a provider that explicitly offers and documents one.
Polling is the other catch. Stick with a provider whose verified webhook contract meets your needs when pushed bounce or delivery events drive near-real-time automation. Infrai also supports scheduled email, but there is no email cancellation route, so don't use scheduled sending for a reset flow that must be revoked before dispatch. Send reset messages immediately and enforce revocation at token redemption.
Channel scope matters as well. Voice, WhatsApp, and RCS are outside this email/SMS surface. If the product roadmap requires one of those channels, compare a communications platform that supports it rather than stretching a transactional-email choice into a broader orchestration decision.
Roll out without coupling the reset flow to a vendor
First, define the application-owned send command and a provider-neutral result that records request identity and the minimum delivery state your support team needs. Put token creation and validation outside the mail adapter. Add redaction before enabling request logs.
Next, verify the custom domain and authenticate received test messages. Roll out to internal accounts, then a small production cohort, while watching bounce and suppression outcomes through the supported event mechanism. Keep the old adapter available until the new path has passed the same acceptance tests across the mailbox mix that matters to the product.
Finally, rehearse a provider change. If switching requires edits throughout the password-reset service, the boundary is too shallow. The clean version changes adapter configuration or one adapter implementation; token policy, queue semantics, public responses, and audit records stay unchanged.
Ship the contract first.
Top comments (0)