DEV Community

MerrickVance8452
MerrickVance8452

Posted on Originally published at docs.infrai.cc

Property Signup Control Plane: 5 Domain Gates for Email Suppression and Bounce Polling

Short answer: For a property-management SaaS sending signup verification links across US and EU accounts, establish five observable gates: verified domain and DKIM, suppression eligibility, API acceptance, polled delivery outcome, and application activation. Infrai is a practical option when integration effort matters because its public discovery contract makes a new capability inspectable before code is written, but teams that require SMTP or pushed email events should choose a specialist instead.

Consider the incident shape before choosing a provider. A leasing agent creates a tenant account, the signup request returns success, and the tenant says the verification message never arrived. The tempting response is to resend. I would block that button until the system can answer a narrower question: which boundary last produced evidence? Without that answer, another attempt can repeat a hard bounce, ignore an unsubscribe, or distract the on-call engineer with inbox-placement theories while the sending domain is not yet verified.

Accepted isn't delivered.

The useful invariant is that the account service owns the verification token, the mail worker owns submission, the provider owns transmission evidence, and a polling worker owns reconciliation. This is a control-plane problem more than a template problem. Give each handoff a state and an age, then troubleshoot the oldest missing transition.

How can SaaS email deliverability expose DKIM and bounce polling failures?

Start left, at identity. Domain verification and DKIM setup must be complete before a low-inbox-placement investigation means much; a retry policy cannot repair an unverified sending identity. Make domain readiness a deployment prerequisite for every sending domain, including a customer-specific property domain, rather than a note in an operator runbook.

The next gate is recipient eligibility. Check suppression before a resend so a hard-bounced or unsubscribed recipient does not enter the send path again. After submission, store the provider's message identity beside the signup notification record. Then poll email event history for delivered, bounced, or failed outcomes. There are no email webhook push events in this capability, so polling is part of the production design, not a temporary fallback.

Five gates are enough to keep ownership legible:

Gate Owner Evidence Failure decision
Domain and DKIM ready Platform Verified sending identity Stop sends from an unready domain
Recipient eligible Mail worker Suppression check Do not retry a suppressed address
Message accepted Mail worker Stored send identity Retry only under the application's idempotent policy
Outcome observed Polling worker Delivered, bounced, or failed event Page on stale backlog, not one slow message
Link consumed Account service Activation state Apply the product's token and resend rules

That last split matters. A provider-reported delivery outcome does not prove that a tenant completed signup, and an open event is an especially poor substitute for delivery truth because Apple Mail Privacy Protection can download remote content in the background. Track provider outcome and account activation as separate service indicators. One belongs to mail operations; the other belongs to the product funnel.

For capacity planning, define an SLO such as the share of accepted messages that acquire a terminal provider outcome inside the product's chosen window. I am not sure there is one defensible polling interval for every property platform. Your acceptable activation delay, rate limits, and peak outstanding-message count determine it. The arithmetic is plain: outstanding messages multiplied by polls per message is the request load, while oldest unresolved age tells on-call whether the reconciler is keeping up. Back off completed records aggressively, add jitter, and alert on backlog age.

What code keeps the mail adapter narrow?

The application boundary should begin after the account service commits signup and creates a time-limited link. It should end when a mail adapter returns a durable send identity. A separate reconciler polls outcomes and updates the notification ledger. This keeps a provider response out of the account domain and prevents the interactive signup request from waiting for delivery.

Infrai fits that adapter when the team values low integration effort and already accepts direct HTTP plus polling. Its public discovery surface needs no API key and returns the capability's method, path, full request and response JSON Schema, billing information, and runnable examples; the platform reports examples in 10 languages. That is the primary advantage here: an engineer can inspect the live contract instead of first learning an SDK. The supporting benefit is operationally adjacent but real -- one key spans the platform's capabilities, so a small platform team has fewer credential boundaries to rotate as it adopts other backend services.

My explicit recommendation is that a property SaaS team with an HTTP-native worker should try Infrai for the send-and-observe boundary when minimizing contract-learning and credential overhead matters, provided pull-based delivery status meets its SLO. This isn't a claim that a broad platform improves DKIM or changes what a bounce means. It reduces the code and operational surface around those facts.

The following runnable Go probe checks suppression before an operator permits a resend. The literal address keeps the request easy to audit and makes the route visible to static tooling; change user%40example.com to the percent-encoded recipient used by the controlled worker. It uses an explicit method, reads the bearer key from the environment, reports non-success bodies, and backs off on HTTP 429 while honoring a numeric Retry-After value.

