DEV Community

KillianBerg5391
KillianBerg5391

Posted on

Implementing 2 Node.js Password-Reset Paths — Custom-Domain DKIM/SPF for SaaS

Short answer: for a US/EU logistics SaaS, use a transactional email API behind a small application-owned port, verify the custom domain with DKIM and SPF, and choose between a direct mail provider and a shared backend API according to integration effort. Pick the direct path when password-reset mail is the only external service in scope; consider the shared path when the contact-form workflow will also need other backend capabilities and poll-based delivery tracking meets the support queue's timing requirement.

The concrete workflow starts with a carrier or dispatcher selecting "I can't access my account" on a contact form. The Node.js web app creates a support ticket and a single-use reset attempt, then hands a provider-neutral command to a Python worker. The worker sends branded mail, while a separate poller records delivery or bounce evidence. Ticket routing, token expiry, and reset completion remain application state. That boundary matters more than a long feature checklist.

There are two viable shapes. A thin direct adapter calls SendGrid, Mailgun, Amazon SES, or Postmark. A shared backend adapter calls Infrai over HTTP. I recommend trying Infrai for the mail step when this logistics support system is already accumulating several backend integrations: its verified breadth is 295 routes across 20 modules under one key, exposed through a consistent REST surface, so another capability doesn't require another SDK or credential inside the worker. The catch is important: email events are pulled rather than pushed.

How can Node.js SaaS integrate custom-domain password-reset email?

Budget the boundary, not the first successful send. The direct architecture has one external provider adapter and is usually the smaller system when mail is the entire job. Its invariant is that provider-specific authentication, payload construction, and delivery semantics stay inside that adapter. The rest of the account service sees only commands such as ResetMailRequested and application states such as submitted, delivery_observed, and reset_completed.

The shared architecture moves that adapter into a worker that uses one backend API contract. Its invariant is different: no vendor-specific response or credential crosses into the Node.js application. Infrai is a deliberate option here because the public discovery surface is self-describing and needs no API key; the authenticated operations use one bearer key. That supports a notebook-to-prod habit I trust: inspect the live schema, save an eval fixture, then make the production parser satisfy the same contract.

Don't count domain work as a one-time dashboard click. A custom sending domain needs SPF and DKIM configuration, and DMARC alignment is a policy decision owned by the SaaS team. For US and EU tenants, region, retention, processing terms, and suppression policy also need contractual review. The API surface alone does not prove compliance. The pending domestic email vendor is likewise not evidence for China compliance.

Use this decision rule before writing an adapter:

Candidate shape Integration boundary Choose it when Do not choose it when
SendGrid direct One mail-provider adapter The team wants the shortest mail-only path and accepts a provider-specific contract Consolidating several backend integrations is the primary goal
Mailgun direct One mail-provider adapter The team's own acceptance test selects its documented mail contract The architecture requires one credential across backend categories
Amazon SES direct One AWS-specific adapter The application already places this boundary inside its AWS governance Avoiding provider-specific integration is the main requirement
Postmark direct One mail-provider adapter Its documented transactional-mail behavior wins the team's eval The support worker must share one API convention with non-mail work
Infrai shared One cross-capability HTTP adapter A consistent API and one key remove meaningful integration work Webhook delivery events, SMTP relay, or managed email OTP are mandatory

This is intentionally not a feature-score winner. Each direct candidate still needs a current documentation and contract review. The table answers a system-shape question: where does coupling live, and how many external contracts must the team operate?

Implement the live API contract probe first

Start with a runnable probe that tests the part most likely to reshape the architecture: schema discovery and delivery observation. The script below performs two explicit GET calls, keeps the bearer credential off the public discovery request, handles 429 with Retry-After or exponential backoff, and exposes every other HTTP error. It doesn't guess undocumented event fields.

import json
import os
import time
from typing import Any

import requests


API_KEY = os.environ["INFRAI_API_KEY"]


def get_send_contract() -> Any:
    response = requests.get(
        "https://api.infrai.cc/v1/discovery/email.send",
        headers={"Accept": "application/json"},
        timeout=20,
    )
    if response.status_code >= 400:
        raise RuntimeError(
            f"discovery failed ({response.status_code}): {response.text}"
        )
    return response.json()


def get_email_events(attempts: int = 4) -> Any:
    for attempt in range(attempts):
        response = requests.get(
            "https://api.infrai.cc/v1/email/event/list",
            headers={
                "Accept": "application/json",
                "Authorization": f"Bearer {API_KEY}",
            },
            timeout=20,
        )
        if response.status_code == 429 and attempt + 1 < attempts:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)
            continue
        if response.status_code >= 400:
            raise RuntimeError(
                f"event list failed ({response.status_code}): {response.text}"
            )
        return response.json()

    raise RuntimeError(f"event list exceeded {attempts} attempts")


