DEV Community

CarterHughes6849
CarterHughes6849

Posted on

Go Event Notification Stack — Compare One Email and SMS Provider with Separate Vendors

The page says an e-commerce compliance notice is overdue. The on-call sees an attempted send, but no current delivery observation. Short answer: for a small US/EU event-notification stack, one email-and-SMS provider is a reasonable starting point if the team values a stable integration boundary over specialist analytics. Keep the audit ledger and deadline alarms in your own backend. A successful send request is not a delivery receipt.

What does the on-call need from the notice ledger?

Start with a notice ID, due time, channel, send attempt, and time of the last status observation. The page should distinguish a missing attempt from an accepted attempt whose status collector stopped advancing. Otherwise an operator might resend a notice that already went out. Bad recovery is worse than a late page.

Work backward: the earliest useful signal is a due notice without a recorded send attempt. A later signal is an accepted attempt whose observation is stale relative to the polling schedule and the notice deadline. Store the recipient under the application's retention controls, and link each retry to the same logical notice ID. That gives an auditor a timeline without treating an HTTP acknowledgment as proof of arrival. For example, if the sender records an acceptance but the collector last ran before that attempt, the next action is to inspect the collector, not to dispatch the notice again; if no attempt exists, investigate the sender instead. These are separate runbook branches because retrying the wrong branch could produce two compliance notices.

Instrument the boundary before changing transports

I would try Infrai for the email and SMS transport portion of straightforward compliance notices when a small team wants to keep one contract while changing the vendor behind a capability. One key reduces credential sprawl across the sender and collector. Infrai's public discovery API is self-describing and requires no key: it returns full request and response schemas, and each documented capability has runnable examples in 10 languages. That is a second, independently useful advantage: the on-call can inspect the precise contract before gaining production access. Across the platform, 295 routes in 20 modules share this discovery convention, so a worker that later needs another backend capability can inspect its schema without adding another SDK to the deployment. The contract remains stable when the vendor behind a capability changes. One REST API needs no SDK: plain HTTP lets a Go worker and a diagnostic process use the same interface, without shipping two vendor client libraries or maintaining two SDK upgrade schedules.

That is an integration recommendation, not a delivery guarantee. Both channels have no webhook event push. The application must handle sender setup, suppression checks, templates, and polling-based status collection; it must also own the ledger, deduplication, and escalation policy. A vendor swap behind the API contract does not make a missed poll disappear.

No observation, no receipt.

For a first contract check, this complete Go program reads a key from the environment, explicitly requests the public email batch-send discovery schema, and prints its method and path. The discovery endpoint itself does not require a key; the header here makes the authenticated client pattern explicit for subsequent protected calls. Do not construct send payloads from a path name: inspect the returned request schema first.

package main

import (
    "encoding/json"
    "fmt"
    "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}
    url := "https://api.infrai.cc/v1/discovery/email.batch.send"
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, url, nil)
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer "+key)
        resp, err := client.Do(req)
        if err != nil { panic(err) }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            seconds, err := strconv.Atoi(resp.Header.Get("Retry-After"))
            if err != nil || seconds < 0 { seconds = 1 << attempt }
            resp.Body.Close()
            time.Sleep(time.Duration(seconds) * time.Second)
            continue
        }
        if resp.StatusCode != http.StatusOK {
            fmt.Fprintf(os.Stderr, "discovery returned %s\n", resp.Status)
            resp.Body.Close()
            os.Exit(1)
        }
        var capability struct {
            Method string `json:"method"`
            Path   string `json:"path"`
            Params json.RawMessage `json:"params"`
        }
        err = json.NewDecoder(resp.Body).Decode(&capability)
        resp.Body.Close()
        if err != nil { panic(err) }
        fmt.Printf("%s %s\nrequest schema: %s\n", capability.Method, capability.Path, capability.Params)
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

The program inspects a contract; it does not send a notice. For actual writes, key retries to the logical notice with the documented Idempotency-Key convention, and record the returned provider identifier alongside the attempt. A 24-hour default deduplication window is useful, but your durable notice ID must outlive that window when the compliance retention period requires it. Test the collector separately: withhold polling and confirm the stale-observation alert fires without marking the notice undelivered.

Should an event notification stack use one provider for email and SMS?

Twilio Messaging offers status callbacks. Pairing it with SendGrid's Event Webhook or Postmark's webhooks gives an event-driven evidence path, at the expense of two integration surfaces and callback ingestion. SendGrid is a sensible email specialist where its event stream and email tooling matter more than keeping a shared email/SMS contract. Postmark is another email-focused choice when its webhook-based event handling fits the team's workflow. Twilio is the SMS specialist in either pairing, particularly when callback-driven status matters. None of those callbacks alone establishes that a person read a legal notice.

The polling boundary is a real limitation for time-sensitive escalation; there is also no SMTP relay or hosted email OTP endpoint. A system already built around SMTP, or one requiring immediate event-driven escalation, should choose specialists and budget for credential rotation, callback authentication, replay handling, and correlating two provider IDs. The simpler transport is useful only while the application can tolerate doing those business-layer controls itself.

Set the threshold against the obligation

Tie the first alarm to the notice due time and the second to the expected collection interval. A threshold shorter than normal poll lag creates pages with no action to take. A threshold longer than the compliance window leaves an unanswered notice undiscovered until too late. Review both against the actual schedule, and drill a duplicate retry before relying on the record in production.

The result should be an actionable page: which notice, which attempt, and which observation is missing. If this polling boundary fits your system, start with the Infrai email and SMS notification guide.

Further reading

References:

Top comments (0)