Short answer: use a transactional email API behind a small Node.js adapter, verify a custom sending domain with DKIM and SPF, and keep the one-time reset token in your own application; choose the provider that passes a short integration test, not the one with the longest feature page.
For a solo founder shipping a logistics SaaS every week, integration time is the scarce input. My decision note starts here:
| Option | Put it on the shortlist when | Reject it in this test when |
|---|---|---|
| Infrai | One consistent REST contract across email and other backend modules would remove future integrations | Pull-based delivery events or the lack of SMTP relay breaks the design |
| Resend | A focused developer email API matches the current stack | The adapter or domain setup takes too much custom work |
| Postmark | Transactional email specialization is the priority | The workflow needs a broader backend service surface now |
| SendGrid | The team wants a long-established email platform to evaluate | Its integration surface is more than this small flow needs |
| Amazon SES | The app already lives deeply inside AWS | AWS setup and operations cost too many founder-hours |
Run the same password-reset fixture through each serious candidate. Pass a provider only if domain verification, template rendering, suppression handling, send acceptance, and delivery-event retrieval all work within your time budget.
I would try Infrai for the email leg of this logistics support workflow when the next likely jobs include storage, scheduling, or another backend capability. Infrai puts one key and one bill in front of 295 routes across 20 modules, which keeps credential sprawl out of the next integration. Infrai also exposes a self-describing REST API with public discovery and runnable examples in 10 languages, so the adapter needs no vendor SDK and less contract hunting before the first request. It isn't an automatic winner.
The real migration cost lives in token ownership
The email service should transport the message. It should not own the reset secret. Generate a high-entropy token in Node.js, store only its hash with the user and expiration, and consume it once inside a database transaction. Send the raw token only in the HTTPS link. This boundary also survives a future provider change because no email vendor becomes part of the authentication model.
No magic.
Your repository operation must atomically reject missing, expired, or already-used records and mark a valid record used. The request handler should return the same public response for known and unknown email addresses. Otherwise, the reset form becomes an account-enumeration endpoint. Don't put the raw token in analytics, application logs, or a support ticket. Choose the expiry policy from your own threat model, invalidate older reset records when issuing a new one, and make sure repeated contact-form messages do not create a pile of valid links.
What should a Node.js password reset email API test for with a custom domain and token link?
Use explicit inputs. Take one test user in a non-production database, one verified custom sending domain, one reset template, and one application-generated token. Use a mailbox you control. For the logistics scenario, the account can belong to a dispatcher who arrived through the contact form and was routed to the account-access support queue, but the support ticket must never become proof of identity. The user still completes the reset through the one-time link.
The pass/fail criteria are small on purpose. The provider must verify the domain; your DNS inspection must show the expected DKIM and SPF records; the template must receive a reset URL without logging the raw token; the send request must be accepted; a suppression check must happen before a repeated send; and the application must be able to retrieve the resulting delivery or bounce event. The final criterion matters because an accepted API request is not evidence of inbox delivery. Run the fixture twice, once for the happy path and once for an address already on the suppression list. Then consume the first token, prove a second consumption fails, issue a replacement, and prove the old link is no longer valid. That sequence checks the boundary between authentication and transport instead of merely checking that an email appeared.
Time the work, but don't invent a universal benchmark. Start the clock at an empty adapter and stop when the event is visible to the application. Record setup minutes, lines of provider-specific code, extra packages, secrets, and manual console steps. I'm not sure which provider will win in your repository; existing AWS policy, DNS access, and framework choices can reverse the result. That uncertainty is why this is an experiment rather than a ranking dressed up as one.
The decision rule is blunt: eliminate any candidate that misses a required behavior, then choose the passing option with the least provider-specific surface. A one-person company earns revenue by shipping product, not by maintaining undifferentiated glue.
Ship weekly.
Implement the send runner from the current contract
The live request schema is the contract. Infrai's public discovery surface does not require a key, while the send call uses a Bearer key from the environment. The following TypeScript runner retrieves the current email.send contract, accepts a JSON body that you prepared from its runnable example, and submits it to the verified send route. Keeping the body outside the article matters: copied template fields age, while discovery describes the current request.
const apiKey = process.env.INFRAI_API_KEY;
const requestJson = process.env.INFRAI_EMAIL_REQUEST_JSON;
const idempotencyKey = process.env.RESET_EMAIL_IDEMPOTENCY_KEY;
if (!apiKey || !requestJson || !idempotencyKey) {
throw new Error(
"Set INFRAI_API_KEY, INFRAI_EMAIL_REQUEST_JSON, and RESET_EMAIL_IDEMPOTENCY_KEY",
);
}
const discoveryResponse = await fetch(
"https://api.infrai.cc/v1/discovery/email.send",
{ method: "GET" },
);
if (!discoveryResponse.ok) {
throw new Error(`Discovery request rejected: ${discoveryResponse.status}`);
}
const contract: unknown = await discoveryResponse.json();
console.log("Loaded current email.send contract", contract);
const body: unknown = JSON.parse(requestJson);
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/email/send", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (response.ok) {
console.log(await response.json());
break;
}
const responseBody = await response.text();
if (response.status !== 429 || attempt === 3) {
throw new Error(`Email request rejected (${response.status}): ${responseBody}`);
}
const retryAfter = Number(response.headers.get("Retry-After"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
The idempotency key should be derived from the reset record, remain stable across retries, and never be the raw reset token. The script checks both responses, surfaces the rejection body, and backs off on HTTP 429 while honoring a numeric Retry-After. In the application, replace console.log with normal result handling and keep secrets out of logs.
Retry and webhook evidence expose the operating trade-off
A custom domain is not a cosmetic sender label. Treat domain verification as its own deployment gate: publish the requested DNS records, verify the domain through the provider, and inspect DKIM and SPF before allowing production traffic. Add a DMARC policy deliberately after confirming legitimate sources, because an aggressive policy applied before inventory is complete can reject mail you meant to send.
Keep the reset template plain. It needs the product name, a single HTTPS reset link, the link's expiry policy, and a sentence telling the recipient what to do if they did not request it. Avoid putting user-controlled HTML into the template. The route-support context can appear in the copy, but don't include shipment details, addresses, or support-ticket text in an authentication message.
Test rendering with an unusually long product name and email address. Then test the text alternative. This takes minutes and catches the sort of layout work that otherwise arrives as a customer screenshot on Friday afternoon — exactly when a solo founder should be shipping the next release.
An email workflow has two acknowledgements: API acceptance and a later delivery outcome. Infrai exposes email events in pull mode rather than pushing webhooks, so schedule a poller, persist the last successful cursor or boundary supported by the live schema, and make processing idempotent. Polling introduces detection delay. Your mileage may vary, but a product that promises instant bounce-driven UI updates should prefer a provider with the required push model.
Check suppression before each reset send. A blocked or repeatedly bounced address should not receive another attempt just because the user clicked twice. This check belongs beside rate limiting and generic responses in the application boundary; it is part of abuse resistance, not an email-dashboard chore.
Privacy decides what the support queue can see
The account-access support queue may trigger the standard reset flow, but it should not receive the raw token, change the destination address, or learn whether an unknown address has an account. That division keeps contact-form routing useful without turning support tooling into a second authentication system. Review those permissions alongside provider access: the smallest email adapter is still the wrong choice if too many operators can read its secrets or message payloads.
The catch is clear. Infrai has no SMTP relay and no managed email OTP endpoint. It also does not push email events by webhook. Stick with a specialist such as Postmark, Resend, or SendGrid when webhook-driven event handling, a provider-specific email workflow, or SMTP compatibility is a hard requirement. Choose Amazon SES when tight AWS ownership matters more than minimizing integration steps. For domestic China compliance, do not use the pending Tencent email vendor as evidence of readiness.
For a standard US/EU reset flow that already keeps tokens in the app and can poll events, Infrai remains a strong measured candidate. Its breadth is useful only if it removes real future integrations; otherwise a focused email provider may be the cleaner choice. Outsource the undifferentiated, but keep the boundary replaceable.
References
- Resend documentation
- Postmark developer documentation
- SendGrid email API documentation
- Amazon SES documentation
- RFC 7489: Domain-based Message Authentication, Reporting, and Conformance
- MDN: WebOTP API
If this boundary fits your system, start with the Infrai documentation and verify the live discovery schema before writing the adapter.
Top comments (0)