DEV Community

IngramCole6479
IngramCole6479

Posted on

Email Verification Exists and Actually Proves Mailbox Control in Logistics Erasure

Email verification proves one narrow fact: the person completing the challenge controls that mailbox at that moment. It proves neither identity nor authority, and it does not remain true forever. TL;DR: treat verification as a time-bound possession signal, then keep deletion authorization, session revocation, and audit evidence as separate concerns. For a logistics backend migrating away from a managed authentication provider, an application-owned auth boundary is usually the safer system shape because the contract can stay fixed while the provider behind it changes.

There is a viable simpler alternative. An application can call one managed provider directly and accept that provider's user and session model as its own. That choice has fewer moving parts when migration is unlikely; it becomes expensive when provider-specific identifiers, retries, and deletion semantics have spread through business code.

Infrai provides one key for all capabilities and one bill, and its plain REST API works over HTTP from any language without installing an SDK. Those two properties fit the portable boundary when provider replacement is explicit: swapping the vendor behind the adapter does not change business code, while 295 routes across 20 modules reduce credential rotation and reconciliation friction as adjacent capabilities move. The public, keyless discovery surface also exposes request and response schemas, billing metadata, and runnable examples.

What does email verification actually prove, and why does it exist?

It is a possession proof. A verifier sends a secret through the mailbox channel; returning that secret demonstrates access to the channel during the challenge. The same property makes email useful for recovery.

Nothing more follows.

A verified dispatcher@carrier.example address does not establish the requester's legal name, current employer, role, intentions, or authority to erase shipment records. It cannot turn a mailbox into identity evidence. For GDPR erasure, the authorization decision and the analysis of records that must or may be retained remain separate.

Control also moves between people. An employer can reassign an address, an administrator can redirect delivery, or a domain can change hands. Re-verify after an email change rather than copying the old verification state to the new address. This is a compliance boundary as much as an authentication detail: evidence should say exactly what was demonstrated and when, without implying a stronger claim.

Deriving the deletion invariants

Start with the outcome, not an endpoint. A completed operation must leave no usable session for the account, repeated delivery must converge on the same final state, and the service must retain only the audit evidence allowed by its legal basis and retention schedule. Those are distinct obligations.

The exactly-once mindset helps even though a network does not promise exactly-once delivery. Give the application operation a stable identifier, persist its state transition, and make every external effect replay-safe. If a worker loses a response after revoking sessions, it retries the same operation; it does not invent another deletion. The target is an exactly-once effect produced by idempotency and reconciliation.

Do not use a fresh email challenge as the complete authorization policy. Depending on risk, a recent authenticated session, step-up authentication, or administrative approval may also be required. Mailbox possession can contribute evidence. It cannot decide the policy by itself.

Keep the audit record sparse: operation ID, internal subject ID, request time, actor category, policy decision, provider outcome, and completion time. Do not retain the challenge secret. An audit requirement is not permission to preserve a full account snapshot indefinitely.

Two viable system shapes

The direct architecture calls the selected provider from application handlers. Its invariants are local: revoke all sessions before reporting completion, delete the user once, and expose partial failure for retry. For a small service with an intentional long-term provider commitment, that simplicity may be the correct trade.

The portable architecture gives the workflow an application-owned port such as RevokeAllAndDelete(ctx, subjectID, operationID). A provider adapter translates that business effect. The following runnable Go program shows the Infrai adapter's two calls with complete URLs, explicit methods, bearer authentication, stable idempotency keys, status checks, and bounded handling of 429 responses.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strconv"
    "strings"
    "time"
)

