→ From Coder to Shipper
From Coder to Shipper · Web fundamentals

Modern email stack

How a web app sends REAL email in 2026 — without running a mail server: one API call, a provider's network, and three DNS records that keep you out of spam.

← / → to navigate · EN/VI switch below

Big picture

Email is a feature, not an afterthought

Every real product sends email. If it stops, users lock themselves out, lose receipts, and quietly stop trusting the product.

Password reset is the first test

If a user can't get the reset email, they can't get back in. No email = no login, no support, no refunds.

Receipts and confirmations

Order placed, payment received, account created — each event produces an email. It's the paper trail of your product.

Notifications are the product

Mentions, shipping updates, reminders — many apps live or die on how fast and reliably email reaches the inbox.

Email is still the universal channel

Push needs your app installed, SMS costs per message. Email is near-free, universal, and backed by 40+ years of infrastructure.

Big picture

The modern email stack, in one picture

Your code never talks to the internet's mail network. It makes one HTTPS call — the provider handles the rest.

📱 Your app

Triggers email from user actions: reset clicked, order paid. It builds From/To/subject/body — nothing more.

→

📡 Email API

The door: one authenticated HTTPS POST. You get back a message ID and a 202 'queued'. That's the whole contract.

→

🚚 Provider's network

Sending servers with clean IPs and years of reputation. They sign the message (DKIM) and deliver it over SMTP.

→

📬 Recipient's mailbox

The recipient's server checks SPF/DKIM/DMARC against YOUR domain's DNS, then files the message: inbox or spam.

WHO DOES WHAT you: write the message + call the API + publish 3 DNS records · provider: everything in the middle · recipient: the checks at the end

no mail server on your VPS · no SMTP code in your app · no IP reputation to build

You don't run a mail server. You rent a mail courier — and your only job is to hand over an envelope with the right address.
— the modern stack in one sentence
01
Part 1

Why a provider

Sending email yourself is technically possible and practically painful. This part shows why every serious app pays someone else to do it.

The split

Two kinds of email — you're building the first

Almost all app email is transactional. Marketing email is a different discipline with different rules — know the line.

Transactional — one user, one event

Password reset, receipt, 'your package shipped'. One message, one recipient, triggered by an event. Urgent, low volume, must arrive.

Marketing — many users, one blast

Newsletters, promos, digests. Opt-in required, strict send limits, unsubscribe links. Usually a separate service and a separate sending domain.

The line matters

Providers price and rate-limit by kind. Pushing newsletters through your transactional account gets you throttled — or your app mail mislabeled.

This deck covers the first kind

Everything below: how a web app sends transactional email that lands in the inbox, not the spam folder.

The trap

Why 'just send from the VPS' fails

It's tempting: one less service, one less bill. In practice, a fresh server is the worst possible mail sender.

Port 25 is closed by default

Most VPS and cloud providers block outbound port 25 (the mail port) at the network level — so you can't become a spam factory overnight.

Fresh IP = guilty until proven innocent

Inbox providers track IP reputation. A brand-new IP with zero history looks exactly like a spam bot's IP. Warming it takes weeks of careful volume.

Failure is invisible

Messages silently go to spam, bounce, or vanish. Without a dashboard and webhooks, you find out only when a user complains — days later.

Reputation is a long game

Building sending-IP reputation is a months-long project: gradual volume, monitoring, complaint management. That's someone else's business.

Proof

The wall, in one command

From a typical fresh VPS, the mail port simply isn't there — while the API door is wide open.

ops@vps — zsh
$ nc -vz mx.google.com 25
nc: connect to mx.google.com port 25 (tcp): timed out
# the VPS provider blocked outbound port 25 — by design
$ curl -X POST https://api.resend.com/emails -H 'Authorization: Bearer re_...' -d @email.json
{"id": "d41d8cd98f00b204e9800998ecf8427e"}
202 Accepted — queued for delivery
The value

What the provider actually does for you

You're not paying for 'sending'. You're paying for a reputation, a network, and a feedback loop that took years to build.

Clean IPs with history

Their sending servers have years of good behavior behind them. Inbox providers let that traffic through — that's the product.

They sign for you (DKIM)

Every message is signed with your domain's DKIM key by their network. You publish one DNS record; they sign forever.

A feedback loop

Bounces, clicks, complaints and delivery status come back to your app as webhooks. Your code reacts: stop sending to dead addresses.

One API, any language

The same HTTPS call works from Python, Node, Go, or a serverless function. The mail stack stops being part of your codebase.

The landscape

Choosing a provider

