Blazalek.com

07 / 10

Marketing Emails

Running a consent-based program: how to collect opt-in, make unsubscribing painless, design valuable messages and keep the list healthy so mail keeps landing.

By

Key takeaways

  • Run a consent-based program: only mail people who opted in, and make unsubscribing painless.
  • Engagement is the reputation lever, send valuable mail and prune the disengaged.
  • One-click unsubscribe and promptly honored opt-outs are now table stakes for bulk senders.
On this page

This chapter answers the everyday questions about running a marketing email program: how to collect consent the right way, how to make unsubscribing painless, how to design messages that get opened, and how to keep your list healthy so your mail keeps landing in the inbox. The legal details by jurisdiction live in the Compliance chapter; here we focus on day-to-day practice.

Quick answers to the questions covered:

  • What counts as a marketing email, and how is it different from transactional?
  • What are the core principles I should never break?
  • What counts as valid consent (opt-in), and what does not?
  • Should I use double opt-in?
  • How easy does my unsubscribe have to be?
  • How do I implement one-click unsubscribe (RFC 8058) correctly?
  • How do I design subject lines, structure, and mobile layout that convert?
  • How should I segment and personalize?
  • How often and when should I send?
  • How do I keep my list clean, and what bounce/complaint thresholds matter?
  • What must every marketing email legally contain?

A note on provider neutrality: this chapter assumes you talk to an email service provider (ESP) through a thin wrapper rather than calling a specific vendor SDK directly. Wherever code appears, emailClient / provider stands in for whatever you use, Amazon SES, Postmark, SendGrid, Mailgun, Resend, Brevo, SparkPost, Mandrill, or your own SMTP relay. The standards (RFC 8058 one-click unsubscribe, RFC 7208 SPF, RFC 6376 DKIM, RFC 7489 DMARC) are identical across all of them; only the API surface differs. Keep the abstraction so you can switch vendors without rewriting your program.

What is a marketing email, and how is it different from a transactional email?

A marketing email is a promotional message you send to people who chose to hear from you: newsletters, product announcements, offers, digests, and re-engagement campaigns. A transactional email responds to a specific action a specific user took, like a password reset or an order receipt.

The practical difference drives everything else:

  • Marketing email requires explicit consent. You may only send to people who opted in.
  • Transactional email is triggered by the user, so it does not need separate marketing consent (but it still must not contain promotional content if you want to keep it transactional).
  • Marketing email must always deliver value to the recipient, not just ask them to buy.

See Email Types: Transactional vs Marketing for the full distinction and why mailbox providers treat the two differently, and the Transactional Catalog for the inventory of message types that are unambiguously transactional.

The marketing/transactional decision table

The hardest cases are the hybrids: messages that ride on a transactional trigger but carry promotional intent. Use this table to classify a borderline message before you decide which consent rules, sending stream, and headers apply.

MessageTriggerPromotional content?ClassificationConsent needed
Password resetUser clicked "forgot password"NoTransactionalNone (user-initiated)
Order receiptUser placed an orderNoTransactionalNone
Shipping notificationOrder shippedNoTransactionalNone
Receipt with "you might also like" blockUser placed an orderYes (cross-sell)Hybrid, treat as marketingMarketing opt-in for the promo block
Abandoned-cart reminderUser left items in cartYes (recover the sale)Marketing in EU/Canada, often allowable as transactional in the USOpt-in where required
Win-back "we miss you"Inactivity, sent by youYesMarketingMarketing opt-in
Weekly newsletterSchedule, sent by youYesMarketingMarketing opt-in
Account expiry / dunning noticeSubscription lapsingNo (service continuity)TransactionalNone
Free-trial "upgrade now"Trial endingYesHybrid, leans marketingMarketing opt-in for the upsell

The rule of thumb: if a reasonable recipient would describe the email's purpose as "they want me to buy something," it is marketing, regardless of what triggered it. When in doubt, split the message. Send the lean transactional notice on your transactional stream and the promotion as a separate marketing send to consented recipients only. Mixing the two on one stream is the most common way teams accidentally taint their transactional reputation. Stream separation is covered in Sending Reliability.

Why does getting marketing email right matter so much?

Because mailbox providers (Gmail, Yahoo, Microsoft) now judge senders largely on recipient engagement and complaint rates. A marketing program built on real consent, clear value, and easy unsubscribe does not just stay legal; it stays in the inbox.

The bigger risk is shared reputation: sloppy marketing email is the single fastest way to wreck the sender reputation that your transactional email depends on too. Mailbox providers apply reputation at the domain level, so a complaint-heavy newsletter can push your password-reset emails into spam.

Since February 2024, Gmail, Yahoo, and Microsoft enforce concrete requirements on any sender doing bulk volume (Gmail and Yahoo define "bulk" as roughly 5,000+ messages per day to their users; Microsoft applies comparable rules through 2025 rollouts). The three pillars are:

  1. Authentication. SPF (RFC 7208), DKIM (RFC 6376), and a DMARC (RFC 7489) record must all be in place, and the visible From domain must align with at least one of them. Authentication mechanics live in Deliverability.
  2. One-click unsubscribe. Marketing mail must carry List-Unsubscribe and List-Unsubscribe-Post headers implementing RFC 8058, and the request must be honored within two days.
  3. A spam-complaint rate kept below 0.3%, measured at the mailbox provider (Google Postmaster Tools / Yahoo equivalents), with under 0.1% as the operating target.

