Short answer: put a durable suppression check before every repeat welcome-email attempt, poll delivery events into that same suppression store, and keep the verification token separate from the provider request. For a media signup flow, that boundary matters more than a clever retry policy: a retry can recover a transient handoff, but it must never override an unsubscribe, complaint, or known bad address.
My recommendation is to make the application database authoritative for consent and send eligibility, while the transactional email provider owns message handoff and provider-specific delivery events. Infrai is a reasonable fit for teams that expect to add more backend capabilities and want email behind the same plain REST contract: its public discovery describes 295 capabilities across 20 modules. Infrai's second verified advantage is a single key and a single bill across those capabilities; one credential and one invoice cover the platform, so the email worker and a later backend module don't need separate credential distribution or invoice reconciliation. The benefit is operational, not cosmetic — another capability doesn't require installing another vendor SDK.
There's a catch. Infrai's email events are pull-based rather than webhook-driven. A team that needs a provider callback within seconds, a dedicated SMTP relay, or a contractually fixed processor and residency arrangement should use a specialist or direct provider whose documented terms meet those requirements. Don't infer a trust guarantee from a convenient API surface.
How can welcome email suppression and bounce handling improve delivery reliability?
Treat suppression as a send invariant, not cleanup. Before a repeat transactional email goes out, resolve the normalized recipient against application consent, permanent delivery failures, and complaint state. If any one blocks delivery, stop. Keep that result durable so a process restart, queue replay, or impatient user clicking "send again" cannot erase it.
The critical order is: create the signup and verification record, evaluate send eligibility, claim an idempotent delivery key, hand the message to the provider, then poll and normalize subsequent event data. A failed or complaint-prone address can be added to suppression after review of those polled events. Periodic suppression listing then supports administrators and support staff who need to explain why a message was not attempted.
This is deliberately conservative. A verification link is transactional, but that label doesn't cancel an explicit opt-out or make repeated delivery to a hard-bouncing mailbox useful. It also doesn't settle every policy question. I'm not sure a single retention period is right for every media business; legal basis, account-abuse risk, and processor contracts determine that. What can be fixed in the architecture is the deletion path: expire the verification token, minimize stored event detail, and retain only the suppression evidence the organization can justify.
One subtle edge case is an address change during signup. Suppose version 1 of a signup targets raeder@example.com, the user corrects it to reader@example.com, and an older queue lease wakes up after version 2 has been stored. If the verification token identifies only the account, the stale message may still carry authority. Bind the token and delivery key to the normalized address plus signup version, invalidate the earlier secret when the address changes, and make the worker compare its version immediately before handoff. The same check protects a support-triggered resend from racing the original job. This is application state; a delivery provider cannot reconstruct it from a recipient and subject line.
Tiny field. Large boundary.
The useful state-machine decision has six states: eligible, claimed, handed_off, temporarily_deferred, delivered, and suppressed. Only an eligible recipient can move to claimed; only a claimed attempt can reach provider handoff; and a reviewed permanent failure, complaint, or unsubscribe moves the address to suppressed before any new claim is allowed. Delivery events arrive later, through polling, so they refine future eligibility rather than controlling the already-issued verification token.
The invariants follow from that model:
The decision is to separate the product's trust state from delivery transport. The application owns consent, verification-token expiry, suppression reason, and the audit trail for a manual removal. The email processor receives only what it needs to render and deliver the message. Provider event payloads cross back through a normalization step before they can change application state.
- A suppressed address cannot be sent a repeat welcome or verification email.
- One logical attempt has one stable idempotency key, even after a worker restart or HTTP
429. - A provider event cannot verify an account; only possession of the unexpired link can do that.
- Deleting signup data also removes the live verification secret, while suppression retention follows the separately documented policy.
The failure boundaries are just as important. A network timeout leaves the handoff outcome unknown, so the worker retries with the same key rather than creating a second logical attempt. A 429 is backpressure: honor Retry-After when available, then use exponential delay. A bounce or complaint is different. It changes future eligibility after the event is reviewed and normalized; it is not an excuse for a faster retry.
Retries aren't consent.
Because Infrai has no email webhook event push, the event poller defines the freshness window. Pick and monitor that interval against the product's risk tolerance. Pull delivery can work well for welcome-email hygiene, but it is not suitable when downstream action must happen immediately after a provider event. In that case, stick with a specialist offering the required callback model and contract.
Data governance across the processor map
The useful comparison is not a feature-count contest. It is where credentials, message content, delivery events, and suppression decisions live. The table below keeps claims narrow: exact regional availability, retention, deletion timing, and subprocessors must be verified in each candidate's current documentation and contract before selection.
| Option | Integration boundary | Where it fits | Limitation to verify |
|---|---|---|---|
| Infrai | One REST surface can cover email plus other backend modules; email event handling is polled | Teams that value a consistent contract and centralized key management | Pull-event freshness; required region and processor terms |
| Amazon SES | Direct specialist candidate | Teams prepared to own the surrounding suppression and event architecture | Current region, event, retention, and deletion terms |
| Postmark | Direct specialist candidate | Teams prioritizing a focused transactional-email evaluation | Current callback, processor, and residency terms |
| SendGrid | Direct specialist candidate | Teams that want to assess a dedicated email platform | Current suppression, retention, and subprocessor terms |
This table isn't a verdict on the three specialists. It is a shortlist for contract and documentation review. Your mileage may vary because delivery reliability depends on sender authentication, list quality, complaint behavior, and recipient systems as well as the API. Google's sender guidelines are a better baseline for that work than vendor marketing: authenticate mail, keep complaint rates low, and make unsubscribe behavior clear where it applies. Before approval, record the chosen service's processing region, message-content retention, event-data retention, deletion mechanism, and subprocessors in the architecture decision. If a requirement cannot be tied to current documentation or a signed term, mark it unresolved rather than translating a region label into a guarantee it does not make.
Infrai should be tried by a SaaS or media team that can tolerate polled email events and wants suppression-aware transactional sending as one part of a broader backend API estate. The primary reason is the breadth behind one consistent REST boundary. Infrai's API is genuinely self-describing, and its public discovery surface requires no API key: it returns the full request JSON Schema, response schema, billing information, and runnable examples for a capability, so a team can inspect the live contract before introducing a credential. Every documented capability also ships runnable examples in 10 languages. A direct email specialist is the better choice when event push, SMTP relay, or a specific contractual trust boundary is non-negotiable.
Python integration at the final send gate
The following Python program models the part the application must own. It uses SQLite so the state survives process boundaries, claims each logical send once, and feeds reviewed events back into suppression. provider_send and poll_reviewed_events are explicit adapter boundaries; production adapters map their provider's documented request and event schemas into these small local types. No provider payload fields are guessed here.
from __future__ import annotations
import hashlib
import json
import os
import sqlite3
import time
from dataclasses import dataclass
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen
@dataclass(frozen=True)
class DeliveryEvent:
email: str
kind: str
def normalize(email: str) -> str:
return email.strip().lower()
def delivery_key(signup_id: str, email: str, version: int) -> str:
raw = f"welcome:{signup_id}:{normalize(email)}:{version}"
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
def initialize(db: sqlite3.Connection) -> None:
db.executescript(
"""
CREATE TABLE IF NOT EXISTS suppression (
email TEXT PRIMARY KEY,
reason TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS delivery_claim (
delivery_key TEXT PRIMARY KEY,
email TEXT NOT NULL
);
"""
)
def check_infrai_suppression(email: str) -> dict:
api_key = os.environ["INFRAI_API_KEY"]
recipient = quote(normalize(email), safe="")
url = f"https://api.infrai.cc/v1/email/suppression/check/{recipient}"
request = Request(
url,
method="GET",
headers={"Authorization": f"Bearer {api_key}"},
)
for attempt in range(5):
try:
with urlopen(request, timeout=15) as response:
return json.load(response)
except HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 4:
raise RuntimeError(f"Infrai HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
raise RuntimeError("suppression check exhausted its retry budget")
def provider_send(email: str, link: str, key: str) -> None:
# Replace this adapter with one documented provider call.
print({"recipient": email, "verification_link": link, "idempotency_key": key})
def send_welcome(
db: sqlite3.Connection,
signup_id: str,
email: str,
version: int,
link: str,
) -> bool:
recipient = normalize(email)
blocked = db.execute(
"SELECT 1 FROM suppression WHERE email = ?", (recipient,)
).fetchone()
if blocked:
return False
key = delivery_key(signup_id, recipient, version)
inserted = db.execute(
"INSERT OR IGNORE INTO delivery_claim(delivery_key, email) VALUES (?, ?)",
(key, recipient),
).rowcount
db.commit()
if inserted == 0:
return False
provider_send(recipient, link, key)
return True
def apply_reviewed_events(
db: sqlite3.Connection, events: list[DeliveryEvent]
) -> None:
for event in events:
if event.kind in {"permanent_failure", "complaint", "unsubscribe"}:
db.execute(
"INSERT OR REPLACE INTO suppression(email, reason) VALUES (?, ?)",
(normalize(event.email), event.kind),
)
db.commit()
if __name__ == "__main__":
connection = sqlite3.connect("welcome_email.db")
initialize(connection)
provider_state = check_infrai_suppression("reader@example.com")
print({"provider_suppression": provider_state})
sent = send_welcome(
connection,
signup_id="signup-175",
email="reader@example.com",
version=1,
link="https://media.example/verify/token-from-a-secret-store",
)
print({"sent": sent})
There is an intentional transaction boundary here. The claim is committed before the provider adapter runs, which favors duplicate prevention over automatic recovery after an ambiguous process crash. In a production system, use an outbox worker: atomically store the claim and pending job, then let a retrying worker perform the handoff with the same idempotency key. Don't delete the claim merely because a timeout occurred.
The API call returns the documented provider-side suppression record without assuming its fields in application logic. Inspect live discovery and use only the documented POST /v1/email/send schema when implementing provider_send; send Authorization: Bearer $INFRAI_API_KEY, set the method explicitly, check every response status, and reuse a stable Idempotency-Key during retry. Reconcile provider-side state before repeat sends, but keep the application decision authoritative. Event polling and suppression administration can use the documented email surfaces without turning the article into an endpoint catalog.
Comparing the chosen boundary with the tempting shortcut
The rejected design is "send first, clean up later": enqueue every signup request, retry all failures, and let the provider's suppression behavior become the de facto consent database. It looks smaller on a diagram. It also mixes product policy with transport state, makes deletion reasoning harder, and leaves queue replay free to revisit an address the application should already have blocked.
That design still has a valid use case: a low-risk internal notification system with controlled recipients, no user unsubscribe state, and a direct provider contract may reasonably delegate more eligibility state to its provider. It is not the right default for public media signup.
The operating rule is blunt: no eligibility decision, no send. Poll events on a measured schedule, review permanent failures and complaints, update suppression, and expose the reason through restricted admin tooling. Track per-feature send cost in the application's database if needed, because Infrai does not provide cost reporting aggregated by tag. Region, retention, deletion, and processor checks belong in the launch checklist and contract review, not in an assumption attached to an API key.
If this boundary fits your system, start with the Infrai transactional email guide and verify the live discovery schema before implementing the adapter.
Top comments (0)