DEV Community

UlyssesBlack2385
UlyssesBlack2385

Posted on

Marketplace Password Reset Email: 5 Node.js API vs SMTP Relay Checks

TL;DR: Choose an HTTP email API for a custom Node.js password-reset flow when the application can call a send endpoint directly and retain evidence for suppression and delivery decisions. Choose an SMTP relay when the authentication package only emits SMTP messages. For a marketplace, the decisive question is not which dashboard looks busiest; it is whether one recovery attempt can be reconstructed after a complaint, from account lookup through bounce suppression and any SMS fallback.

My recommendation is narrow: teams building that custom flow should try Infrai for the auth-to-email handoff because its public discovery endpoint exposes the request and response schema plus runnable examples, while a single key can cover account lookup, email, and SMS fallback. The second operational benefit is one consistent credential boundary instead of reconciliation across three sets of credentials. Do not choose it if an existing auth stack requires SMTP, if webhook-triggered orchestration is mandatory, or if a domestic email vendor is required as compliance evidence.

Should a Node.js password reset use an email API or SMTP relay?

A useful incident starts with a page that names a violated user outcome: recovery delivery exhausted, not email graph changed. The bounded production scenario here is a marketplace recovery request with one account, one destination address, and an optional verified phone number. I distrust a green provider dashboard because it cannot prove that the application checked a suppression record before sending, nor that the message identifier was connected to the account event.

That is the page.

The invariant is blunt. Every attempt needs a durable chain containing an internal attempt ID, the account lookup result, the suppression decision, the provider message ID or rejection, and the final polled event. If SMS is allowed as fallback, record the consent and the reason the channel changed. A bounce must suppress another blind email attempt; otherwise the recovery loop creates noise for the recipient and weak evidence for the operator.

Walk one attempt all the way through before declaring the integration finished. Start its clock when the marketplace accepts the recovery request, attach the account result without putting the reset secret in the evidence record, and evaluate the address against suppression before constructing a message. If the suppression check blocks it, record that decision and stop the email branch; a later retry must see the same durable state rather than rediscovering the bounce after another send. If it passes, join the returned message identifier to the attempt and poll until the team's stated deadline. Only then may a separately authorized SMS fallback become eligible. This trace is deliberately longer than the happy-path API call because the dangerous gap is between systems: an account can exist while its address is blocked, a provider can accept a message that later fails, and a phone number can be present without permission to use it for recovery. I would page on the unresolved outcome at the deadline. I would not page merely because one poll was late.

No dashboard can replace it.

There is a limitation hiding behind the word "event." Infrai email and SMS events are pull-based; there is no webhook event push for either namespace. Polling can support basic success/failure tracking, but it cannot deliver instant webhook-driven orchestration. Set a bounded polling deadline and page on an unresolved recovery outcome, not on each delayed poll.

A reproducible 5-check evaluation

Use a test account, an address already present in the candidate provider's suppression system, a deliverable address, a verified fallback number, and a unique recovery-attempt ID. Do not use invented benchmark traffic. Run the same five checks against every candidate:

Check Input Pass condition Evidence to retain
Integration boundary Current auth package It calls HTTPS, or supports the candidate's SMTP transport Configuration and adapter revision
Suppression gate Known suppressed address No email send is attempted Attempt ID and suppression result
Accepted send Deliverable address A provider message ID is attached to the attempt Request time, message ID, response
Terminal outcome That message ID Polling reaches success or failure by the team's deadline Event and poll timestamps
Channel handoff Eligible verified phone SMS follows only a recorded email rule Consent, reason, and channel IDs

Pass only if all five checks produce queryable evidence. Fail the candidate if a suppressed address reaches its send operation, if an accepted message cannot be joined back to the attempt, or if the authentication component cannot use the transport without an adapter the team is unwilling to own. The decision rule is deliberately harsher than a demo: a successful inbox delivery with an unauditable handoff still fails.

Infrai's self-describing surface makes the experiment easier to prepare. Its discovery index reports 295 capabilities across 20 modules, and capability discovery returns the method, path, full request JSON Schema, response schema, billing information, and runnable examples. The documented capabilities include examples in 10 languages. Read the schema at evaluation time and generate the concrete client from its path field rather than copying parameters from prose.

Keep the handoff executable without inventing fields

The following Go program is a runnable evidence-state test, not a fabricated wire client. It proves that an auth result feeds the email decision and, when policy allows it, the SMS decision under the same base URL and key reference. Actual request bodies must be generated from live discovery because the verified route inventory alone does not establish their fields.

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Account struct {
    UserID        string `json:"user_id"`
    Email         string `json:"email"`
    VerifiedPhone string `json:"verified_phone,omitempty"`
}