They all do the same job. Pick on free tier, developer experience, and how well they fit your stack.

ProviderKnown forGood fit
SendGridThe veteran — huge ecosystem + free tierbig projects, many integrations
Resenddeveloper-first API, very clean DXmodern web apps, quick setup
Postmarkobsession with deliverability, very strictemail where arrival is critical
Amazon SESrock-bottom price at scalehigh volume, cost-sensitive
Mailgunflexible APIs, domains & trackingapps needing fine-grained control
Your job is one HTTP call and three DNS records. The hardest part of email — reputation, delivery, authentication — is someone else's full-time job.
— why the modern stack exists
02
Part 2

The flow — one email, four hops

Follow a single password-reset email from your code to someone's inbox. Every piece you'll ever need is on this map.

The flow

The pipeline, with the protocol labels

Same four stations as the big picture — but now each hop is labeled with what actually travels over the wire.

📱 Your app

→ HTTPS POST

Sends From/To/subject/body plus your API key. No mail protocol in your code.

↓

📡 Provider's API

→ queue

Validates and queues the message, returns 202 + a message ID. Your request is done — delivery runs in the background.

↓

🚚 Provider's mail servers

→ SMTP :25

Looks up the recipient's MX record, connects to their mail server, hands over the message. Retries and re-routes on failure.

↓

📬 Recipient's server

→ local

Runs the SPF/DKIM/DMARC checks against your domain's DNS, scores the message, files it: inbox, spam — or a bounce back.

you own hop 1 (and the DNS records) · the provider owns hops 2–3 · the recipient's world owns hop 4

Your code

The entire mail feature

No SMTP client, no mail server, no port 25. An HTTPS request with a JSON body — that's it.

send_email.py
import httpx

# the entire 'email feature' of your app
r = httpx.post(
    "https://api.resend.com/emails",
    headers={"Authorization": "Bearer re_..."},
    json={
        "from": "Acme App <noreply@shop.example.com>",
        "to": ["alice@example.com"],
        "subject": "Your password reset link",
        "html": "<p>Click <a href='/reset?token=...'>here</a> to reset.</p>",
    },
)
print(r.status_code)          # 202 — queued for delivery

Your provider's docs give you a snippet for your language. With AI this is a five-minute task — the skill is knowing what the response means.

End to end

The life of one message

What happens after your 202 — the provider's side of the contract.

  1. 1

    Validate + queue

    The API checks your key, your sending domain and the payload, then queues the message. You already hold a message ID.

  2. 2

    Sign the message

    The network signs it with your domain's DKIM private key. The public half lives in your DNS — recipients use it in a moment.

  3. 3

    Find the mailbox

    A DNS MX lookup finds the recipient's mail server (say, Google's). The provider connects over SMTP on port 25 and hands the message over.

  4. 4

    Get judged

    The recipient's server checks SPF, DKIM and DMARC against your domain's records, then runs its spam filters. Your DNS setup gets tested here.

  5. 5

    Land — or bounce

    Inbox: delivered, a webhook fires. Spam: delivered but useless. Bounce: the address doesn't exist, and your app is told to stop sending there.

The mental split

Sending side vs receiving side

Most confusion about email mixes these up. You live on the left; the right is where your DNS records get read.

The sending side (yours, via provider)

SMTP is the protocol that moves mail between servers — but you never touch it. Your app calls an API; the provider's servers do the SMTP, the retries, the IP reputation.

If you hear 'SMTP', think: the provider's internal plumbing.

The receiving side (their rules)

The recipient's server is a gatekeeper: it looks up your SPF/DKIM/DMARC records, verifies the message, then decides inbox, spam, or reject.

This is where deliverability lives — the entire next part.

Under the hood

Anatomy of a received message

Every real email carries a header block — the message's passport, and where authentication shows up.

ops@vps — zsh
From: Acme App <noreply@shop.example.com>
Reply-To: support@shop.example.com
To: alice@example.com
Subject: Your password reset link
Message-ID: <20261027.abc123@resend.net>
Authentication-Results: mx.example.com;
dkim=pass header.d=shop.example.com;
spf=pass; dmarc=pass
# the trio passed -> this message reaches the inbox
Operations

The feedback loop (free with the provider)

The provider watches every message after you send it. You subscribe to the outcomes — and your app can act on them.

Bounces

Hard bounce = the address doesn't exist (stop sending, mark it). Soft = full or busy (retry later). A good app updates its user list from these.

Complaints

The recipient hit 'report spam'. A few is normal; a spike means your content or list is wrong — and your deliverability is about to suffer.

