23 years from Stockholm
Operator notes · From the Stockholm desk

Email API for SaaS products and event-driven workflows

A technical editorial guide for teams evaluating infrastructure, delivery trade-offs and the operational boundary around this category.

There is a specific moment in a SaaS product's life when SMTP starts feeling wrong. The product wants to treat email as a first-class feature — events, templates, tenants, analytics, retries — and the SMTP contract is pushing back on all of that. That moment is the real decision point for moving to an Email API, and it is worth understanding clearly rather than reflexively. The question is not whether HTTP is more modern than SMTP. It is whether your product has genuinely absorbed email into its data model, or whether SMTP is still the right abstraction for what the product actually needs.

Key takeaways

  • An Email API is the natural fit when your product treats messages as events — each send has an ID, a tenant, metadata and a webhook stream that feeds back into product analytics.
  • The four capabilities that justify the integration cost are: first-class events (delivered / bounced / opened / clicked / complained / unsubscribed), idempotency, server-side templating, and per-tenant suppression.
  • Multi-tenant SaaS is where API-based sending earns its keep. Per-tenant bounce data, per-tenant suppression lists and per-tenant sending identities are awkward over SMTP and natural over HTTP.
  • Webhook handlers must be idempotent and signature-verified. These are not optional — they are the contract with the provider's at-least-once delivery guarantee.
  • An Email API does not replace SMTP relay; it complements it. Most mature SaaS companies end up running both — API for product-owned sending, relay for systems they will not rewrite.

Why Email API earns its place

The case for an Email API is not "HTTP is better than SMTP." It is "our product has absorbed email." When a SaaS product sends a password reset, it does not want to hand the message to a transport layer and forget about it. It wants to know that the message was accepted, it wants to know when the user opened it, it wants to correlate that event with the authentication flow that triggered it, and it wants the tenant boundary to be visible throughout. That is a data model, not a protocol choice.

Once a product is in that state, SMTP starts feeling like a compatibility shim. The send call returns a three-digit status code and nothing else. Events about the message arrive via a separate pipeline that has to be bolted on. Metadata about the tenant and trigger gets encoded into custom headers that each provider parses differently. Idempotency is the application's problem, not the transport's. Each of those gaps is solvable over SMTP, but solving them means reimplementing what a modern Email API already provides as part of the contract.

The alternative framing — the one that keeps the decision honest — is workload-first. Look at how your product already behaves and ask which transport fits that shape. Products that send "fire and forget" messages tied to low-level operational events (an admin alert, a cron job notification) are happy over SMTP. Products that treat email as part of the user experience (a welcome flow, an onboarding sequence, a usage alert that drives in-app state) want the API. The answer is usually obvious once the question is framed that way.

An Email API is not a replacement for SMTP. It is a different contract that fits products whose data model already includes email events. If the product's data model does not include email events, the API integration adds friction without adding value. — A framing worth stealing for your next architecture review

What "event-driven" means for email in practice

The phrase "event-driven" gets overused, so it is worth defining. In the context of a SaaS product that sends email, event-driven means four things, and all four tend to travel together:

The four pillars of event-driven email in a SaaS product
PillarWhat it means operationallyWhy SMTP is awkward for it
Triggered by product eventsA state change in the application publishes an event; a worker consumes it and sends the messageWorks, but the application has to build its own event bus between its logic and SMTP
Traced by message identifierEvery send has a stable ID that persists across the application, the provider and the analytics layerSMTP message IDs are provider-specific; the application has to maintain its own correlation map
Enriched with metadataEach message carries tenant, feature, flow, session — structured data for downstream analyticsRequires encoding metadata into custom headers with provider-specific parsing conventions
Closed by downstream eventsDelivered, opened, clicked and bounced events feed back into the application's stateSeparate webhook infrastructure that each provider surfaces differently

Each pillar is individually trivial to approximate over SMTP. Together, they add up to a substantial amount of glue code that the Email API absorbs as part of its contract. The fourth pillar in particular — the closed loop from send to downstream event — is the one that most often tips the architectural decision toward the API. For the deeper treatment of how to design transactional workflows around application events rather than protocol mechanics, see designing transactional email workflows around application events.

Event-driven email flow in a SaaS product How a product event becomes an email with feedback Each step handed off with typed data and message IDs that persist through the system Product event published (user.password.reset) Worker renders template, calls API Email API accepts, queues, returns msg_id Mailbox accepts / bounces / opened / clicked Webhook → Product analytics event.delivered, event.opened, event.bounced
The closed loop from product event to analytics is the shape that Email APIs are built to handle. SMTP can approximate each piece, but the provider handles the whole pipeline when you integrate through the API.