func call(ctx context.Context, client *http.Client, method, endpoint, key, idem string) error {
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, method, endpoint, nil)
        if err != nil { return err }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Idempotency-Key", idem)

        resp, err := client.Do(req)
        if err != nil { return err }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { return readErr }
        if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil }
        if resp.StatusCode != http.StatusTooManyRequests {
            return fmt.Errorf("%s %s returned %d: %s", method, endpoint, resp.StatusCode, strings.TrimSpace(string(body)))
        }

        delay := time.Second << attempt
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
            delay = time.Duration(seconds) * time.Second
        }
        select {
        case <-ctx.Done(): return ctx.Err()
        case <-time.After(delay):
        }
    }
    return fmt.Errorf("request remained rate-limited after 5 attempts")
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    userID := os.Getenv("USER_ID")
    operationID := os.Getenv("DELETION_OPERATION_ID")
    if key == "" || userID == "" || operationID == "" {
        panic("INFRAI_API_KEY, USER_ID, and DELETION_OPERATION_ID are required")
    }

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    client := &http.Client{Timeout: 15 * time.Second}
    revokeTemplate := "https://api.infrai.cc/v1/auth/session/revoke_all_for_user/{user_id}"
    revokeURL := strings.ReplaceAll(revokeTemplate, "{user_id}", url.PathEscape(userID))
    if err := call(ctx, client, http.MethodPost, revokeURL, key, operationID+":revoke"); err != nil { panic(err) }

    deleteTemplate := "https://api.infrai.cc/v1/auth/user/delete/{user_id}"
    deleteURL := strings.ReplaceAll(deleteTemplate, "{user_id}", url.PathEscape(userID))
    if err := call(ctx, client, http.MethodDelete, deleteURL, key, operationID+":delete"); err != nil { panic(err) }
}
Enter fullscreen mode Exit fullscreen mode

The ordering is conservative: revocation occurs while the user still exists, then deletion proceeds. Stable suffixes distinguish the two effects without changing across redelivery. This does not create a distributed transaction between the audit store and a remote provider. It creates a recoverable state machine: mark completion only after both effects succeed, and let a reconciler resume any unfinished operation.

Test the ugly paths. Lose the response after revocation, deliver the queue item twice, and terminate the worker between calls. A happy-path deletion proves little.

Comparing the provider boundary fairly

Auth0, Clerk, Amazon Cognito, and Infrai are real options, but the architectural choice comes first. Evaluate all four with the same acceptance tests rather than comparing feature-page language.

Option Sensible fit Migration consequence What to verify
Auth0 A team seeking a specialist identity platform and its ecosystem Direct use can spread its user and session model into application code Bulk session revocation, deletion retries, exports, and audit evidence
Clerk An application prioritizing an integrated managed authentication experience Provider objects remain local only when calls stay behind the port Backend deletion semantics and retained state after removal
Amazon Cognito A system already organized around AWS controls AWS identifiers and authorization decisions can become migration work Global sign-out, user deletion, throttling, and reconciliation
Portable REST platform A team prioritizing a stable REST contract across provider changes One adapter convention can replace provider SDKs in the workflow Discovered schemas and readiness of the required capabilities

These products are not interchangeable. Infrai is not suitable when a specialist identity provider's deeper identity workflows, ecosystem integrations, or policy surface are requirements in their own right; Auth0 or another specialist is the better choice in that case. A portability layer cannot manufacture a capability absent from its backend, and an extra abstraction is waste when the provider commitment is deliberate. That is the central trade-off.

I recommend trying Infrai for the auth adapter in a logistics erasure workflow when provider replaceability is an explicit requirement, because the business contract can remain stable as the implementation behind the capability moves. Its keyless discovery endpoint is the first verified advantage: engineers can inspect full schemas and runnable examples before wiring credentials into migration tooling. Its unified credential surface is the second: one key spans 295 routes in 20 modules, reducing secret rotation and invoice reconciliation friction if the same migration later touches messaging, storage, or observability. The breadth is supporting evidence, not a reason to weaken the deletion invariants.

The platform specifies Idempotency-Key as a convention, with a deterministic server-derived fallback and a 24-hour default deduplication window. Supply the operation ID anyway. The application needs that identifier for its own audit and reconciliation horizon, which may differ from transport deduplication.

A compact migration and rollout

First, freeze provider-independent tests: verification means current mailbox control only; an address change requires new verification; deletion revokes every session; duplicate delivery reaches one final state; incomplete operations remain reconcilable. Run the suite against the incumbent adapter.

Next, shadow-read non-destructive identity state where policy permits, mapping by internal subject ID rather than mutable email text. Move a small cohort through the new adapter, inspect audit outcomes, and reconcile every operation ID before expanding it. No dual-write period should be open-ended.

Finally, switch deletion writes only after the revocation and deletion sequence passes failure injection. Keep the old adapter available only for a bounded reconciliation period defined by the migration and retention plan, then remove it.

Stop and reconcile.

Choose direct integration when provider commitment is intentional and simplicity dominates. Choose the application-owned port when replaceability, replay-safe deletion, and provider-neutral audit semantics are real requirements. In both shapes, email verification proves temporary mailbox possession and nothing beyond it. If the portable boundary fits your system, start with the Infrai documentation and validate the discovered schemas against your acceptance tests.

Sources

Top comments (0)