Delivery webhooks

An HTTPS POST back to YOUR app when a message is delivered, bounced, or complained. 'Send' becomes a reliable feature, not fire-and-forget.

The discipline

Suppression lists (never send to known bounces) and clean lists keep a young domain out of trouble. Most providers do the basics for you.

03
Part 3

Deliverability — SPF · DKIM · DMARC

Inboxes are paranoid because anyone can fake a From: header. Three DNS records are the condition of entry — and a tie-in to the DNS you already know.

The threat

Why inboxes are paranoid

The mail protocol has no built-in identity. The From: header is a claim, not a fact — and the internet is full of false claims.

From: is trivially fakeable

Anyone can write any address in From:. Without checks, a scam from 'support@example.com' sent by a stranger's server looks structurally identical to the real one.

The internet has no mail police

No central authority verifies senders. Each inbox provider (Google, Microsoft, Apple…) runs its own gatekeeping — and they share threat data to keep spam out.

Your domain is your ID card

Since no one verifies you, YOU publish the verification rules in your domain's DNS. The recipient's server reads them before deciding where your mail goes.

No records = spam folder

A domain with no SPF or DKIM is a red flag in itself. For a new product, unauthenticated mail may never reach the primary inbox — not even the first reset email.

The trio

Three records, one job each

Learn them in this order: who is ALLOWED (SPF), who actually SIGNED (DKIM), and what to DO when the story doesn't add up (DMARC).

SPF — the guest list

A TXT record on your domain: 'only these servers may send mail claiming to be my domain.' The recipient checks the sending server against it.

DKIM — the wax seal

Each message is signed with your private key; the public key lives in your DNS. If the recipient can verify the signature, the message is unbroken and truly yours.

DMARC — the law

A policy record: 'if a message fails SPF or DKIM, reject it / quarantine it / ignore.' It also tells the recipient where to send reports about who used your domain.

SPF · the record

SPF, read left to right

One TXT record on your domain. It's a guest list, and the last token is the rule for everyone else.

dns — SPF
# registrar panel -> your domain -> add TXT record
name:  @
value: v=spf1 include:resend.net ~all

# read it left to right:
#  v=spf1            -> this is an SPF record, version 1
#  include:resend.net -> the provider's servers are allowed
#  ~all              -> everyone else: soft-fail (flag, don't reject)

Your provider's dashboard hands you the exact include value to paste. '-all' is the stricter variant: hard-reject anyone not on the list.

Deliverability · the check

The moment of judgment

When your message arrives at the recipient's server, it runs this sequence — every step reads YOUR domain's DNS.

1 · Message arrives

The recipient's mail server accepts it from the provider's IP — and starts checking the claims inside.

↓

2 · SPF check

DNS lookup on your domain: is this sending server on the guest list? Pass or fail, recorded.

↓

3 · DKIM check

DNS lookup for your selector record, then verify the signature. Did this message really come from your key, unmodified?

↓

4 · DMARC policy applies

If the checks pass and the From: domain aligns — inbox. If they fail — your DMARC policy says reject, quarantine, or let it slide.

two of the four steps are DNS lookups on YOUR domain — you already know how to do that part

DKIM · plain language

DKIM — signing every envelope

Skip the key-generation details; here's the shape that matters.

A key pair, one DNS record

Your provider generates a DKIM key pair for your domain and gives you one TXT record to publish (e.g. under mail._domainkey.yourdomain.com). That's your only task.

The private key signs, the provider does it

Every message leaving their network gets a DKIM-Signature header: a digest of the message, signed with your private key. You never touch the key.

The recipient verifies in DNS

The recipient reads the signature header, fetches your public key from your DNS, and checks: same message, same sender. Tamper with anything and it breaks.

Why it beats SPF alone

SPF only checks WHICH server sent. If that server is compromised or abused, SPF still passes. DKIM binds the signature to the CONTENT — a different lock.

DMARC · plain language

DMARC — the policy + the report

DMARC adds two things: what happens on failure, and who tells you when someone misuses your domain.

The policy record

One TXT record at _dmarc.yourdomain.com: v=DMARC1; p=none → p=quarantine → p=reject. You start at none and tighten as your SPF+DKIM stabilize.

Alignment: the stories must match

DMARC only counts a pass if the domain in From: matches the domain authenticated by SPF or DKIM. 'Sent by my partner, from my brand' is exactly what it polices.

Reports: the free security feed

Point rua= at an inbox and inbox providers will mail you daily digests of who sent as your domain — including attackers you never authorized.

The endgame: p=reject