The provider landscape in late 2022

The Email API market has stabilized over the last five years into a small set of serious options, each with a different position. The table below is a working summary of what we have seen teams choose and why, as of late 2022.

Email API provider landscape, late 2022 — positioning, not pricing
ProviderSweet spotWhat they are known forWhere teams grow out of them
SendGrid (Twilio)High-volume marketing plus transactional in one vendorVolume capacity, long event history, marketing featuresDeliverability variance on shared pools at growth phases
Postmark (ActiveCampaign)Transactional-only, inbox-placement focusedBest-in-class shared IP reputation; Message Streams for separationNot the right shape for bulk marketing; intentional limits
Mailgun (Sinch)Developers wanting granular routing and validationFive-API architecture (send, validate, templates, events, analytics); open routing rulesConfig surface area grows quickly; some teams prefer simpler vendors
Amazon SESAWS-native teams willing to own more of the operational layerPer-email cost is the lowest; deep AWS integrationDeliverability tooling is thin; DIY for most of the experience
SparkPost (MessageBird)Large senders wanting the same engine ESPs run onHigh throughput; built for bulk; PowerMTA heritageMore operational lift than smaller-scale teams want
Mailjet (Sinch)European-regulated workloads; marketing plus transactionalEU data residency; bilingual support; moderate volumesUS-based teams usually pick something else first

Two observations about the table. First, the market has consolidated under a few strategic acquirers — Twilio absorbed SendGrid in 2019, Sinch absorbed Mailgun and Mailjet in 2021, MessageBird absorbed SparkPost in 2021, ActiveCampaign absorbed Postmark in 2022. The "independent Email API startup" is less common than it was five years ago. Second, the right provider depends on the shape of the workload, not the brand of the provider. A team picking SendGrid because "everyone uses SendGrid" is doing worse analysis than a team picking Postmark because their transactional latency SLA demands it.

Core capabilities that justify the integration cost

Moving from SMTP relay to an Email API is not free. Application code changes; templates move from the app to the provider; webhook infrastructure has to be built; monitoring changes. The question is whether the capabilities justify that cost. The honest answer is: for some workloads yes, for others no. The table below is the short version.

Capabilities that justify Email API integration — the minimum set
CapabilityWhat it enablesWorkload where it matters
First-class event webhooksDelivered, bounced, opened, clicked, complained, unsubscribed — per message, in near-real-timeAny product that shows email status in-app or uses events for analytics
Idempotency keysRetries of the same logical send are deduplicated server-sideSystems with automatic retry logic; payment flows; order processing
Server-side templatingTemplates live with the provider; variables render at send timeProducts where marketing or product wants to iterate on templates without an app deploy
Per-tenant scopingEach customer in a multi-tenant SaaS has isolated suppression, reputation, analyticsMulti-tenant SaaS and agency-style platforms
Structured metadataArbitrary key/value tags travel with the message into events and analyticsProducts with advanced analytics; audit/compliance needs
Batch sending with per-recipient substitutionsOne request sends N personalized messagesDigests, scheduled notifications, marketing-adjacent transactional
Scheduled sends with cancellationSend at timestamp; cancel before then if state changesReminder systems, time-sensitive flows

Every row marked "yes" is a concrete thing the API does that SMTP does not, and a product that needs that thing will either build it themselves on top of SMTP or pay the API provider to include it. For two or three of the "yes" rows, the API path wins on total cost. For none of them, SMTP is still the correct tool. For the specific case of account lifecycle messaging — signup, activation, onboarding, password — where the API pattern most naturally applies, see building reliable API-based email for account lifecycle messaging.

Multi-tenant SaaS: the hardest part

Every mature Email API has a multi-tenant story, and every multi-tenant story looks slightly different, and every one of those differences shows up in your integration code. This is the single most important detail to get right before committing to a provider.

The three tenancy models providers offer

Tenancy models in Email API providers
ModelHow it worksGood forTrade-offs
Sub-accountsEach tenant is a separate account under your master, with its own credentials, templates, suppression listsPlatforms with many independent customers, agency-styleHighest lift per tenant; overkill for small customers
Message Streams / tagsAll tenants share one account; per-message tags scope the trafficSaaS with shared product surface across customersSuppression scoping requires careful application discipline
Dedicated IP per tenantPremium tenants get isolated reputation on their own IPsPlatforms selling deliverability guarantees to enterprise customersWarmup overhead per new dedicated IP; substantial cost per tenant