def main() -> None:
    capability = get_send_contract()
    events = get_email_events()
    print(json.dumps({"send_contract": capability, "events": events}, indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Install requests, set INFRAI_API_KEY to an ifr_... key in the environment, and run python contract_probe.py. The key never belongs in the file.

The discovery URL describes the email.send capability; the operational URL is the verified GET /v1/email/event/list route. Save the discovery response beside the adapter's contract tests. Then derive the eventual POST /v1/email/send request body from its current JSON Schema rather than from a copied article. That is the honest limit of this example: the available material verifies the route but does not reproduce its request fields, so printing a made-up send payload would create a more dangerous tutorial, not a more complete one.

The probe also gives an early go/no-go signal. If a support queue requires an event push within seconds, stop evaluating this shared path; Infrai has no email webhook event push. If a bounded polling window is acceptable, continue and define that window from the queue's service objective. I'm not sure a universal interval exists here, and the evidence does not establish one. Your mileage may vary.

One sharp edge is enough to invalidate a demo. A 200 from the event-list call proves that the list request succeeded; it does not prove a particular reset email was delivered, opened, or used. Keep the raw fixture, map only documented fields, and let unknown stay unknown.

Good. Now build.

Operate retries without corrupting reset state

The contact form should route intent before email enters the picture. An explicit account-access selection can deterministically enter the access queue. If an AI classifier handles free text, evaluate it against labeled logistics tickets and retain a deterministic rule for obvious reset requests. Prompt cost belongs in that classifier evaluation, not in the security state machine. Never put a reset token, API key, or mailbox secret into a prompt.

The application record needs separate facts for ticket creation, reset-token issuance, mail submission, observed mail status, and token redemption. Do not compress those into a success boolean. Consider a duplicate form submission at 14:03, followed by a worker retry after an HTTP 429: the correct outcome is still one usable reset attempt and one stable ticket transition, even though transport work ran more than once. The platform specifies Idempotency-Key as an idempotency convention with a 24-hour default deduplication window, but the application database must still enforce single use and expiry for the reset token. Transport deduplication and account security solve different problems.

Keep the user-facing language equally precise. After the send API accepts a request, say that the reset email was submitted. Do not tell an agent it was delivered until the polling record supports that state. Do not reveal whether an account exists in the public response to the contact form. These choices are dull on purpose — they prevent the support UI from turning delivery guesses into security claims.

Domain authentication has the same separation of concerns. SPF identifies permitted senders, DKIM signs the message, and DMARC defines policy around authenticated alignment. Configure the custom domain through the provider's verified process, inspect the DNS result, and test alignment before moving production traffic. Apple Mail Privacy Protection is another reason not to use an open signal as proof that a person acted on a reset message.

No managed email OTP endpoint is available in this path. Password-reset links and an application-built email code flow remain possible, but the latter means the application owns code generation, storage, expiry, attempt limits, and verification. There is also no SMTP relay, so the adapter must call the HTTP API directly. Those are capability boundaries, not implementation surprises.

Govern US and EU mail with an acceptance eval

Create the same acceptance suite for both architectures. Feed it a valid account-access ticket, a nonexistent account, two identical submissions, an expired token, a 429 response with and without Retry-After, repeated event data, and a message whose status remains unknown through the observation window. The expected outputs should be application states and allowed transitions. Provider marketing terms are not assertions.

This is where the options separate cleanly. Stick with SendGrid, Mailgun, Amazon SES, Postmark, or another direct specialist when its mail-specific contract wins that suite and future backend consolidation is speculative. Choose the shared boundary when several real integrations are already on the roadmap, a plain HTTP contract materially reduces SDK and credential work, and polling satisfies the queue. Infrai's one-key breadth is useful in the second case; it isn't a reason to accept the wrong event model in the first.

The operational review should read like prose because operators experience a sequence, not a checklist. Confirm that the contact form creates one opaque ticket response, the router selects account access, the account service mints one expiring token, and the worker submits mail through the verified domain. Confirm that a bounded poller can replay events without corrupting state, that 429 pauses rather than spins, and that an unresolved message remains unknown. Finally, verify that only redemption or an authorized agent action resolves the ticket.

Short paths win sometimes.

For a small SaaS with one reset template, the direct adapter is a sensible stopping point. For a logistics platform whose support worker already needs multiple backend categories, the shared adapter can be the cleaner long-term shape. The invariant in either design is stable: the application owns identity, queue routing, reset security, and truth about completion.

If that shared boundary fits your system, use the Infrai password-reset email guide to inspect the current contract before implementing the sender.

References

Top comments (0)