Skip to content
Claim your free business email

Email deliverability guide: how developers and small businesses reach the inbox

Published Updated 12 min readBy the Rasket team

Written for developers, beginners and small business owners

Email deliverability guide: an envelope passing through a funnel into an inbox tray.

Email deliverability is whether your mail lands in the inbox or in the spam folder. It depends mostly on three things you control: DNS records that prove the mail is yours, recipients who asked for it, and sending that looks the same from day to day.

On a Tuesday, customers with Gmail addresses stop getting your sign-up confirmations. Every message still shows as delivered, and nothing in your code or your settings changed. The last section gives the order to look in; the rest explains why it is that order.

Not a developer? The five things that are yours

Most of this page is for whoever writes the code that sends your mail. These five are decisions, not code, and they matter most:

  • Get the domain verified. DNS is your domain’s public settings, kept by the company where you bought or host it. Ask whoever runs it (your IT person, or that company’s dashboard) to “add the SPF, DKIM and DMARC records our sending service gives us, with DMARC at p=none to start”. Authentication below says what each record does and when to go past p=none.
  • Only mail people who asked. New sign-ups confirm by clicking a link, and you never buy or scrape a list.
  • Watch spam reports. Gmail’s figure is in Google Postmaster Tools; other providers’ is under Metrics in Rasket, as Complaint rate. The table below gives the limits.
  • Make leaving easy. Newsletters and offers carry an unsubscribe link that works in one click. They also have legal rules, such as a postal address, and consent first in the EU and Canada; the marketing email guide lists them.
  • Test with your own inboxes. Put a Gmail and an Outlook.com address on every list and note which folder each send lands in.

Everyday one-to-one mail from your office email account needs the first point too: if your domain has no SPF, DKIM or DMARC, those messages can go to spam as well. The company that runs your business inbox publishes the records for that mail, and a sending service like Rasket is not the tool for it; the business email article covers choosing one.

Delivered is not the same as inboxed

A delivered event means the receiving server accepted responsibility for the message. It says nothing about the folder. A message filed straight into spam produces exactly the same event as one at the top of the inbox, and no provider sends a webhook when it filters you. So deliverability is measured indirectly: complaint and bounce rates, the providers’ own dashboards, and test messages to accounts you control.

Authentication: the entry requirement

Three DNS records let a receiver check that a message claiming to be from your domain really is. SPF lists the servers allowed to send for the domain. DKIM stamps each message with a signature, like a wax seal only your domain can make and anyone can check against a key in your DNS. DMARC checks that the domain which passed SPF or DKIM matches the one in the visible From line, and tells receivers what to do with mail that fails and where to send reports. By default the match is loose: bounce.example.com counts for example.com (relaxed alignment).

Start DMARC at p=none: monitor only, so nothing is blocked while the reports show who sends as you. Once a few weeks of reports show only your own services, move to p=quarantine and then p=reject, so receivers that enforce DMARC refuse mail that forges your exact domain; Rasket’s domain page builds each step. If the domain already has a DMARC record, keep it rather than adding a second.

Records only get you considered. Gmail requires SPF or DKIM from every sender and all three from bulk senders, those sending about 5,000 or more messages a day to Gmail addresses; Outlook.com sets the same bar for domains over 5,000 a day. Missing them gets you rate-limited, sent to junk or refused. The details bite: Gmail requires DKIM keys of at least 1024 bits, and since May 2025 Outlook.com can reject a non-compliant bulk sender’s mail with 550 5.7.515.

The test is one message sent to a Gmail account you control. Open it, choose the three-dot menu beside Reply, then Show original: the top of that page says PASS or FAIL for SPF, DKIM and DMARC. In Outlook.com, choose More actions, View, then View message details. What you want in the Authentication-Results header is spf=pass, dkim=pass for your own domain and dmarc=pass; Outlook.com’s bulk rules need SPF and DKIM both to pass. If you see that, authentication is not your problem, and you can stop looking at DNS. DKIM, SPF and DMARC explained reads that header line by line, and the bulk sender checklist lists every Gmail and Yahoo rule.

Reputation: per domain and per IP

Every message leaves from a server with a numeric address, its IP, and carries your domain in the From line. Providers keep a running judgement of both from how recipients react: opens and replies on one side, spam reports and mail to dead addresses on the other. Gmail says it “tracks volume, feedback, and limits per domain and IP address”. How much each counts varies by provider and is not published, so look after both.

