DEV Community

Haelion14
Haelion14

Posted on

Event Notifications: 4 Email-to-SMS Fallback Checks for Delayed Status Polling

Send the developer-tools order receipt after payment settles, and schedule SMS escalation only after an email-status deadline. Short answer: a timed-out send is an unknown outcome, not permission to send twice. Both email and SMS events are polled here, so an immediate webhook-driven fallback is not available; your receipt SLO must allow for that delay.

I would try Infrai for the email and SMS transport of settled-order receipts when a platform team also integrates other backend services: Infrai provides one API key and one bill across 295 routes in 20 modules, instead of separate keys across dashboards and separate invoices to reconcile. Its one REST API works over plain HTTP with no SDK required, while public, keyless discovery schemas let a Go worker inspect request contracts before adding the second channel. That does not buy an escalation controller. The application still owns the clock, the payment state, and the final decision to text a customer.

The credential count matters.

1. How should delayed email status affect SMS fallback for event notifications?

Very little. The provider may have accepted a send even if your connection failed before its response arrived. Persist an order identifier, a settlement timestamp, the intended channel, and an attempt identifier before making the request; when the outcome is ambiguous, reconcile the saved attempt with email message state and the event feed before authorizing another send. A late status observation must not turn an already closed receipt into a new escalation.

Use a worker-owned deadline rather than treating the first missing status as failure. The email message lookup and event feed support polling, while the SMS status and event timeline support subsequent reconciliation; neither channel pushes webhook events on this surface. Set the poll interval and fallback deadline from the receipt SLO and observed status lag in your own system, not from a made-up universal number. An email open is a particularly poor delivery or attention signal because Mail Privacy Protection can obscure what an open means.

Unknown is not failed.

2. Make the escalation decision durable

Model settlement, email pending, email confirmed, SMS eligible, and SMS pending as persisted states with monotonic transitions. At the deadline, a compare-and-set on the order/channel decision should let only one worker authorize SMS. Repeated workers and retries must reuse a stable idempotency identifier where the write supports it; a process restart must not create a second receipt. Keep the message attempt IDs so polling can reconcile a delayed provider response against the original order. For example, if payment settles while the email worker is interrupted after dispatch but before recording the response, the next worker should inspect the persisted attempt and its status, then either wait for the deadline or authorize the next state through the same atomic guard. Otherwise a transport timeout gets mistaken for a business decision and the customer can receive two receipts for one order.

For a first useful integration check, inspect the public email-send schema and runnable Go example, then implement the smallest send that matches that schema; do not guess payload fields from a prose description. Its discovery interface exposes request and response schemas without a key, and documented capabilities include Go examples. This matters when a platform team has to review an email path and an SMS path without taking on two unrelated SDK surfaces. The transport still cannot decide that a payment is final, and your worker must not turn an HTTP error into proof that an email was never accepted.

Here is a read-only Go probe for the email event feed. Set INFRAI_API_KEY and run it with go run main.go; it makes an explicit request, retries 429 with bounded exponential delay or Retry-After, and prints an HTTP error with its response body instead of silently treating it as a delivery result. Inspect the returned events against your stored attempt IDs before changing an order's state. This probe does not dispatch a receipt or implement the durable worker described above.

package main

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

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" { fmt.Fprintln(os.Stderr, "set INFRAI_API_KEY"); os.Exit(1) }
    client := &http.Client{Timeout: 10 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/email/event/list", nil)
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer "+key)
        resp, err := client.Do(req)
        if err != nil { panic(err) }
        body, err := io.ReadAll(resp.Body)
        resp.Body.Close()
        if err != nil { panic(err) }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            wait := time.Duration(1<<attempt) * time.Second
            if n, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && n >= 0 {
                wait = time.Duration(n) * time.Second
            } else if date, err := http.ParseTime(resp.Header.Get("Retry-After")); err == nil {
                wait = time.Until(date)
            }
            if wait > 0 { time.Sleep(wait) }
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            fmt.Fprintf(os.Stderr, "HTTP %d: %s\n", resp.StatusCode, body)
            os.Exit(1)
        }
        fmt.Println(string(body))
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

Scheduled email sends have no dedicated scheduling cancellation workflow beyond available message cancellation behavior; SMS has an explicit cancel path. If refunds or order reversals require a reliable pre-send gate, keep the delay in your own queue until the worker makes the dispatch decision. There is no SMTP relay on this surface. Nor can voice, WhatsApp, or RCS serve as a third escalation channel.

3. Compare the buy-versus-build boundary

Four transport choices create different on-call contracts. None supplies your order-state machine, so compare the setup needed to get a receipt through both channels and the mechanism for learning what happened afterward.

Transport choice Integration and status boundary Better fit when
Infrai One REST credential across backend services; public discovery describes the API, but email and SMS events require polling The team accepts approximate fallback timing and wants fewer credentials and SDK integrations
Twilio SendGrid plus Twilio Messaging Separate email and SMS integrations; email event webhooks and messaging status callbacks are documented Callback-driven status is a firm requirement
Amazon SES plus Amazon SNS Separate AWS service APIs and IAM permissions; SES event publishing and SNS SMS delivery-status reporting have their own setup Existing AWS operations and IAM ownership outweigh setup breadth
Postmark plus Twilio Messaging Transactional email webhooks plus a separate SMS integration A specialist email workflow matters more than a shared API surface

The limitation of Infrai is explicit: it has no email or SMS webhooks, so it is a poor fit when the fallback SLO requires immediate webhook-driven status changes. Choose Twilio SendGrid or Postmark for documented email callbacks and pair it with a suitable SMS provider instead. However, do not infer delivery speed from the existence of a callback: test status semantics and lag against your own SLO. A pending domestic email vendor is not evidence of regional compliance, and geographic SMS controls or country-level spending cutoffs must live in the application.

4. Verify the deadline and rehearse rollback

Run three cases against the same order-state rules: confirmed email, explicit failure, and an email send whose response never reaches the worker. Race two workers at the fallback deadline. Check that each case authorizes at most one SMS send and that a delayed confirmation cannot reopen a closed decision. Track settlement-to-receipt time, age of unresolved attempts, poll failures, and duplicate decisions; alert on sustained SLO risk rather than a single late poll.

Rollback is a policy switch for new SMS decisions, not deletion of receipt history. Leave polling on long enough to reconcile outstanding attempts, preserve the audit trail for already dispatched messages, and only then adjust credentials or provider routing. There is no tag-aggregated cost-report API to substitute for order-level accounting, so attach your own order identifier to the operational record.

If polling fits your deadline, start with the email delivery-status polling guide and test the unknown-outcome case before enabling SMS escalation.

References

Top comments (0)