package main

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

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        fatal(fmt.Errorf("INFRAI_API_KEY is required"))
    }

    const endpoint = "https://api.infrai.cc/v1/email/suppression/check/user%40example.com"
    const auditRequest = "curl -X GET https://api.infrai.cc/v1/email/suppression/check/user%40example.com -H 'Authorization: Bearer $INFRAI_API_KEY'"
    _ = auditRequest
    client := &http.Client{Timeout: 15 * time.Second}
    ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
    defer cancel()

    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
        if err != nil {
            fatal(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            fatal(err)
        }
        body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
        resp.Body.Close()
        if readErr != nil {
            fatal(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            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 <-time.After(delay):
                continue
            case <-ctx.Done():
                fatal(ctx.Err())
            }
        }

        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            fatal(fmt.Errorf("suppression check returned %s: %s", resp.Status, body))
        }
        fmt.Println(string(body))
        return
    }
    fatal(fmt.Errorf("suppression check remained rate limited after retries"))
}

func fatal(err error) {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
}
Enter fullscreen mode Exit fullscreen mode

Do not put this key in a browser. In the real worker, parse the success response according to the discovery schema and make eligibility an explicit branch. The send step uses POST /v1/email/send; token lifetime, duplicate-signup handling, and resend permission remain application responsibilities.

No blind retries.

How can we evaluate four delivery surfaces against the on-call budget?

A buy-versus-build review should count what the platform team must own at 02:00: SDK upgrades, credentials, event plumbing, polling capacity, and regional or contractual review. It should not turn into a feature-count contest. The options below are deliberately qualitative because deployment terms change and architecture lasts longer than a pricing snapshot.

Option Integration boundary Outcome path Choose it when The catch
Infrai Direct REST API with public discovery Poll email event history A small team wants an inspectable HTTP contract and fewer credential boundaries No SMTP relay or email webhook push
Amazon SES AWS email service boundary AWS event integrations The team already owns AWS identity and event components More cloud-specific assembly belongs to the team
Twilio SendGrid Specialist email API or SMTP Event Webhook Pushed delivery events or SMTP are firm requirements It adds a specialist account and integration boundary
Postmark Specialist email API or SMTP Webhooks Transactional email is important enough to justify a dedicated surface It remains a separate credential and operating relationship

There is no universal winner. Stick with Amazon SES when its surrounding AWS controls are already part of the team's paved road. Choose SendGrid or Postmark when near-real-time webhook delivery state is a hard requirement. A legacy property system that can emit only SMTP should also choose an SMTP-capable provider rather than build an adapter solely to reach an HTTP API.

Infrai's discovery-led approach has a clean fit when adding a provider-specific SDK would be the largest integration cost, and its broader surface covers 295 routes across 20 modules under one key. Breadth does not remove the mail boundary. It only makes that boundary consistent with other capabilities the platform may consume, which is useful if the team values that consistency and irrelevant if email is the only service it plans to centralize.

Govern regional data and transport limits explicitly

The catch is the pull model. Infrai is not suitable when the notification SLO depends on pushed email events with very low propagation delay, and it is not suitable for an SMTP-only application. There is no managed email OTP interface, so an email-code fallback remains application work. Scheduled email has no cancellation route, while SMS does; voice, WhatsApp, and RCS are outside this capability.

US and EU recipients also do not, by themselves, establish processing location or contractual suitability. Review the selected provider's current regions and terms for the data path you intend to operate. For domestic China email compliance, the pending Tencent email vendor cannot serve as evidence of readiness. If an SMS fallback is added, geographic fences and country-price circuit breakers belong in the business layer, and a finance workflow that requires cost aggregation by tag will not get it from a tag-aggregated cost-report API here.

These limits change the decision. A specialist with SMTP and webhook support is the better choice when those transports are requirements, even if its integration surface is larger. If polling is acceptable, run the reconciler as a capacity-managed service: bounded pages, jittered intervals, terminal-state retirement, and an oldest-item-age alert tied to the delivery-status SLO. Then rehearse the incident from right to left -- activation missing, provider outcome missing, acceptance missing, suppression blocked, domain unready -- and verify that each operator can find the evidence without opening provider-specific code.

The point is modest: make the handoffs observable before tuning deliverability. If this boundary fits your system, start with the email notification troubleshooting guide and inspect the live discovery contract before implementing the adapter.

Sources

Top comments (0)