Shared IP pools compared with dedicated IPs
Shared IP poolDedicated IP
ReputationShared with other senders, managed by the providerYours alone, starting from none
NeedsNothing extraSteady volume and a warm-up of its own
SuitsMost sendersHigh, steady volume where the pool holds you back

Rasket sends from a shared pool by default; a dedicated IP is an add-on from the Scale plan. A dedicated address with low or uneven volume usually does worse than a well-run pool, because it has no history to lean on. Your domain’s reputation travels with you either way.

Three questions to ask about any shared pool

A pool is only as good as its worst sender. So ask a provider: are new accounts capped while they are unknown, is a new account whose bounces jump stopped automatically, and is an address that bounced as non-existent for one customer refused for all of them? A provider that cannot answer all three is asking you to share its worst customer’s reputation.

New domains and IPs: ramp, do not launch

A domain with no history that suddenly sends 100,000 messages looks like a spammer on a fresh domain, because that is what spammers do. Google advises starting with a low volume to engaged users and increasing slowly. M3AAWG’s Sender Best Common Practices (version 4.0, August 2026) suggests treating about six weeks as an average warm-up for a domain, spreading each day’s mail out rather than sending it in one burst.

In practice: send first to the people most likely to engage, such as recent sign-ups and active customers; keep each day’s volume steady and spread across the day; and raise it only while bounces and complaints stay low. If a provider starts deferring you with 4xx replies, hold or reduce the volume to that provider until they clear and check the list you are sending to, as M3AAWG advises. The warm-up guide turns this into a week-by-week schedule.

The list: who you send to decides most of it

  • Confirm new subscribers. A sign-up confirmed by a click keeps typos, fake sign-ups and spam traps off the list; the double opt-in article shows the flow.
  • Stop mailing bounces and complainers at once. Rasket suppresses hard bounces and spam complaints automatically. Repeating soft bounces are yours: remove an address that keeps bouncing over a couple of weeks.
  • Retire the inactive, judged by clicks, purchases or logins. Not by opens: Apple Mail Privacy Protection loads remote images in the background for people who turn it on, so an “open” often means nothing. Mail them once more to ask if they still want it, then stop.
  • Never send to a list you bought or scraped. Nobody on it asked, so complaints and spam traps come with it.

The list cleaning guide turns the third point into a sunset policy with dates.

What each message should carry

  • One-click unsubscribe on marketing mail. Gmail requires it from bulk senders for marketing and promotional messages and asks them to act on a request within 48 hours. The two headers are List-Unsubscribe: <https://…> and List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058); Rasket adds them to every campaign. Receipts and password resets do not need one: they are transactional mail, sent because of something the person just did.
  • The same From address for the same kind of mail. Google asks for this directly, and it lets recipients recognise you.
  • A text part beside the HTML. Common practice rather than a published rule: some clients show it, and a message that is one large image with no text is hard for any filter to judge.
  • Links on domains with a history. Also common practice: M3AAWG lists domain age among the inputs to a domain’s reputation, the same logic applies to link domains, and public URL shorteners are shared with spammers.

Sending at volume without tripping limits

Providers judge the pattern as well as the content, and a burst from an application that normally sends a trickle looks like a compromised account. Spread large sends out: schedule a campaign rather than firing it from a loop, and send batches of transactional mail through POST /emails/batch, up to 100 messages a request, checked all or nothing and covered by one idempotency key. The Rasket API allows 10 requests a second per team; past that it answers 429 with a Retry-After header, as the rate limits reference documents. Wait that long and retry with the same idempotency key, which Rasket remembers for 24 hours, so a retry can never become a duplicate. Derive the key from the event, such as signup-confirm:<confirmation id>, and store it before the first attempt, so a restarted worker retries with the same one:

const API = "https://api.rasket.com";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// The per-second limit, and the same key still in flight from an earlier attempt.
const RETRY = new Set(["rate_limit_exceeded", "concurrent_idempotent_requests"]);