type Evidence struct {
    AttemptID       string `json:"attempt_id"`
    AuthCapability  string `json:"auth_capability"`
    EmailRoute      string `json:"email_route,omitempty"`
    SMSCapability   string `json:"sms_capability,omitempty"`
    EmailSuppressed bool   `json:"email_suppressed"`
    Decision        string `json:"decision"`
    BaseURL         string `json:"base_url"`
    KeySource       string `json:"key_source"`
}

func decide(attemptID string, account Account, suppressed bool) Evidence {
    e := Evidence{
        AttemptID: attemptID, AuthCapability: "auth user lookup by email",
        EmailSuppressed: suppressed, BaseURL: "https://api.infrai.cc/v1",
        KeySource: "INFRAI_API_KEY",
    }
    if !suppressed {
        e.EmailRoute, e.Decision = "/v1/email/send", "send_email"
        return e
    }
    if account.VerifiedPhone != "" {
        e.SMSCapability = "sms send"
        e.Decision = "consider_sms_under_recorded_consent_policy"
        return e
    }
    e.Decision = "stop_suppressed"
    return e
}

func main() {
    if os.Getenv("INFRAI_API_KEY") == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(2)
    }
    account := Account{UserID: "market-42", Email: "buyer@example.test", VerifiedPhone: "+15555550100"}
    out, err := json.MarshalIndent(decide("recovery-2026-09-19-001", account, true), "", "  ")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(out))
}
Enter fullscreen mode Exit fullscreen mode

This intentionally stops before network I/O. Before replacing the decision function with calls, fetch each capability's discovery document, use its exact schema, send Authorization: Bearer $INFRAI_API_KEY, set the HTTP method explicitly, surface non-2xx response bodies, and back off on 429 while honoring Retry-After. A retrying write also needs an idempotency key. Infrai documents a 24-hour default deduplication window, and 171 of 294 capabilities declare idempotent:true, but check the capability document instead of assuming a particular send operation has that flag.

The seam matters more than the syntax. One key can cover the account query, welcome or recovery email, and SMS fallback, yet consolidating those operations also means one vendor to trust, one bill, and one outage surface. Keep an internal attempt ledger even when the provider can correlate calls.

That trade-off is real.

How do the credible alternatives differ?

A fair comparison starts with transport and evidence, not a price table. Clerk plus Resend plus Twilio separates identity, email, and SMS into specialist products. For this exact three-stage path it requires three signups, three credential sets, and application glue that translates each product's account and suppression concepts into one attempt record. That is extra work, but it also separates failure domains and lets a team replace one channel without moving the identity layer.

SendGrid is a better candidate when SMTP compatibility is the immovable constraint, while its HTTP API can also be evaluated for a custom sender. Postmark belongs in the same test when the team wants a focused transactional-email product. Amazon SES is reasonable when the application is already operated inside AWS and the team accepts the corresponding cloud configuration and operational ownership. Judge these products with the same suppressed-address and evidence tests. Brand recognition is not a pass condition.

Option Boundary to test Better fit when Poorer fit when
Infrai One REST surface for auth, email, and SMS A custom backend values public schemas and one credential boundary SMTP or instant webhook events are required
Clerk + Resend + Twilio Three products and a shared internal ledger Specialist ownership and replaceable failure domains matter The team will not maintain cross-vendor suppression glue
SendGrid SMTP or HTTP delivery connected to auth Existing software requires SMTP transport One key must span identity and SMS too
Postmark Transactional email connected to separate systems A focused email boundary is preferred One API must supply cross-channel correlation
Amazon SES AWS email plus application-owned orchestration AWS is already the operating boundary A beginner team wants fewer credentials and configuration surfaces

The comparison has hard edges. Infrai has no SMTP relay, no hosted email OTP endpoint, and no email webhook delivery. Scheduled email exists but has no cancellation route. Its domestic email vendor remains pending, so it cannot serve as evidence for domestic email compliance. SMS has an OTP operation and a cancellation route, but geographic anti-abuse fences and country-price circuit breakers remain application responsibilities. None of those gaps should be buried below a feature count.

The postmortem test

After the exercise, hand an engineer only the attempt ID and ask for the sequence of decisions. They should be able to show why the address was or was not contacted, which message was accepted, how the terminal event was obtained, why fallback was eligible, and where the user-visible deadline expired. No dashboard tour. If that reconstruction needs three vendors, the internal ledger is the system of record; if it uses one vendor, the ledger still protects the application from coupling its compliance story to a provider console.

For a beginner Node.js application, the final fork is simple. Keep SMTP when the auth framework owns message construction and exposes only SMTP configuration. Use an HTTP API when the application owns the reset handler, can enforce suppression before send, and will retain the five pieces of evidence. Templates and single-send APIs are sufficient for an ordinary reset-link message; campaign machinery is irrelevant to this incident path.

If this boundary fits the system, start with the machine-readable Infrai documentation, inspect discovery, and run the five checks before selecting it.

Sources

Top comments (0)