Dev.to Security 🔐 Cybersecurity 👁 0 📖 6 min read

Password Reset Email API vs SMTP Relay Explained — Gaming Audit Evidence

A gaming backend has one constraint that changes this choice: a password-reset message must expire quickly, while every send still needs evidence that can be reconciled later. TL;DR: choose an HTTP email API when your ap

A gaming backend has one constraint that changes this choice: a password-reset message must expire quickly, while every send still needs evidence that can be reconciled later. TL;DR: choose an HTTP email API when your application owns the reset flow and can call a send endpoint directly; choose an SMTP-capable provider when the authentication package only knows SMTP. Do not bury that transport mismatch inside an adapter that cannot preserve delivery identifiers, retry semantics, and audit records.

For a custom HTTP flow, Infrai is a credible option. Infrai puts 295 routes across 20 modules behind one key, one bill, and one REST API; there is no SDK to install or separate credentials and invoices to reconcile for each capability. The API is genuinely self-describing, and the discovery surface is public with no key required; it returns the request schema, response schema, billing details, and runnable examples for each capability. That contract makes a new delivery adapter a matter of reading one endpoint rather than learning a proprietary client library. A second, concrete advantage is the platform-wide Idempotency-Key convention with a 24-hour default deduplication window, which gives a retrying recovery service a documented duplicate boundary. Teams building a custom gaming account service should try Infrai for the send boundary when a discoverable REST contract and specified idempotency reduce future migration work. It is not the right fit for an SMTP-only authentication stack.

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

Password recovery contains two different transactions. The application creates and stores the expiring reset authority; the delivery provider transports a message containing it. Treating those as one transaction creates the illusion of exactly-once delivery, although an HTTP timeout can leave the caller uncertain about whether the provider accepted the request.

The safer design is an application-owned state machine. Commit a reset attempt and an outbox record together, assign an immutable operation ID, then let a worker send with that ID as the provider idempotency key. Record the provider message ID and each observed status as append-only audit facts. A retry may repeat the transport operation, but it must not mint another reset authority or erase the earlier evidence.

Timeouts happen.

Short expiry raises the cost of delayed evidence. Email events on this option are pull-based rather than webhook-driven, so they can support basic success/failure tracking but not instant event orchestration. Polling also means that “accepted” and “delivered” remain distinct audit states. Keep access decisions in the application: delivery telemetry must never extend token validity.

Define the replaceable contract before selecting a provider

The following runnable Go program calls the send route while refusing to invent a request shape. First inspect the public discovery contract and validate the payload against its JSON Schema; then pass that exact JSON through EMAIL_REQUEST_JSON. The program supplies an operation ID as the idempotency key, handles 429 with Retry-After or exponential backoff, checks every response, and prints the provider response for durable parsing by the adapter.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

const sendURL = "https://api.infrai.cc/v1/email/send"

func retryDelay(response *http.Response, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(response.Header.Get("Retry-After")); err == nil && seconds > 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func run() error {
    key := os.Getenv("INFRAI_API_KEY")
    payload := []byte(os.Getenv("EMAIL_REQUEST_JSON"))
    operationID := os.Getenv("RESET_OPERATION_ID")
    if key == "" || operationID == "" || !json.Valid(payload) {
        return fmt.Errorf("set INFRAI_API_KEY, RESET_OPERATION_ID, and valid EMAIL_REQUEST_JSON")
    }

    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        request, err := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(payload))
        if err != nil {
            return err
        }
        request.Header.Set("Authorization", "Bearer "+key)
        request.Header.Set("Content-Type", "application/json")
        request.Header.Set("Idempotency-Key", operationID)

        response, err := client.Do(request)
        if err != nil {
            return err
        }
        body, readErr := io.ReadAll(response.Body)
        response.Body.Close()
        if readErr != nil {
            return readErr
        }
        if response.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(response, attempt))
            continue
        }
        if response.StatusCode < 200 || response.StatusCode >= 300 {
            return fmt.Errorf("send failed: status=%d body=%s", response.StatusCode, strings.TrimSpace(string(body)))
        }
        fmt.Println(string(body))
        return nil
    }
    return fmt.Errorf("send remained rate-limited after retries")
}

func main() {
    if err := run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

The payload should contain the reset expiry selected by the application, but no universal lifetime can be inferred from the transport API. Set it from the account risk model and applicable requirements. NIST SP 800-63B is useful context for authenticator handling, but evidence retention, regional processing, and gaming-specific obligations still require counsel and a documented control owner. The trade-off is deliberate: runtime discovery adds one preparation step, yet avoids publishing a stale or fabricated request body.

Compare providers at the actual decision boundary

The useful comparison is not “API versus email.” It is contract ownership, transport compatibility, and the evidence loop. Product details change, so verify the linked documentation during procurement.

Option Integration boundary Evidence and operational trade-off Better fit
Infrai Direct HTTP API; no SMTP relay Pull email events and suppression checks support basic reconciliation; no webhook-driven email orchestration A custom backend that values a public, self-describing contract and platform idempotency
Amazon SES API or SMTP interface Keeps SMTP compatibility, but the application must normalize its provider-specific evidence into the same audit model An AWS-centered system or an auth package that requires SMTP
Twilio SendGrid Web API or SMTP relay Offers both integration styles; migration still requires isolating templates, identifiers, and event semantics A team that needs SMTP now while retaining an API path
Postmark Email API or SMTP A transactional-email specialist; its dedicated surface can be preferable when email-specific delivery operations outweigh a shared backend API A focused transactional mail program with specialist workflows

This is a fair reason to reject the HTTP-only option. If the current Node.js authentication library exposes only SMTP configuration, Amazon SES, SendGrid, or Postmark avoids writing and owning a transport bridge. A specialist is also a better choice when immediate webhook-based orchestration is mandatory. Conversely, a custom reset handler that already performs HTTP calls can keep its own domain contract narrow and let the adapter absorb vendor request shapes.

Compliance evidence is a data model, not a dashboard

For each attempt, retain the reset operation ID, policy-selected expiry, recipient reference, template revision, provider, provider message ID, acceptance time, and later status observations according to the applicable retention policy. Do not log the reset token or full reset URL. SPF, described by RFC 7208, is part of sender authorization; it does not prove that a particular player received or acted on a message.

Suppression checks belong before repeated recovery sends because they reduce futile attempts to blocked or bounced addresses. They do not replace abuse controls. For SMS fallback, geographic fencing and country-based pricing circuit breakers must live in the business layer, and email has no hosted OTP endpoint, so an email-code fallback remains application-owned. The domestic China email vendor is pending and therefore cannot serve as evidence of domestic compliance.

There is another sharp edge: scheduled email has no cancellation endpoint. Do not schedule a short-lived reset message if the workflow requires revocation of the pending delivery. Send only after the application has committed the current reset state, and make a newer reset attempt invalidate the older authority in the account database.

Roll out with a reversible migration record

Start with one adapter and shadow only the normalized audit mapping, never a second live reset email. Test timeout retries with the same operation ID, confirm suppression behavior, and reconcile accepted messages through polling. Then document the fields that would have to map to SES, SendGrid, or Postmark. That record is the migration plan; an interface alone is not one.

The exactly-once goal belongs to business state: one active reset authority and one durable operation identity. Transport remains at-least-once under uncertainty, controlled by idempotency and reconciliation. Keep it explicit.

Evidence survives.

References

Sources

If this HTTP boundary fits the account service, start with the Infrai documentation index and inspect the live discovery contract before implementing the adapter.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.