// Retries only what clears on its own: those two and a server error.
// The same Idempotency-Key on every attempt means a retry can never send twice.
export async function send(message, idempotencyKey, attempts = 5) {
  for (let attempt = 1; ; attempt++) {
        let response;
    try {
      response = await fetch(`${API}/emails`, {
        method: "POST",
        headers: {
          authorization: `Bearer ${process.env.RASKET_API_KEY}`,
          "content-type": "application/json",
                    "user-agent": "acme-ops/1.0", // required: a request without one gets a 403
          "idempotency-key": idempotencyKey,
        },
        body: JSON.stringify(message),
        signal: AbortSignal.timeout(10_000),
      });
    } catch (error) {
      // No answer at all: it may or may not have been sent, and the key makes a retry safe.
      if (attempt === attempts) throw error;
      await sleep(2 ** attempt * 500);
      continue;
    }
    const body = await response.json().catch(() => ({}));
    if (response.ok) return body;

    const retryable = RETRY.has(body.name) || response.status >= 500;
    if (!retryable || attempt === attempts) {
      throw new Error(`${response.status} ${body.name ?? response.statusText}`);
    }
    const seconds = Number(response.headers.get("retry-after")) || 2 ** (attempt - 1);
    await sleep(seconds * 1000 * (1 + Math.random() / 2)); // jitter: up to half again
  }
}

A 429 named daily_quota_exceeded or monthly_quota_exceeded carries no Retry-After and does not clear in seconds, which is why the loop retries by error name, not status. The rate limits article shows the headers that tell you how close you are to the limit.

The numbers to watch, and the alert to build

Deliverability metrics and alert thresholds
MetricWhereAlert at
Spam rate at GmailGoogle Postmaster Tools0.1%, or 1 report per 1,000 messages. Google asks senders to stay below it and never reach 0.3%
Complaint rate elsewherecomplaint_rate0.1%, for the same reason
Bounce ratebounce_rateAbout 2%: no mailbox provider publishes a limit, and sending services commonly recommend staying under it

There is no separate delivery-rate alert: delivery_rate and bounce_rate are both shares of the recipients that were delivered or bounced, so one is 1 minus the other. Gmail does not send senders a report per complaint, so your own complaint rate mostly reflects other providers; Gmail’s number is in Postmaster Tools. The Postmaster Tools guide shows how to set it up, and the complaint rate guide explains what happens above 0.3%. Microsoft’s Smart Network Data Services (SNDS) reports by IP address, so it helps once you have a dedicated IP; on a shared pool, the pool’s operator watches it. For your own data, a daily job like this one is enough; save it as a .mjs or .ts file, since it uses top-level await:

const API = "https://api.rasket.com";

// Last seven days, one row per sending domain (domain_id).
const url = new URL("/emails/metrics", API);
url.searchParams.set("metrics", "delivered,bounce_rate,complaint_rate");
url.searchParams.set("dimensions", "domain");
url.searchParams.set("start_date", new Date(Date.now() - 7 * 86_400_000).toISOString());

const response = await fetch(url, {
  headers: {
    authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "user-agent": "acme-ops/1.0",
  },
});
if (!response.ok) throw new Error(`metrics: ${response.status}`);
const { data = [] } = await response.json();

// Rates are fractions: 0.001 is 0.1%. A row looks like
// { domain_id: "01a1…", delivered: 970, bounce_rate: 0.03, complaint_rate: 0.002 }
for (const row of data) {
  // Too few to judge: at 300 recipients, one complaint is already 0.33%.
  if (row.delivered < 1000) continue;
  if (row.complaint_rate >= 0.001) console.warn(`${row.domain_id}: complaints at ${row.complaint_rate * 100}%`);
  if (row.bounce_rate >= 0.02) console.warn(`${row.domain_id}: bounces at ${row.bounce_rate * 100}%`);
}

Per message: the webhooks to consume

The daily job shows trends; webhooks show each message. Subscribe an endpoint to email.bounced, email.complained and email.delivery_delayed, and check each request’s svix-id, svix-timestamp and svix-signature headers before trusting it; the webhooks article has the verification code. Store each svix-id and skip one you have seen, because an event can arrive twice. Events can also arrive out of order, so compare created_at before you change a message’s state, and never let a late delivered replace a bounced. A delivery_delayed event means a receiver answered 4xx and our mail servers are retrying; its delay.expirationTime is when they will stop. If the message is still refused then, you get email.bounced with type Transient, so there is no need for a resend layer of your own.

When mail suddenly goes to spam

Look in this order