These are not "marketing-team" concerns, they are engineering requirements that fail closed. Miss them and the provider does not bounce your mail with a clear error; it quietly routes you to spam. The rest of this chapter is largely about staying on the right side of those three pillars by construction.

What are the core principles I should never break?

Three principles drive every decision in this chapter. When you are unsure how to handle an edge case, return to these.

  1. Consent first. Send only to people who explicitly opted in. Explicit opt-in is required under GDPR (EU) and CASL (Canada), and it is the foundation of good deliverability everywhere. No consent, no send.
  2. Value-driven. Every email should give the recipient something useful (knowledge, savings, relevant news), not just a sales pitch. If a recipient cannot articulate why your email is worth opening, you have already lost them.
  3. Respect preferences. Let people control how often they hear from you and about what. Honoring preferences (including the preference to leave) builds the long-term trust that drives revenue.

A useful test before any send: "If this exact email landed in my inbox, would I be glad I subscribed?" If the honest answer is no, fix the email before you fix the send schedule.

How these principles map to engineering controls

Principles only protect you if they are encoded as systems rather than intentions. The table below pairs each principle with the concrete control that enforces it, so that a busy operator cannot accidentally break it.

PrincipleFailure mode if left to disciplineEngineering control
Consent firstSomeone uploads a list; it gets mailedSuppression-list check and consent-source flag enforced at send time, not at import
Value-drivenCadence creeps up to hit revenue targetsPer-subscriber send-frequency cap; engagement-gated audiences
Respect preferencesUnsubscribes lag behind sendsUnsubscribe writes to a global suppression list checked synchronously before every send

The single most important control is a global suppression list that every sending path, marketing campaigns, automations, transactional-with-promo blocks, checks before composing a message. If unsubscribe, complaint, and hard-bounce data all flow into one suppression store and every send consults it, the principles hold even when humans are careless. Suppression-list design is detailed in List Management.

Consent must be a deliberate, affirmative action by the user. It is the legal and practical foundation of marketing email: getting it right protects you legally, improves deliverability, and produces a list of people who actually want your messages.

What counts as valid opt-in:

  • The user checks an unchecked box to subscribe.
  • The user clicks a "Subscribe" button.
  • The user completes a form with clear subscription intent (for example, a newsletter signup form).

What does NOT count:

  • Pre-checked boxes. The user must actively check it themselves.
  • Opt-out models. Assuming consent unless the user objects is not consent.
  • Assumed consent from a purchase. Buying a product does not grant marketing consent (in some regions it may support transactional messages only).
  • Purchased or rented lists. Never. These people never agreed to hear from you; they generate complaints, destroy reputation, and create legal exposure.

Purchased lists are the classic "fast growth" trap. They look like a shortcut but reliably produce high bounce and complaint rates, which mailbox providers punish across your entire sending domain, including transactional mail that real customers depend on.

"We have consent" is not an answer a regulator or a mailbox-provider escalation will accept. You need a per-subscriber, immutable record that can reconstruct the moment of opt-in. Capture these fields at the instant consent is given and never overwrite them:

Code for your engineers (TypeScript)
interface ConsentRecord {
  subscriberId: string;
  email: string;
  consentedAt: string;        // ISO 8601 UTC timestamp
  source: string;             // e.g. "footer-newsletter-form", "checkout-optin"
  method: "single_optin" | "double_optin" | "imported_with_proof";
  ipAddress: string;          // the IP that submitted the form
  userAgent: string;
  formVersion: string;        // hash or version of the exact wording shown
  disclosedScope: string[];   // ["newsletter", "product_updates"], what they agreed to
  doubleOptinConfirmedAt?: string; // set only after the confirmation click
  legalBasis: "consent" | "soft_optin" | "legitimate_interest";
}

Store the exact wording the user agreed to (or a versioned reference to it). Under GDPR you must be able to demonstrate consent; "the form said something like this" is not demonstrable. Versioning the form copy means that if you later change the language, you can still prove what each historical subscriber actually saw.

Consent is only meaningful if the user understands what they are agreeing to. At the point of signup, disclose four things:

  • Email types: what you will actually send (newsletter, offers, product updates).
  • Frequency: roughly how often (weekly, monthly).
  • Sender identity: who is sending.
  • How to unsubscribe: that leaving is easy and always available.

Good: "Subscribe to our weekly newsletter with product updates and tips"

Bad: "Sign up for emails"

The good example tells the user the cadence (weekly), the content (product updates and tips), and the format (newsletter). The bad example tells them nothing, which means any email you send risks feeling like a surprise, and surprises generate complaints.

Should I use double opt-in?

Yes, in almost all cases. Double opt-in adds a confirmation step that verifies the email address really belongs to the person subscribing.

The flow:

  1. User submits their email address.
  2. You send a confirmation email with a verification link.
  3. User clicks the link to confirm.
  4. You add them to the list only after confirmation.