Big companies run p=reject: anything unauthenticated claiming their domain is deleted at the gateway. It's the strongest anti-phishing statement a domain can make.

At a glance

The trio on one table

Three records, three questions, three answers — all in your own DNS panel.

RecordLives atAnswersAnalogy
SPF@ (the domain)Which servers may send as me?a guest list
DKIMselector._domainkeyDid I actually sign this message?a wax seal
DMARC_dmarcWhat do I do when the story fails?the house rules
Verify

dig the trio

Three lookups, three answers. If all three come back, your domain is allowed to send mail that inboxes accept.

ops@vps — zsh
$ dig example.com TXT +short
"v=spf1 include:resend.net ~all"
$ dig _dmarc.example.com TXT +short
"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
$ dig mail._domainkey.example.com TXT +short
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
# SPF + DMARC + DKIM all present -> the domain can send real mail
04
Part 4

Putting it together

From zero to 'my app sends real email': five moves, one DNS panel, and a few habits that keep the mailbox relationship healthy.

The checklist

Five moves to shipping email

The whole deck, compressed into what you'll actually click and paste.

  1. 1

    Own the domain

    A domain you control (you bought it for the site — it doubles as your mail identity). You'll be adding records to its DNS panel.

  2. 2

    Create the provider account

    Pick one (Part 1's table), verify the domain with them, and grab your API key. The dashboard now shows you the exact DNS records to add.

  3. 3

    Add the three records

    SPF (include the provider), DKIM (paste their TXT), DMARC (start with p=none). Save, then wait for DNS to settle — minutes to a day.

  4. 4

    Send the test email

    Call the API once to a real inbox you control. Check it arrived in the primary inbox — not spam — and that the provider marks it delivered.

  5. 5

    Wire the webhooks

    Point the provider's webhook at your app, so bounces and complaints update your user list automatically. Now email is a feature, not a prayer.

The DNS panel

The three records to paste

Everything the provider's dashboard asks you to add, in one view. Values are illustrative — yours will be different strings.

dns records
# your domain's DNS panel — the shippable-email set
name                  type     value
@                     TXT      v=spf1 include:resend.net ~all
mail._domainkey       TXT      v=DKIM1; k=rsa; p=MIIBIjANBgkq...QIDAQAB
_dmarc                TXT      v=DMARC1; p=none; rua=mailto:dmarc@example.com

SPF says who may send. DKIM says how to verify the signature. DMARC says what to do on failure. Add all three on day one.

Habits

Patterns that keep mail healthy

Small choices at send time that decide whether your domain stays in good standing.

From: a human, Reply-To: a team

Send from 'Acme App <noreply@…>' with Reply-To pointing at a real support inbox. noreply addresses that actually take replies annoy people; Reply-To fixes it.

BCC yourself while testing

During development, add your own address as BCC so you can watch exactly what the user gets — subject, body, links, the works.

Digest instead of flood

Low-urgency events (new follower, 3 mentions) batch into one daily digest. Fewer, bigger emails are easier on the inbox — and on deliverability.

One domain for transactions

Keep marketing on its own subdomain (e.g. mail.example.com) so a bad batch of promos can't drag your transactional reputation down with it.

The comparison

Transactional provider vs a regular mailbox

Gmail is a great place to READ mail. It is the wrong tool to SEND product mail at scale.

Transactional providerGmail / Outlook
Built forapp-triggered, one-to-one, high urgencypeople, conversations, low volume
SendingAPI + unlimited-ish volume on fair usemanual, ~500/day limits, no API
Reputation & authmanaged IPs, SPF/DKIM/DMARC tooling, inbox placementyour responsibility, no tooling
Feedbackbounces, clicks, complaints via webhooksnobody tells you anything
Big picture · wrap-up

Three records + one API = email that lands

The entire modern stack from your seat: you publish trust in DNS, you write the message in code, the provider does the rest.

🏷 Your domain

SPF + DKIM + DMARC in its DNS: the published rules that let inboxes trust mail carrying your name.

+

📡 The provider API

One authenticated call from your app. Delivery, signing, retries and IP reputation are the provider's paid job.

+

🔁 Webhooks back

Delivered, bounced, complained — the outcomes return to your app, which keeps its user list honest.

password reset, receipts and notifications now ship with the app — like they do on every product you use

Set up the three records, make the one API call — and your app sends real email that actually lands, with a feedback loop that keeps it honest.
— the whole deck in one breath

Message → API → network → inbox.

Add SPF, DKIM and DMARC to your domain, call the provider once, wire the webhooks — then go build the feature that sends the mail.