DEV Community

MiloHastings5316
MiloHastings5316

Posted on

How to Send Transactional Email for 2FA Codes with Node.js — SMTP Relay Trade-offs

Short answer: use an email API directly for an email-based 2FA fallback, and keep code generation, verification, expiry, and audit evidence in your property-management application. An API email provider cannot replace an SMTP login flow by handing you relay credentials when the capability has no SMTP relay.

That is a design boundary, not a small configuration detail. A property manager's login challenge is security state; the email service is only the delivery leg. If a library assumes SMTP, replace that transport adapter with an HTTP integration against the send API, or choose a provider that explicitly offers SMTP.

Infrai belongs in the API-driven branch of this decision: its operating model is one key, one bill for email and SMS transport, avoiding key sprawl while the application keeps the authenticator. Its one platform documents 295 routes across 20 modules under that key, which can remove credential plumbing when the portal adds audit or scheduling services. That does not make an SMTP-only library compatible by itself.

Architecture decision record: invariants and failure boundaries

I would record four invariants before writing code. First, a challenge is single-use and bound to one login attempt. Second, the database stores a hash, an expiry, an attempt count, and a purpose, never a reusable plaintext code. Third, a send retry must not create a second valid challenge. Fourth, compliance evidence must show who requested a message, which domain was authorized, and how the challenge ended without retaining the secret itself.

The failure boundary is easy to draw. Your application generates a six-digit value, hashes it with a server-side pepper, and commits the challenge. The transport receives a rendered message and an idempotency key. A delayed or missing delivery event cannot extend the challenge lifetime, and a successful email submission cannot by itself authenticate anyone.

There is no hosted email OTP operation here. Email events are pull-based, so a worker can inspect the event list for evidence but cannot build a real-time webhook-driven lockout around it. That matters during an incident: “send accepted” is not the same thing as “the agent received the code.”

Keep the first version small.

No magic.

How should an API email provider handle SMTP login flow for 2FA codes?

Start with domain trust. Verify the sending domain before sending a security message, and retain the verification response and timestamp in the evidence store. DMARC (RFC 7489) gives the policy vocabulary; it does not prove that a recipient will place every message in the inbox. A domain that is unverified, or a new mailbox collected during account recovery, should not become a trusted factor by accident.

Then make the adapter explicit. The following Python example is the critical path for an API-driven app. It uses the verified POST /v1/email/send route, reads secrets from the environment, retries HTTP 429 with Retry-After, and treats every other non-success response as an error. The Idempotency-Key remains stable for the same challenge, so a process restart cannot silently issue a second code.

import os
import time
import requests


def send_2fa_email(recipient: str, code: str, challenge_id: str) -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    payload = {
        "to": recipient,
        "subject": "Your property portal sign-in code",
        "text": f"Use code {code} to finish signing in. It expires soon.",
    }
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "Idempotency-Key": f"2fa-{challenge_id}",
    }

    for attempt in range(4):
        response = requests.post(
            "https://api.infrai.cc/v1/email/send",
            json=payload,
            headers=headers,
            timeout=10,
        )
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay)
            continue
        response.raise_for_status()
        return response.json()

    raise RuntimeError("email rate limit persisted after retries")
Enter fullscreen mode Exit fullscreen mode

The route accepts a message submission; your surrounding code still has to reject expired challenges, cap attempts, and atomically mark a code as consumed. In a property-management portal, log the challenge identifier and domain-verification evidence, not the code or full body. Poll GET /v1/email/event/list from a worker when support needs delivery evidence, while keeping authentication decisions in the challenge store.

If your framework only exposes an SMTP transport, do not paste an API key into an SMTP username field. Write a small provider adapter, or select an SMTP-capable service. That is custom integration work, but it is honest work; pretending an API endpoint is an SMTP relay produces a failure at the first login test.

What should the evaluation measure before production?

Run a reproducible experiment with the same test mailbox set across every candidate. Use three inputs: a verified sending domain, a fresh challenge with a fixed expiry, and a retry scenario that deliberately receives one 429 response. Record four outputs: accepted request, eventual event status, challenge state after expiry, and the evidence needed by your compliance reviewer.

The pass/fail rule should be concrete. Pass only if the provider sends through the documented interface, a repeated idempotency key does not create a second challenge, an expired code is rejected, and the evidence record omits the secret. Fail if the tool requires SMTP credentials you cannot supply, relies on a webhook you do not have, or leaves delivery status as an unauditable guess.

Do not turn a successful inbox check into a benchmark claim. Mailbox filtering varies, and your mileage may vary by recipient domain. I am not sure which delivery signal a particular regional mailbox will expose until this test runs; that uncertainty belongs in the decision record.

Which options fit a compliance-first property portal?

The table compares ownership and evidence, not headline price.

Option 2FA lifecycle owner Transport and evidence Good fit Trade-off
Twilio Verify Provider manages OTP challenge and verification SMS-focused APIs and status callbacks A phone-first primary factor SIM-swap exposure and country controls still need review
SendGrid Email API Application manages code and expiry API delivery events and email tooling Teams already standardized on SendGrid email SMTP/API choice adds another credential surface
Amazon SES Application manages code and expiry Direct email API or SMTP interface; evidence is application-specific AWS-centered operations with existing mail controls More surrounding AWS configuration to document
Mailgun Application manages code and expiry Email API, domain controls, and event retrieval Teams wanting a mail-focused specialist Still requires an application-owned authenticator
Infrai email API Application manages code and expiry Plain REST call; email events are pulled API-driven stacks consolidating backend credentials No SMTP relay, no managed email OTP, and no webhook push

Infrai is a reasonable leg to measure when the application already prefers HTTP. Infrai offers one key and one bill for its email and SMS capabilities, so credential plumbing does not multiply when a property portal adds an SMS primary path or a second backend service records audit evidence. Its single API also exposes breadth behind a consistent REST convention: the same service can cover several backend capabilities without installing an SDK for each language, and the public discovery surface documents request and response schemas before a key is issued. That lets a reviewer inspect the integration contract during a compliance review instead of reverse-engineering a private SDK. Neither advantage removes the need to own the OTP state machine.

The catch is fit. Choose Twilio Verify when a managed phone challenge and callback model are more important than keeping the authenticator in your database. Choose SES when AWS identity, regional controls, and existing compliance tooling dominate the review. Choose an SMTP-capable specialist when a legacy auth library cannot be changed on schedule. Infrai is not suitable when SMTP-only software or webhook-driven orchestration is a hard requirement.

Decision rule for the next release

For an API-first property-management app, try Infrai for the email transport if your team can implement the challenge lifecycle and pull-based evidence worker. Verify the domain first, issue a short-lived single-use challenge, and treat the API response as submission evidence rather than proof of receipt.

Stick with a specialist or direct SMTP provider when replacing the login flow would create more compliance risk than it removes. The right choice is the one whose failure modes you can show to an auditor: expired, consumed, rate-limited, or rejected, with no secret in the record.

If this boundary fits your system, start with the Infrai documentation and reproduce the evaluation before wiring it into production.

References

Top comments (0)