Why it is worth the extra step:

  • Verifies deliverability. Confirms the address is real and reachable, cutting bounces.
  • Confirms intent. Proves the person actually wants your email, not a typo'd or maliciously entered address.
  • Reduces complaints. Confirmed subscribers complain far less.
  • Required in some regions. Notably Germany, where double opt-in is the established standard.

A small amount of friction at signup pays for itself many times over in list quality and deliverability. See Email Capture for form and verification-email implementation details.

How do I implement double opt-in safely?

The confirmation token must be unguessable, single-use, and time-limited, and the confirmation email itself is transactional (it is triggered by the user's submission), so it goes on your transactional stream. Generate the token with a cryptographically secure source, never a sequential ID or a hash of the email.

Code for your engineers (TypeScript)
import { randomBytes, createHash } from "node:crypto";

// At signup: issue a confirmation token.
function issueConfirmationToken() {
  const raw = randomBytes(32).toString("base64url"); // 256 bits of entropy
  const tokenHash = createHash("sha256").update(raw).digest("hex");
  // Store only the HASH in the DB; email the raw token in the link.
  return { raw, tokenHash, expiresAt: Date.now() + 1000 * 60 * 60 * 48 }; // 48h
}

// Send the confirmation email through your provider-agnostic wrapper.
async function sendConfirmation(email: string, rawToken: string) {
  const confirmUrl =
    `https://example.com/newsletter/confirm?token=${rawToken}`;
  await emailClient.send({
    to: email,
    from: "Newsletter <newsletter@mail.example.com>",
    subject: "Confirm your subscription",
    text: `Confirm your subscription: ${confirmUrl}\n\n` +
          `This link expires in 48 hours. If you did not request this, ignore this email.`,
    html: `<p>Please confirm your subscription.</p>` +
          `<p><a href="${confirmUrl}">Confirm subscription</a></p>` +
          `<p>This link expires in 48 hours. If you did not request this, ignore this email.</p>`,
    // No List-Unsubscribe header needed: this is a one-off transactional confirm,
    // and the recipient is not yet on any list.
  });
}

On the confirmation endpoint, hash the incoming raw token and compare against the stored hash in constant time, check expiry, mark the token used, and only then write doubleOptinConfirmedAt on the consent record. Two edge cases worth handling explicitly:

  • Link prefetching. Some security gateways and mailbox scanners follow links in inbound mail to check for malware. A naive GET handler that confirms on visit will "confirm" subscriptions the user never clicked. Mitigate by confirming on a POST behind a button on a landing page, or by detecting bot user-agents and requiring an explicit interaction.
  • Resend abuse. Rate-limit confirmation resends per email and per IP so the confirmation endpoint cannot be used to spam a third party (someone enters a victim's address repeatedly).

Treating one consent as consent for everything. Consent collected for a free download does not authorize a weekly promotional newsletter unless you disclosed that at signup. Keep a record of when, where, and for what each subscriber opted in; under GDPR you must be able to demonstrate consent, and that record is also your defense in a complaint dispute.

  • Confirming on link-open instead of an explicit action. Inbound scanners silently confirm subscriptions; your "confirmed" rate looks great and your complaint rate climbs.
  • One global "marketing" flag instead of scoped consent. When a subscriber opted into "release notes" but you mail them weekly offers, the scope mismatch reads as spam to them and to the provider.
  • Importing a CRM/CSV without proof. "These are our customers" is not consent. Imported contacts need a documented legal basis (or a re-permission campaign) before the first marketing send.
  • Overwriting the consent timestamp on profile edits. Treat consent records as append-only; an updated email or name must not reset consentedAt.
  • No re-confirmation after a long dormancy. A consent from four years ago that has never engaged is stale; mailing it risks spam traps (see list hygiene below).

How easy does my unsubscribe have to be?

Extremely easy. A frictionless unsubscribe is not a weakness; it is a deliverability and trust feature. The alternative to an easy unsubscribe is a spam complaint, and complaints are far more damaging than a clean opt-out.

Your unsubscribe mechanism must be:

  • Prominent in every email. Never hidden in a tiny grey font or buried.
  • One-click (preferred). The fewer steps, the better.
  • Immediate (GDPR) or within 10 business days (CAN-SPAM). Immediate is always the better practice. Note the Gmail/Yahoo bulk-sender rules tighten this to within two days for one-click requests.
  • Free and login-free. Never require the user to sign in or pay to leave.

Reframe the incentive: every person who cannot find the unsubscribe link and hits "Report Spam" instead teaches the mailbox provider that your mail is unwanted, for everyone on that provider. Making unsubscribe trivially easy is how you protect the inbox placement of the subscribers who stayed.

For bulk senders, Gmail and Yahoo's 2024 sender requirements make one-click unsubscribe via the List-Unsubscribe header (with List-Unsubscribe-Post) mandatory, and require complaint rates to stay under 0.3% (ideally under 0.1%). The next section covers the header mechanics; see Compliance for the legal framing.

How do I implement one-click unsubscribe (RFC 8058) correctly?

One-click unsubscribe is defined by RFC 8058, which builds on the older List-Unsubscribe header (RFC 2369). There are two headers, and bulk marketing mail needs both:

  • List-Unsubscribe, one or more URIs the mail client can use to unsubscribe. May contain a mailto: and/or an https: URI.
  • List-Unsubscribe-Post: List-Unsubscribe=One-Click, the signal, added in RFC 8058, that tells the mailbox provider it may POST to the HTTPS URI directly without the user ever loading a page.

A correct header set on an outgoing marketing message looks like this:

List-Unsubscribe: <https://mail.example.com/u/aGVsbG8gd29ybGQ>, <mailto:unsub@example.com?subject=unsubscribe>
List-Unsubscribe-Post: List-Unsubscribe=One-Click

When a Gmail or Yahoo user clicks the native "Unsubscribe" affordance, the provider sends an HTTP POST to the HTTPS URI with the body List-Unsubscribe=One-Click and content type application/x-www-form-urlencoded. There is no human on the other end of that request and no second confirmation page, your endpoint must process it and suppress the subscriber on receipt.

Critical constraints that trip people up:

  1. The one-click target must accept POST and must not require authentication. The provider's bot is not logged in. A login wall or a CSRF-token requirement that the provider cannot satisfy breaks the flow.
  2. The token in the URL must be unguessable and scoped to one subscriber + one mailing. Anyone (or any scanner) who sees the email can POST to that URL, so the token is the only thing that ties the request to a specific recipient.
  3. A GET to the same URL should be safe, show a "you have been unsubscribed" or "confirm unsubscribe" page for humans who click the link in the body, but do not perform a destructive action on GET alone for the body link, to survive prefetching scanners. The RFC 8058 POST is what guarantees removal.
  4. Honor it fast. Within two days is the provider requirement; synchronous removal is better. Do not "soft" unsubscribe into a re-confirmation funnel.

Add the headers through your provider wrapper. Most ESPs expose custom-header or list-management options; the abstraction keeps you portable:

Code for your engineers (TypeScript)
import { createHmac } from "node:crypto";

// Sign a stable token so the endpoint can verify it statelessly,
// or look the token up in a store, either works. HMAC shown here.
function unsubToken(subscriberId: string, campaignId: string) {
  const payload = `${subscriberId}:${campaignId}`;
  const sig = createHmac("sha256", process.env.UNSUB_SECRET!)
    .update(payload)
    .digest("base64url");
  return Buffer.from(`${payload}:${sig}`).toString("base64url");
}

async function sendCampaignMessage(sub: { id: string; email: string }, campaignId: string) {
  const token = unsubToken(sub.id, campaignId);
  const unsubUrl = `https://mail.example.com/u/${token}`;

  await emailClient.send({
    to: sub.email,
    from: "Acme News <news@mail.example.com>",
    subject: "Your weekly digest",
    html: renderCampaign(unsubUrl), // body footer link points at the same unsubUrl
    headers: {
      "List-Unsubscribe": `<${unsubUrl}>, <mailto:unsub@example.com?subject=unsubscribe:${sub.id}>`,
      "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
    },
  });
}

The receiving endpoint, framework-agnostic in shape:

Code for your engineers (TypeScript)
// POST /u/:token  (RFC 8058 one-click target, no auth, accepts the provider's bot)
async function handleOneClickUnsubscribe(req: Request): Promise<Response> {
  const body = await req.text(); // expect "List-Unsubscribe=One-Click"
  const token = req.url.split("/u/")[1];

  const verified = verifyUnsubToken(token); // checks HMAC, returns {subscriberId, campaignId} | null
  if (!verified) return new Response("Invalid token", { status: 400 });

  // Idempotent: a second POST for an already-suppressed subscriber is a no-op success.
  await suppressionList.add(verified.subscriberId, {
    reason: "unsubscribe",
    campaignId: verified.campaignId,
    at: new Date().toISOString(),
  });

  // RFC 8058 expects a 200; some providers retry on non-2xx.
  return new Response("Unsubscribed", { status: 200 });
}

How do I verify the headers are actually being sent?

Do not trust the dashboard, inspect a real raw message. Send yourself a campaign and view source, or pipe a copy through a mailbox you control and check with a script:

# View the raw headers of a saved .eml and confirm both headers exist.
grep -i '^List-Unsubscribe' message.eml

# Expected output:
# List-Unsubscribe: <https://mail.example.com/u/...>, <mailto:unsub@example.com?subject=...>
# List-Unsubscribe-Post: List-Unsubscribe=One-Click

Then test the POST path the way a provider does, with no cookies and no auth header:

curl -i -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "List-Unsubscribe=One-Click" \
  "https://mail.example.com/u/<token>"

# Must return 2xx and the subscriber must be suppressed afterward.

One-click unsubscribe: common mistakes

  • Sending only List-Unsubscribe without List-Unsubscribe-Post. Without the POST header, Gmail/Yahoo do not render the native one-click button; you fail the 2024 requirement even though a header is present.
  • The one-click URL requires login or a session. The provider bot cannot authenticate; the POST fails silently and the user clicks "Report Spam" instead.
  • Performing removal on GET of the body link. Inbound link scanners prefetch it and "unsubscribe" engaged users without their knowledge.
  • Using a guessable token (sequential ID, plain email, unsalted hash). Anyone can unsubscribe anyone; worse, scanners can mass-unsubscribe your list.
  • Returning non-2xx from the POST handler. Some providers retry or flag the failure; either way the user's intent may not be honored, raising future complaints.
  • A mailto:-only List-Unsubscribe. A mailto satisfies the legacy header but does not enable one-click; you need the HTTPS URI plus the POST header for the 2024 rules.

What is a preference center, and do I still need a one-click unsubscribe?

A preference center lets users dial down rather than disappear. Instead of a binary stay/leave choice, it offers:

  • Frequency: daily, weekly, or monthly options.
  • Content types: let them pick which topics or product lines they care about.
  • Complete unsubscribe: always available as a clear option.

A good preference center recovers subscribers who are tired of the volume, not the brand. Important: a preference center is a complement to a true one-click unsubscribe, never a replacement for it. Forcing someone through a preference center before they can fully leave violates the spirit (and often the letter) of one-click unsubscribe rules, and specifically breaks RFC 8058, which expects the one-click POST to remove the recipient outright, not to land them on a multi-step form. More on this distinction in Compliance.

Unsubscribe checklist

  • Unsubscribe link present and visible in every marketing email
  • Works without requiring login or payment
  • One-click where possible
  • List-Unsubscribe header present with an HTTPS URI (and optionally a mailto)
  • List-Unsubscribe-Post: List-Unsubscribe=One-Click present for bulk sends (RFC 8058)
  • One-click POST endpoint accepts unauthenticated POST and returns 2xx
  • Unsubscribe token is unguessable and scoped per subscriber + mailing
  • Request honored within two days (immediately is better)
  • Removal is idempotent and writes to the global suppression list
  • Confirmation shown to the user that they have been removed
  • Preference center offered as an alternative (optional but recommended)

How do I write subject lines that get opened?

The subject line is the single highest-leverage element in the entire email. Most recipients decide whether to open based on it alone.

  • Clear and specific. Aim for 50 characters or less so it is not truncated on mobile.
  • Create curiosity without misleading. Intrigue is good; bait-and-switch destroys trust and triggers complaints.
  • A/B test regularly. Small wording changes can meaningfully shift open rates.

Good: "Your weekly digest: 5 productivity tips"

Bad: "You won't believe what happened!"

The good subject is specific and sets accurate expectations. The bad one is clickbait: it overpromises, underdelivers, and trains recipients to distrust (and report) you.

A note on open-rate measurement after Apple Mail Privacy Protection

Open rates have been distorted since Apple Mail Privacy Protection (MPP) began prefetching tracking pixels in 2021. For Apple Mail users, MPP loads your open-tracking pixel whether or not the message was actually opened, inflating opens and detaching them from real behavior. The practical consequences for an engineering-led program:

  • Do not gate suppression on opens alone. A subscriber who "opens" every email via MPP but never clicks may be entirely disengaged. Use clicks, and server-side signals (purchases, logins, page visits) as the trustworthy engagement axis.
  • A/B test on clicks and conversions, not opens, for any audience with meaningful Apple Mail share.
  • Subject-line tests still work in aggregate if you compare click-through and conversion, since the subject's job is to earn the open that leads to a click.

What about preview text and the from name?

Two elements sit next to the subject line in the inbox and are often wasted:

  • Preview text (preheader): the snippet shown after the subject. Set it deliberately to extend the subject's promise rather than leaking "View in browser" or alt text.
  • From name: a recognizable, consistent sender name drives opens more than any subject trick. Avoid switching between "Company", "Company News", and a personal name, which fragments recognition.

A common bug: the preheader leaks the first text in the HTML body, often a hidden "View in browser" link or image alt text, because no explicit preview text was set. Set an explicit, hidden preview-text block as the first element of the body, padded with a zero-width run so the inbox does not append stray body text after it:

HTML template code
<!-- Hidden preheader: shows in the inbox preview, not in the rendered body. -->
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;">
  5 ways to ship faster this week, plus a reader question answered.
  &#847;&zwnj;&nbsp;&#847;&zwnj;&nbsp;&#847;&zwnj;&nbsp;&#847;&zwnj;&nbsp;
</div>

How should I structure a marketing email?

Design for skimming. Most people scan; very few read top to bottom.

Above the fold (visible without scrolling):

  • A clear value proposition: why this email matters.
  • A primary CTA: the one action you most want.
  • An engaging visual: supports the message, does not replace it.

Body:

  • Scannable: short paragraphs and bullet lists.
  • Clear hierarchy: headings and emphasis guide the eye.
  • Multiple CTAs: reinforce the primary action without overwhelming.

Footer:

  • Unsubscribe link: always.
  • Company info and physical mailing address: required by CAN-SPAM.
  • Social links: optional.

A practical rule: keep one primary CTA per email. Adding a second competing goal typically lowers the click rate on both.

How do I make my emails work on mobile?

A large share of email is opened on phones, so design for mobile first and let it scale up to desktop.

  • Single-column layout. Multi-column designs break on small screens.
  • 44x44px minimum buttons. Large enough to tap reliably with a thumb.
  • 16px minimum text. Readable without pinch-zoom.
  • Test on iOS, Android, and dark mode. Rendering differs across clients; dark mode in particular can invert colors and hide logos if you have not tested.

What email-design mistakes most often hurt deliverability?

  • Image-only emails. If the whole email is one image, it looks empty when images are blocked, and spam filters distrust it. Keep a real text-to-image balance.
  • Missing alt text. Always set alt text so the message survives blocked images.
  • Tiny or invisible unsubscribe links. Hiding the link drives complaints, the opposite of what you want.
  • No plain-text part. Send multipart (HTML plus plain text); a missing plain-text alternative is a spam signal.
  • Link-shortener or mismatched-domain links. Bare URL shorteners and tracking domains that do not match your sending domain look like phishing to filters. Use a tracking/click domain that is a subdomain of your authenticated sending domain so it inherits your reputation and aligns with DKIM/DMARC.

How do the body and headers relate to authentication?

Marketing content does not authenticate itself; the envelope and headers do. But two body-adjacent choices interact with deliverability:

  • The visible From domain must align with SPF (RFC 7208) or DKIM (RFC 6376) so DMARC (RFC 7489) passes. If you send marketing from news@mail.example.com, that domain (or its organizational domain) must be the one signing. Misalignment is the most common reason authenticated-looking mail still fails DMARC.
  • Click-tracking and image domains should be on your own authenticated subdomains. A click.example.com you control and have set up SPF/DKIM for keeps the whole message coherent to filters.

For the full authentication setup, generating DKIM keys, publishing SPF, building a DMARC policy, and the optional MTA-STS (RFC 8461), TLS-RPT (RFC 8460), ARC (RFC 8617), and BIMI layers, see Deliverability. Marketing mail depends entirely on that foundation being in place.

Content and design checklist

  • Subject line ≤ 50 characters, specific, non-misleading
  • Explicit hidden preheader set (not leaked body text)
  • Consistent, recognizable From name
  • Value proposition and primary CTA above the fold
  • Body is scannable (short paragraphs, bullets, clear hierarchy)
  • Single-column, mobile-first layout
  • Buttons ≥ 44x44px, body text ≥ 16px
  • Tested on iOS, Android, and dark mode
  • Footer has unsubscribe link, company info, and physical address
  • Real text content, not image-only; alt text on all images
  • Multipart message with a plain-text alternative
  • Links use your own authenticated domain, not bare shorteners

How should I segment my list?

Sending the same email to your entire list wastes its potential. Segmentation means dividing your audience into groups and sending each the content most relevant to them.

Common ways to segment:

  • Behavior: past purchases, site activity, product usage.
  • Demographics: location, industry, role.
  • Preferences: topics or product lines they selected.
  • Engagement level: highly active versus dormant subscribers.
  • Signup source: which form or campaign brought them in.

Why it works:

  • Higher open and click rates: relevant mail gets engaged with.
  • Lower unsubscribe rates: people leave when content stops being relevant.
  • Better overall experience: recipients feel understood, not spammed.

Even basic segmentation (for example active versus inactive, or by product interest) typically outperforms a single undifferentiated blast. The single most valuable segment for deliverability is engagement: mailing your most engaged subscribers first when warming a domain or IP signals quality to mailbox providers.

Engagement segmentation as a deliverability tool

Engagement is not just a relevance lever; it is the primary control you have over inbox placement. Mailbox providers infer wanted-ness from how recipients treat your mail. Define explicit engagement tiers from server-side signals (clicks, conversions, logins, not MPP-inflated opens) and treat them as sending policy, not just targeting:

TierDefinition (trailing window)Sending policy
Highly engagedClicked or converted in last 30 daysFull cadence; lead here when warming
EngagedClicked or converted in last 90 daysNormal cadence
LapsingNo click/conversion in 90–180 daysReduce cadence; targeted re-engagement only
DormantNo click/conversion in 180+ daysSuppress from broadcasts; one win-back attempt then remove

When warming a new domain or dedicated IP, ramp volume by sending to the most-engaged tier first and expanding outward only as positive signals accumulate. This is the safest path to building reputation; the warm-up schedule and IP-vs-shared decision are covered in Sending Reliability. Pruning the dormant tier protects your aggregate engagement metrics, which is what providers actually score.

How do I personalize without being creepy?

Personalization tailors content to the individual. Done well, it lifts engagement; done clumsily, it feels intrusive.

Common options:

  • Name in subject line or greeting.
  • Location-specific content: local events, regional offers.
  • Behavior-based recommendations: "because you viewed X."
  • Purchase history: relevant restocks, complements, or upgrades.

The rule: only use data you have clear permission to use, and only in ways the recipient would find helpful rather than unsettling. A first name in the greeting is welcome; a reference to something they browsed once and never bought can read as surveillance. Referencing data the user did not realize you had can feel intrusive, so favor obvious, expected uses over clever ones.

Defensive personalization: handle the missing-data case

The fastest way to look unprofessional, and to trigger a complaint, is a personalization token that renders empty or wrong: "Hi {{first_name}}," or "Hi ,". Every dynamic field needs a fallback and a render-time guard. Never ship a template where a null produces a broken greeting.

Code for your engineers (TypeScript)
function greeting(firstName?: string | null): string {
  const name = (firstName ?? "").trim();
  // Reject placeholder junk and obviously-invalid values.
  const looksReal = name.length > 0 && name.length <= 40 && !/^(null|undefined|n\/a)$/i.test(name);
  return looksReal ? `Hi ${escapeHtml(name)},` : "Hi there,";
}

Guards to enforce in any personalization pipeline:

  • Always provide a neutral fallback ("Hi there,") for every token.
  • Escape user-supplied values in HTML to prevent a stored name from injecting markup.
  • Validate before send, not after. A pre-send dry run on a sample should fail the campaign if any required field is missing for any recipient.
  • Limit personalization to data with documented consent and an obvious connection to the message, per the consent scope you recorded at signup.

How often and when should I send?

How often and when you send shapes engagement and unsubscribe rates as much as content does.

Frequency:

  • Start conservative. It is easier to add volume than to win back annoyed subscribers.
  • Increase based on engagement. Let positive signals (opens, clicks) earn more frequent sends.
  • Let users set preferences. The preference center lets people self-select cadence.
  • Monitor unsubscribe rates. A spike after a frequency change is your signal to back off.

Timing:

  • Weekday mornings (9 to 11 AM local time) tend to perform well.
  • Tuesday to Thursday are often the strongest days.
  • Always test your specific audience. These are starting points, not laws. A B2B audience and a consumer audience may behave very differently. Use send-time data from your own campaigns to refine.

How do I enforce a frequency cap in code?

A frequency cap is the engineering control behind "respect preferences." Enforce it at send time against a per-subscriber send log, so no campaign, automation, or one-off blast can exceed the cap regardless of who scheduled it.

Code for your engineers (TypeScript)
interface FrequencyPolicy {
  maxPerWindow: number;   // e.g. 3
  windowDays: number;     // e.g. 7
}

async function mayReceiveMarketing(
  subscriberId: string,
  policy: FrequencyPolicy,
): Promise<boolean> {
  if (await suppressionList.has(subscriberId)) return false; // unsub/complaint/hard-bounce
  const since = new Date(Date.now() - policy.windowDays * 86_400_000);
  const recentCount = await sendLog.countMarketingSince(subscriberId, since);
  return recentCount < policy.maxPerWindow;
}

// In the campaign fan-out, filter the audience THROUGH this gate before sending.
async function buildSendable(audience: string[], policy: FrequencyPolicy) {
  const checks = await Promise.all(
    audience.map(async (id) => ((await mayReceiveMarketing(id, policy)) ? id : null)),
  );
  return checks.filter((id): id is string => id !== null);
}

This gate also gives you a single place to honor per-subscriber preference-center cadence (override maxPerWindow per subscriber) and to enforce a quiet period after a complaint or bounce.

Is there a frequency that is objectively too high?

There is no universal number, but watch the relationship between frequency and your unsubscribe and complaint rates. If an increase in cadence pushes your unsubscribe rate above roughly 0.5% per send, or your complaint rate toward 0.1%, you are sending too often for that audience. Send time of day matters less than relevance and frequency; a relevant message at a "wrong" hour beats an irrelevant one at the "right" hour.

How do I keep my list clean?

A clean list is the backbone of both deliverability and accurate metrics. Sending to dead or unwilling addresses drags down your reputation and pollutes your data. For the full treatment, see List Management.

Remove immediately:

  • Hard bounces: permanently invalid addresses.
  • Unsubscribes: honored without delay.
  • Complaints: anyone who marked you as spam.

Remove after inactivity:

  • Run a re-engagement campaign first ("We miss you, still want to hear from us?").
  • Then remove non-responders. Keeping dormant addresses only depresses engagement metrics and risks hitting spam traps.

How do bounces and complaints get back to my system?

List hygiene is only as good as your feedback loop. Two channels deliver the signals you must act on:

  • Bounce and complaint webhooks from your provider report each hard bounce, soft bounce, and spam complaint as an event. Process them and write to the suppression list automatically.
  • Feedback Loops (FBLs) at mailbox providers report "this is spam" clicks. Most ESPs aggregate these and surface them as complaint events; if you run your own infrastructure you register FBLs directly with each provider.

Verify webhook payloads before acting on them, an unauthenticated endpoint that suppresses on any POST is a denial-of-service vector. Many providers sign webhooks using the Standard Webhooks scheme (an HMAC over the timestamp and body). Verify with raw crypto, no vendor SDK required:

Code for your engineers (TypeScript)
import { createHmac, timingSafeEqual } from "node:crypto";

// Standard Webhooks-style verification: signature is base64 HMAC-SHA256
// over `${id}.${timestamp}.${rawBody}` using the endpoint secret.
function verifyWebhook(
  rawBody: string,
  headers: { id: string; timestamp: string; signature: string },
  secretBase64: string,
): boolean {
  const signedPayload = `${headers.id}.${headers.timestamp}.${rawBody}`;
  const key = Buffer.from(secretBase64, "base64");
  const expected = createHmac("sha256", key).update(signedPayload).digest();
  // The header may carry several space-separated `v1,<sig>` values; check each.
  return headers.signature.split(" ").some((part) => {
    const sig = part.includes(",") ? part.split(",")[1] : part;
    const given = Buffer.from(sig, "base64");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

Reject events whose timestamp is outside a tolerance window (e.g. five minutes) to prevent replay. Then route the event to suppression. Full webhook handling, idempotency, retries, event taxonomy, is covered in Webhooks and Events.

What bounce and complaint rates are too high?

Monitor these thresholds and treat any breach as an early warning to fix acquisition or content before mailbox providers start filtering you.

MetricTarget
Bounce rate< 2%
Complaint rate< 0.05%

Two more numbers worth tracking against the 2024 Gmail/Yahoo rules: keep your complaint rate consistently under 0.3% (a hard ceiling) and ideally under 0.1%. A re-engagement window of 90 to 180 days of no opens or clicks is a common trigger for a win-back campaign before suppression.

Troubleshooting: my marketing mail started going to spam

When inbox placement drops, work the problem in this order, cheapest and most common causes first:

  1. Check the complaint rate at the provider. Open Google Postmaster Tools (and the Yahoo/Microsoft equivalents) and look at the spam-complaint rate by domain. If it is above 0.1% and trending toward 0.3%, this is almost certainly the cause. Pause the highest-complaint campaigns and prune dormant subscribers.
  2. Confirm authentication still passes. A rotated DKIM key, a changed sending subdomain, or an SPF lookup-limit overflow (RFC 7208 caps SPF at 10 DNS lookups) can silently break DMARC alignment. Verify with dig:
    dig +short TXT example.com | grep -i spf          # SPF (RFC 7208)
    dig +short TXT default._domainkey.mail.example.com # DKIM (RFC 6376)
    dig +short TXT _dmarc.example.com                  # DMARC (RFC 7489)
    
    See Deliverability for reading and fixing these records.
  3. Confirm one-click unsubscribe headers are present and working. A regression that drops List-Unsubscribe-Post will degrade Gmail/Yahoo placement even if everything else is fine. Re-run the grep and curl checks from the one-click section.
  4. Check for a sending-pattern shift. A sudden volume spike, a new IP that was not warmed, or a freshly imported segment all read as risk. Roll back to the engaged tier and re-warm gradually.
  5. Look for spam-trap or list-quality signals. A jump in hard bounces or a stagnant unknown segment suggests a bad import or aged addresses. Suppress and re-permission rather than continuing to mail.
  6. Inspect content changes. New image-heavy templates, a new click-tracking domain that is not authenticated, or shortener links introduced in the last campaign can tip filters.

Make one change at a time and let placement recover over several sends before concluding what fixed it; reputation is a moving average, not a switch.

List hygiene: common mistakes

  • Treating soft bounces like hard bounces (or vice versa). Remove hard bounces immediately; retry soft bounces a bounded number of times, then suppress.
  • Re-mailing unsubscribes after a CRM sync re-imports them. The suppression list must win over every import; never let a sync resurrect a suppressed address.
  • Keeping dormant subscribers "just in case." They depress engagement averages and accumulate spam-trap risk as abandoned addresses get recycled into traps.
  • Acting on unverified webhooks. Always verify the signature before suppressing, or an attacker can suppress your real subscribers or forge engagement.

What must every marketing email contain?

Every marketing email you send must include, at minimum:

  • Clear sender identification: the recipient instantly knows who it is from.
  • Physical mailing address: required by CAN-SPAM (US).
  • Unsubscribe mechanism: prominent and functional.
  • Indication that it is marketing: required under GDPR; do not disguise promotion as something else.

Treat this as a non-negotiable pre-send checklist:

  • Sender clearly identified (From name and address)
  • Physical mailing address in the footer
  • Working, prominent unsubscribe link in the body
  • Message is identifiable as marketing
  • List-Unsubscribe header set with an HTTPS URI (bulk senders)
  • List-Unsubscribe-Post: List-Unsubscribe=One-Click set (RFC 8058, bulk senders)
  • SPF, DKIM, and DMARC pass with From alignment
  • Sent only to consented, non-suppressed recipients

The legal reasoning behind each of these, and the specific penalties for getting them wrong, is detailed in Compliance.

A consolidated pre-send gate

Rather than relying on a human to walk the checklist, encode the hard requirements as a function the send pipeline must call for every recipient. If it throws, the message does not go out.

Code for your engineers (TypeScript)
async function assertSendable(msg: {
  to: string;
  subscriberId: string;
  headers: Record<string, string>;
  html: string;
  text?: string;
}) {
  if (await suppressionList.has(msg.subscriberId)) {
    throw new Error("recipient suppressed");
  }
  if (!(await consentStore.hasMarketingConsent(msg.subscriberId))) {
    throw new Error("no marketing consent on record");
  }
  if (!msg.headers["List-Unsubscribe"]?.includes("https")) {
    throw new Error("missing HTTPS List-Unsubscribe (RFC 8058)");
  }
  if (msg.headers["List-Unsubscribe-Post"] !== "List-Unsubscribe=One-Click") {
    throw new Error("missing List-Unsubscribe-Post (RFC 8058)");
  }
  if (!msg.text) {
    throw new Error("missing plain-text alternative");
  }
  if (!/unsubscribe/i.test(msg.html)) {
    throw new Error("no visible unsubscribe link in body");
  }
  // Physical address presence, marketing label, From alignment, etc. checked similarly.
}

A gate like this is the difference between a program that is compliant on a good day and one that is compliant by construction.

If this is happening in production…

Don't guess. Get a read on it

Book a 30-min reviewWe'll look at where your mail goes today and what it would take to fix it (no obligation).Book a deliverability call

Seen this in the wild: read the case studies