Records first, because a missing one explains a sudden drop and takes two minutes to rule out. Then the rates by sending domain, to find the stream. Then what changed in that stream’s list or content.

  • Check the records. Open one of the affected messages with Show original. dkim=fail or dmarc=fail points at DNS: a migration that lost a record, or a key rotated without publishing the new selector.
  • Split by mailbox provider. Trouble at Gmail alone points at Gmail’s view of you, so open Postmaster Tools; trouble at Microsoft alone, at theirs. Trouble everywhere points at your records, your list or a block list.
  • Read the bounce codes. A 5.7.x reply is a refusal for policy or reputation: 550 5.7.26 from Gmail means authentication, and a reply that names a block list usually includes a link to look the listing up and request removal. On a shared pool, a listed IP address is the provider’s to fix, so send them the bounce.
  • Check the rates by domain. A complaint spike usually traces to one campaign or one import.
  • Check what changed. A new link domain, a redesigned template sent to everyone at once, a list nobody had mailed for a year.

If none of that finds it, work through Why are my emails going to spam?, which takes the symptoms one at a time, with the fix each one needs.

Frequently asked questions

Should marketing and transactional mail come from separate subdomains?

Usually, once you send campaigns. Mail from its own subdomain, such as news.example.com, builds its own record with mailbox providers, so a campaign that draws complaints weighs less on your password resets. Providers can still connect a subdomain to its parent domain, so it softens the effect rather than removing it.

What exactly should I ask my IT person or registrar to set up?

Ask them to publish the DNS records Rasket shows for your domain: DKIM records, a return-path record (the address bounces go back to, which carries Rasket's SPF), and a DMARC record at p=none if the domain has none yet. Because that SPF sits on the subdomain, the SPF record your office email uses at the root of the domain stays as it is.

How can I check inbox placement without special tools?

Keep test accounts at Gmail, Outlook.com and Yahoo, include them in each campaign, and note which folder the message lands in. It is a small sample, so treat it as a smoke test, and use Postmaster Tools for Gmail's view of your whole domain.

Can I see my sender reputation?

Not as one score from Gmail any more. Postmaster Tools v2 shows your spam rate and authentication results, and Google is retiring the old domain and IP reputation dashboards. Microsoft's SNDS shows a per-IP verdict, useful once you have a dedicated IP. DMARC aggregate reports show who sends as you and whether they pass.

Do I need a sending service at all?

Not if you only send one-to-one mail from your office email account and your booking or shop tool sends its own confirmations. You need one when your own app or website sends mail, or when you mail a list larger than an office mailbox is built for. Either way, the domain needs SPF, DKIM and DMARC.

A customer says they never got my email. What do I check?

Ask them to look in spam first. Then find the message under Emails in Rasket: its last event says whether it was delivered, bounced, or suppressed because that address bounced or complained before. A typo in the address shows up as a hard bounce.

Why does my mail go to spam even though SPF and DKIM pass?

Because authentication proves who sent a message, not that anyone wants it. Once you pass, placement depends on complaints, engagement, bounces and the history of your domain and links. A campaign to an old list can send everything to spam with perfect records.

How long does a damaged reputation take to recover?

No provider publishes a recovery time. Gmail calculates the spam rate daily, and a bulk sender that went over 0.3% becomes eligible for its delivery mitigation again once the rate has stayed under 0.3% for seven consecutive days. Reputation itself rebuilds only through weeks of mail people want.

Sources

  1. Email sender guidelines — Google, read 2026-10-16
  2. Email sender guidelines FAQ — Google, read 2026-10-17
  3. Outlook.com Postmaster: policies, practices and guidelines — Microsoft, read 2026-10-16
  4. M3AAWG Sender Best Common Practices, Version 4.0 (August 2026) — M3AAWG, read 2026-10-16
  5. RFC 8058: Signaling One-Click Functionality for List Email Headers — IETF, read 2026-10-17
  6. Rasket docs: Metrics API reference — Rasket, read 2026-10-17
  7. Rasket docs: Rate limits — Rasket, read 2026-10-17
  8. Rasket docs: Emails API reference (batch sending) — Rasket, read 2026-10-17
  9. Rasket docs: Webhooks API reference — Rasket, read 2026-10-17

Start sending this morning

Sign up, verify a domain and send your first email in minutes.