What "per-tenant suppression" really means

A recipient who unsubscribes from tenant A should not be suppressed for tenant B. A hard bounce from tenant A's list should not affect tenant B's deliverability. A complaint about tenant A's message should not reduce tenant B's sender reputation. Each of those is a design choice, and providers handle them differently. The pattern we see work best is:

  1. Global suppression for abuse signals. A hard bounce at the address level (user@example.com does not exist) is suppressed globally, because the address is the problem, not the tenant.
  2. Per-tenant suppression for preference signals. An unsubscribe or complaint from a specific tenant's sending applies only to that tenant, because the recipient's preference is tenant-specific.
  3. Per-tenant reputation isolation where the volume justifies it. Small tenants share the platform's reputation; large tenants earn dedicated IPs and per-IP reputation curves.
  4. Per-tenant event streams. Webhooks include the tenant identifier so the application can route events back to the right customer's analytics surface.
  5. Per-tenant rate limits. One tenant's spike in traffic should not drain the account-level throughput budget for everyone else.
The classic multi-tenant bug A common first-version mistake: the application treats "email address X unsubscribed" as a global suppression, applied across all tenants. Tenant B then cannot email X for any reason, including legitimate transactional messages from a product relationship X actively uses. The fix is scoping suppression to the tenant that generated the signal; the prevention is getting this design right in the first place.

A concrete request, from app to API

What an Email API request actually looks like, in the shape most providers have converged on. The code below is illustrative — the exact field names differ per provider, but the shape is consistent across all the serious ones.

pythonimport httpx, os, uuid

API = "https://api.authorizehosting.com/v1/send"
KEY = os.environ["AHK_API_KEY"]

def send_password_reset(tenant_id, user_id, to_addr, first_name, reset_link):
    payload = {
        "from":    { "email": "notify@app.example.com", "name": "Example App" },
        "to":      [{ "email": to_addr }],
        "template_id": "pwd_reset_v3",
        "variables": {
            "first_name": first_name,
            "reset_link": reset_link
        },
        "metadata": {
            "tenant_id": tenant_id,
            "user_id":   user_id,
            "flow":      "auth.reset",
            "app_event_id": str(uuid.uuid4())
        },
        "tags": ["transactional", "auth", f"tenant:{tenant_id}"],
        "stream": "transactional",
        "idempotency_key": f"reset-{tenant_id}-{user_id}-{int(time.time() // 60)}"
    }
    r = httpx.post(API,
                   json=payload,
                   headers={"Authorization": f"Bearer {KEY}"},
                   timeout=10.0)
    r.raise_for_status()
    response = r.json()
    # Response: { "message_id": "msg_abc123", "accepted": true }
    # We persist message_id so webhooks later can correlate events to this send.
    store_outbound_event(
        tenant_id=tenant_id,
        user_id=user_id,
        message_id=response["message_id"],
        flow="auth.reset"
    )
    return response["message_id"]

Three details worth commenting. First, the metadata field is where the tenant identifier lives; it travels with the message into every webhook event, letting the application correlate downstream events to the tenant that generated them. Second, the idempotency_key is constructed to be stable across retries of the same logical operation — same tenant, same user, same minute bucket produces the same key, so a retried call does not duplicate the message. Third, the stream field tells the provider this is transactional rather than marketing, which scopes the sending identity and reputation pool on the provider side.

What the application does not do in this code is equally important. It does not compose MIME. It does not build the HTML body. It does not sign the message. It does not handle bounces inline. All of that lives at the provider. The application is describing what it wants sent, not how to send it.

Webhooks: idempotency and signature verification

An Email API's webhook endpoint is the reverse direction of the contract: the provider pushes events to your application as they happen. Done well, webhooks are the cleanest possible way to surface email state in a product. Done poorly, they are the source of duplicate database records, inconsistent analytics and mysterious support tickets about password resets arriving twice.

The three non-negotiables

Signature verification on every incoming request
The provider signs each webhook with HMAC-SHA256 using a shared secret. The application verifies the signature before trusting any field in the payload. Without this step, anyone who discovers your webhook URL can post arbitrary events into your analytics.
Idempotency on processing
The provider guarantees at-least-once delivery, which means duplicate webhooks will arrive. The application must deduplicate by the event's unique ID (usually a field like event_id or message_id plus event_type). Inserting the event ID into a uniqueness-constrained table before executing side effects is the clean pattern.
Fast acknowledgment, slow processing
Return HTTP 200 quickly — within one second of receiving the webhook — and push the actual processing onto a background queue. Providers give up and retry after short timeouts; synchronous processing that takes ten seconds produces duplicate deliveries every time.
pythonimport hmac, hashlib
from flask import request, abort

WEBHOOK_SECRET = os.environ["AHK_WEBHOOK_SECRET"]

@app.route("/webhooks/email", methods=["POST"])
def email_webhook():
    # 1. Verify signature BEFORE reading any payload field
    signature = request.headers.get("X-AHK-Signature", "")
    body = request.get_data()
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(401)

    # 2. Parse payload after signature is verified
    event = request.get_json()
    event_id = event["event_id"]

    # 3. Deduplicate by event_id — insert first, process second
    if db.webhook_events.exists(event_id):
        return ("", 200)  # already processed; acknowledge and return
    db.webhook_events.insert(event_id, event["type"], event["payload"])

    # 4. Acknowledge fast; enqueue the actual work
    enqueue_webhook_processing.delay(event_id)
    return ("", 200)

The pattern above is boring and battle-tested. The temptation to cut corners — skip signature verification during development, process inline "because the volume is small," accept duplicates because deduplication feels like overkill — is how production incidents begin. The right approach is to implement the full pattern on day one and never touch it again.

Why idempotency matters even when it feels unnecessary The provider's retry policy is not your choice; it is the provider's. If your endpoint returns a 500 error for a transient reason, the provider retries the webhook, often several times, over minutes or hours. Without idempotency, the application processes the same event multiple times — incrementing counters twice, inserting duplicate rows, sending duplicate notifications. The cost of idempotency is one database lookup per webhook. The cost of skipping it is permanent data inconsistency.

Stream separation inside a single API account

Even within a single Email API account, your product usually has multiple sending streams that should not share reputation. Password resets and marketing newsletters both go out as "transactional" in some sense, but inbox providers treat them differently, and your sending posture should too. Most serious Email API providers offer stream separation as a first-class feature; some call it "message streams," some call it "subaccounts," some call it "IP pools." The shape is the same.

  • Transactional stream. Password resets, receipts, 2FA codes, alerts. Highest reputation priority. Dedicated IP pool or a clearly-separated shared pool. Aggressive bounce suppression. No open/click tracking that adds pixels (which can correlate with marketing signatures).
  • Lifecycle stream. Onboarding sequences, trial reminders, product update digests. High priority. Shared pool with controlled reputation. Open and click tracking appropriate.
  • Marketing / broadcast stream. Newsletters, campaigns, promotional sends. Separate pool entirely. Complaint rate monitoring aggressive; the reputation cost of marketing mistakes should not leak into transactional.

The provider-side configuration is straightforward. The application-side discipline is harder: every outgoing message has to pick the right stream, and getting that wrong conflates the reputations you carefully separated. A common pattern is to add stream selection into the template itself — the pwd_reset_v3 template is tagged as stream=transactional on the provider side, and the application never has to specify it at send time. For teams whose sending includes cold outreach as a parallel stream — which creates its own set of isolation requirements — domain separation for cold email teams is the companion read.

Authentication posture with a provider in the loop

Using an Email API does not exempt the sender from SPF, DKIM and DMARC. It moves responsibility around. The provider signs DKIM on your behalf, publishes the SPF include record you add to your domain, and typically handles the DMARC reporting pipe if you want them to. Your domain owner still has to publish the right records.

DNS# SPF — authorize the provider's sending infrastructure
app.example.com.  IN  TXT  "v=spf1 include:spf.authorizehosting.com -all"

# DKIM — CNAMEs to the provider's signing infrastructure
ahk1._domainkey.app.example.com.  IN  CNAME  ahk1.dkim.authorizehosting.com.
ahk2._domainkey.app.example.com.  IN  CNAME  ahk2.dkim.authorizehosting.com.

# DMARC — start at monitoring, progress to enforcement
_dmarc.app.example.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com; fo=1"

Two details deserve attention. First, the CNAME-based DKIM pattern is the modern default — it lets the provider rotate keys without requiring DNS changes on your side. Second, the DMARC policy should progress from p=none (monitoring) through p=quarantine (soft enforcement) to p=reject (full enforcement) as you confirm that every legitimate source of mail from the domain is aligned. Jumping to p=reject on day one, especially with legacy applications still in the mix, breaks things invisibly.

BIMI adds another dimension for 2022. Once your DMARC is at p=quarantine or p=reject, you can publish a BIMI record that lets supporting mailbox clients display your brand logo next to authenticated messages. Gmail rolled out BIMI support in mid-2021; the ecosystem has been growing steadily since. For product teams that care about brand recognition in the inbox, BIMI is worth the setup cost once the authentication foundation is in place.

When SMTP relay remains the better answer

A lot of the Email API positioning you read online implies that SMTP is obsolete. That framing is convenient for vendors but wrong for most real programs. SMTP relay remains the better answer in several specific situations.

  1. Third-party applications you did not write. Billing systems, ERP suites, help desks, monitoring agents. These speak SMTP natively and will not be rewritten. An API integration is not an option; a relay is the only practical path.
  2. Internal tools that send occasional operational mail. Cron jobs, nightly reports, backup-status notifications. The product does not care about open rates on these. SMTP is cleaner.
  3. Low-volume single-stream sending. A small SaaS that sends 5,000 password resets a month does not need the complexity of an API integration. A relay with proper authentication is enough and cheaper to maintain.
  4. Teams that explicitly want to minimize vendor lock-in. An SMTP relay can be swapped by changing credentials. An API integration is a code change. For teams that plan to migrate providers, relay gives optionality the API does not.

Most mature SaaS companies end up running both. The product itself sends through the Email API. Internal tools, legacy integrations and system alerts send through the SMTP relay. Both paths can authenticate against the same domain (with different DKIM selectors), and both share the benefits of the organization's authentication posture. The choice is per-workload, not per-company. For the deeper treatment of the comparison, see SMTP relay for billing systems and legacy applications.

Frequently asked questions

Can we use the same Email API for transactional and marketing?

Technically yes, in the sense that providers support both. Operationally, serious programs separate them at the stream or sub-account level, because the reputation dynamics are different. Mixing them in the same pool means a bad marketing campaign can affect your password-reset delivery, which is the wrong direction for reputation contamination.

How do we pick a provider?

Match your workload to the provider's shape. Transactional-only with strict latency requirements: Postmark. High-volume mixed transactional and marketing: SendGrid. AWS-native with engineering capacity to own more of the layer: SES. Developer-focused with granular routing needs: Mailgun. Evaluate on a test workload for two weeks before committing; the only comparison that matters is your own traffic.

What happens when the provider has an outage?

Transactional mail through any provider will sometimes be delayed or lost. A production-ready integration includes a dead-letter queue for sends that fail permanently, a retry policy with exponential backoff for transient failures, and either a secondary provider for failover or an explicit SLA relationship with the primary provider. Building this is part of the cost of API-based sending.

Should we use the provider's template system or our own?

Use the provider's template system when your marketing or product team needs to iterate without deploying. Use your own templates when your templates are complex, shared across channels (email plus push plus in-app), or tied to code deployments for regulatory reasons. Most teams start with the provider's templates and graduate to their own as templating needs grow.

How do we test webhook handling before we are live?

All the serious providers offer a webhook test endpoint or replay feature. Tunnel your local webhook endpoint to the internet with a tool like ngrok or Tailscale Funnel, point the provider at the tunneled URL, and trigger test events. Test the signature verification, the idempotency path, and the timeout handling before the first production send, not after.

What about inbound email?

Some Email APIs support inbound (receiving mail at a domain you configure, parsing it, and webhook-delivering the parsed content to your application). This is a separate capability from transactional sending; not every provider offers it, and the implementations differ substantially. Evaluate it independently from outbound when you need it.

Closing perspective

The Email API decision is not a modernization story. It is a workload-fit story. Products that have absorbed email into their data model — events, tenants, metadata, downstream state — benefit from the HTTP contract that the API provides. Products that still treat email as a side channel behind SMTP credentials are usually better off leaving that relationship alone and improving other things.

The teams that do this well tend to share a few habits. They start with a real workload audit rather than a vendor comparison. They design their tenancy model before they pick a provider, because providers differ more on tenancy than on raw sending capability. They implement webhook idempotency and signature verification on day one. They keep SMTP relay as the right tool for legacy and internal workloads, rather than forcing everything through the API because "modern means API." And they treat the Email API as part of their product's architecture, not as a vendor relationship.

The teams that do this poorly tend to pick the provider first, spend six months adapting their product to the provider's conventions, discover the tenancy model does not fit, and migrate eighteen months later to the provider that matches the workload they had all along. The cost of that migration is usually larger than the cost of getting the initial decision right.

The good news is that the Email API landscape has matured. The serious providers all handle the basics competently. The differentiation is at the edges — multi-tenant support, deliverability culture, event model, template system — and those edges are where the workload-fit analysis earns its keep. Take the analysis seriously and the provider choice becomes straightforward; skip it and the choice becomes expensive.