# AcelleMail — Full LLM-friendly content bundle

> Auto-generated concatenation of all markdown companion files at /{slug}.md, then every published knowledge-base article.
> Generated: 2026-10-22T10:08:59+00:00
> Source pages: 19 · Knowledge-base articles: 143

---

## /about.md

# About AcelleMail

> Canonical HTML: https://acellesend.com/about
> Content-Type: text/markdown

AcelleMail is a self-hosted, source-available email-marketing platform built by **Basic-Technology**, first released in **2016** (full source ships under a paid commercial license — not OSI open-source). Trusted by thousands of customers worldwide over its years of development, it is now on **v4.2.0 LTS** (released April 14, 2026).

## Mission

Give every business — from a one-person Substack to a multi-tenant SaaS — full ownership of its email-marketing stack: the data, the code, the deliverability decisions, the cost curve.

## Why self-hosted

Email marketing data is among the most sensitive operational data a company holds: customer email addresses, behavioral history, purchase signals. Handing that to a SaaS vendor on recurring billing exposes the business to (a) a tier-driven cost curve, (b) vendor lock-in, (c) a single point of failure outside your control. AcelleMail flips all three: pay once, own the source, host where you trust.

## Product principles

1. **{{terms.license.updates_months}} months of updates.** Every license includes the releases published in its first {{terms.license.updates_months}} months, and the versions you have keep running after that.
2. **Unlimited contacts + sends.** Pricing is per-product, not per-volume.
3. **No telemetry.** The application does not phone home.
4. **Source ships.** Full PHP/Laravel codebase. Modify, white-label, redeploy without permission.
5. **Standards over lock-in.** SPF / DKIM / DMARC / RFC 8058 / RFC 8288 — we publish to the spec, not a vendor extension.

## Timeline

- **2016** — first public release
- **2018** — automation engine + SaaS multi-tenant mode (Extended License)
- **2020** — REST API v2 + plugin system
- **2022** — drag-and-drop builder v2 (current)
- **2024** — knowledge base consolidated to `acellesend.com/kb`
- **2026** — AI plugin (`acelle/ai`) + Messenger plugin (`acelle/messenger`) ship

## Team

A small distributed team led by Basic-Technology. Support, sales, and engineering all reachable at `admin@acellesend.com` (24h typical response, business days).

## Get in touch

- **Email:** admin@acellesend.com
- **Contact form:** https://acellesend.com/contact
- **Knowledge base:** https://acellesend.com/kb

## Next

- [Features](https://acellesend.com/features.md)
- [Pricing](https://acellesend.com/pricing.md)
- [Security](https://acellesend.com/security.md)


---

## /automation.md

# AcelleMail — automation

> Canonical HTML: https://acellesend.com/automation
> Content-Type: text/markdown

Trigger-based workflows for onboarding, drip campaigns, re-engagement, and customer journeys — no extra subscription, included in the base license.

## Triggers

- **Subscriber signup** to a specific list
- **Tag added / removed**
- **Custom event** posted via API (e.g. "purchase completed", "trial started")
- **Date-relative** (subscribe date + N days, birthday, anniversary)
- **Email opened or link clicked** in a previous campaign
- **Field value change**
- **Workflow goal reached** (chain workflows together)

## Actions

- **Send email** (uses any template + segmentation logic)
- **Wait** for a duration (minutes / hours / days / weeks) or until a condition
- **Tag add / remove**
- **Move to another list**
- **Update custom field**
- **Webhook call** to your own API
- **End workflow** with optional goal-reached flag

## Conditional logic

- **If / else branches** on subscriber attributes, tags, recent activity, or workflow state
- **Multi-branch routing** with up to 16 paths per node
- **Re-enter on trigger** (or once-only) — configurable per workflow

## Workflow analytics

- **Funnel view** with conversion rate per step
- **Subscribers currently waiting** at each node (live count)
- **Goal-reached %** and average time-to-goal
- **Exported as CSV** for offline analysis

## Examples shipped

The product ships preset templates for:

- **New subscriber onboarding** (welcome → about us → first offer)
- **Cart abandonment** (1h → 24h → 3d sequence)
- **Re-engagement** for stale subscribers (60d / 90d / 180d windows)
- **Birthday + anniversary** sends
- **Post-purchase upsell** chain
- **Lead nurture** for SaaS trial signups

## Limits

Automations run on the standard Laravel queue + scheduler. With Redis or DB queue + supervisor managing workers, throughput scales linearly with worker count — typical single-server setup handles 100,000+ workflow runs per day comfortably.

## Next

- [Features](https://acellesend.com/features.md)
- [Email Marketing](https://acellesend.com/email-marketing.md)
- [Integrations](https://acellesend.com/integrations.md)
- [Pricing](https://acellesend.com/pricing.md)


---

## /best-self-hosted-2026.md

# Best Self-Hosted Email Marketing Software 2026 — 11 Platforms Compared

> Source: https://acellesend.com/best-self-hosted-email-marketing-2026
> Updated: 2026-05-26
> Legal posture: factual comparison only; every claim verifiable against vendor's published pricing/feature pages or the AcelleMail codebase.

## Quick pick

If you only have 30 seconds:

| Platform | License | Stack | Multi-tenant | Best for |
|---|---|---|---|---|
| **AcelleMail** 🏆 | $80 / $199 one-time | PHP / Laravel 11 | Yes (Extended) | All-in-one + modern Laravel + plugin SDK + AI/SMS add-ons |
| MailWizz | ~$79 / ~$399 one-time | PHP / Yii | Yes (Extended) | Long-running CodeCanyon listing |
| Listmonk | Free (AGPL) | Go + Vue | No | Tech-savvy newsletter, minimal + free |
| Mailcoach | $99–$249/yr | PHP / Laravel | Add-on | Spatie ecosystem users |
| Mautic | Free (GPL) | PHP / Symfony | Limited | Enterprise marketing automation |
| SendPortal | Free / paid | PHP / Laravel | Yes (built-in) | Agencies wanting multi-tenant |
| Mailtrain | Free (GPL) | Node.js | No | Node.js shops |
| Sendy | ~$69 one-time | PHP | No | SES-only workflows, lowest cost |
| BillionMail | Free OSS | PHP | No | Early adopters (new 2024+) |
| OpenEMM | Free (AGPL) | Java | Yes | Java enterprise / govt |
| phpList | Free (AGPL) | PHP | No | Longest-running stable option |

## Pick by what matters

- **Lowest 5-year cost** → AcelleMail / MailWizz / Listmonk
- **Modern dev experience** → AcelleMail (Laravel 11) / Mailcoach
- **Agency / SaaS reseller** → AcelleMail Extended / SendPortal
- **Pure newsletter** → Listmonk / Sendy / phpList
- **AI features built-in** → AcelleMail (via the Acelle AI service)
- **Multi-channel SMS/WhatsApp** → AcelleMail (via Messenger plugin)

## Per-platform summary

### 1. AcelleMail
Laravel 11 self-hosted platform, on CodeCanyon since 2016. Regular $80 covers self-use unlimited subscribers; Extended $199 adds multi-tenant SaaS layer for agencies. 8 sending drivers built-in (SES, SendGrid, SparkPost, Mailgun, Postmark, Elastic Email, Blastengine, SMTP). 4-pattern Hook system for extensions. Acelle AI service + Messenger multi-channel plugin available.

### 2. MailWizz
PHP/Yii framework, on CodeCanyon since ~2014. Mature feature set, established community, regular maintenance releases. ~$79 Regular, ~$399 Extended.

### 3. Listmonk
Free open-source (AGPL), Go backend + Vue frontend, 15K+ GitHub stars. Single-binary install, fast, modern stack. AGPL means modifications must be open-sourced if deployed publicly.

### 4. Mailcoach
Laravel-based, built by Spatie. Sold as annual subscription ($99-$249/yr). Tight integration with Spatie's Laravel package ecosystem.

### 5. Mautic
Free open-source (GPL), PHP/Symfony, Acquia-backed since 2019. Full marketing-automation suite (lead scoring, landing pages, multi-channel). Larger scope than email-only platforms.

### 6. SendPortal
Open-source Laravel platform with multi-tenancy built into the core data model. Newer + smaller community than established players.

### 7. Mailtrain
Free open-source (GPL), Node.js. AWS SES + SMTP support, RSS-driven campaigns. Good fit for Node.js shops.

### 8. Sendy
~$69 one-time, PHP, laser-focused on Amazon SES. Single-purpose: bulk newsletters through SES with deep integration (bounces, complaints, send-rate tuning).

### 9. BillionMail
Newer (2024+) free open-source PHP option, smaller community.

### 10. OpenEMM
Free open-source (AGPL), Java, Agnitas-maintained. European enterprise + government use case.

### 11. phpList
Longest-running open-source newsletter manager (since 2000). Mature, stable, focused on simple newsletter sends.

## Feature comparison matrix

| Feature | AcelleMail | MailWizz | Listmonk | Mailcoach | Mautic | SendPortal | Mailtrain | Sendy | phpList |
|---|---|---|---|---|---|---|---|---|---|
| Drag-and-drop builder | ✅ | ✅ | 🟡 | ✅ | ✅ | ✅ | 🟡 | 🟡 | 🟡 |
| Visual automation | ✅ | ✅ | ❌ | ✅ | ✅ | 🟡 | 🟡 | ❌ | 🟡 |
| Multi-tenant / SaaS | ✅ | ✅ | ❌ | 🟡 | 🟡 | ✅ | ❌ | ❌ | ❌ |
| REST API | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 | ✅ |
| Plugin SDK | ✅ | ✅ | 🟡 | 🟡 | ✅ | 🟡 | 🟡 | ❌ | ✅ |
| Multiple sending drivers | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 | ❌ | ✅ |
| Built-in AI features | ✅ | ❌ | ❌ | ❌ | 🟡 | ❌ | ❌ | ❌ | ❌ |
| Multi-channel (SMS/WA) | ✅ | ❌ | ❌ | ❌ | 🟡 | ❌ | ❌ | ❌ | ❌ |
| One-time license | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| Free (OSS) version | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |

Last updated May 2026. Verify against vendor's current changelog before committing.

## Honest verdict

- **Pick AcelleMail** if you want a modern Laravel 11 platform with one-time license, {{terms.license.updates_months}} months of updates, plugin SDK, and first-party AI + multi-channel add-ons.
- **Pick Listmonk** if you want free + minimal + happy to handle deliverability + comfortable with AGPL.
- **Pick Mautic** if you want full marketing-automation suite (not just email) and don't mind heavier setup.
- **Pick MailWizz** if you're already in the Yii ecosystem and want a long-established CodeCanyon listing.
- **Pick Mailcoach** if your stack is Laravel + Spatie packages and annual subscription is preferred over one-time license.
- **Pick SendPortal** if multi-tenant from day one is non-negotiable.
- **Pick Sendy** if you only need SES-bulk-newsletter and want the lowest entry cost.

Try AcelleMail's live demo (no signup): https://acellesend.com/demo
Get AcelleMail Regular License: https://acellesend.com/go/buy


---

## /demo.md

# AcelleMail Live Demo

**Direct sandbox URLs** (auto-login, no signup required):
- Marketer demo: <https://demo.acellesend.com/demo/go/frontend>
- Admin demo: <https://demo.acellesend.com/demo/go/backend>

Both open the actual AcelleMail platform with pre-loaded data. The sandbox resets nightly so anything you create gets wiped.

## What the demo proves

AcelleMail is a **self-hosted, one-time license** email marketing platform (Laravel + PHP, source code included). The demo runs on `demo.acellesend.com` so you can poke at the same software you'd install on your own server after buying.

## Two demo accounts

### Marketer demo (frontend)
Login auto-fills with `emma@awesome-solutions.jp`. You'll see the platform from a marketing-team perspective:

- Build & send a campaign in 3 minutes
- Try the drag-and-drop email builder
- Trigger an automation (welcome flow, abandoned-cart sequence)
- A/B test subject lines on a real list
- Open analytics + per-campaign reports

### Admin demo (backend)
Login auto-fills with `michael@nanosoft.tech`. You'll see the operator/admin control panel:

- Provision customers + assign plans
- Configure Amazon SES, SendGrid, or any SMTP service
- Set up Stripe / PayPal / Braintree payment gateways
- Install plugins (AI, Messenger, custom)
- Inspect REST API + Webhook payloads

## Pricing after the demo

One-time license, no recurring fees, unlimited subscribers:
- **Regular License**: $80 — for your own business
- **Extended License**: $199 — for reselling email marketing as a service (charge end-users)

Buy: <https://acellesend.com/go/checkout?plan=regular>

## What the demo is NOT

- ❌ Not a free trial of a hosted service (we don't host for you long-term — buy the license, install on your server)
- ❌ Not data-isolated from other demo visitors (sandbox resets nightly; don't use real customer data)
- ❌ Not feature-limited — full platform, full features

## Related agent skills

- `pricing-lookup` at `/agent/v1/pricing` (JSON) — exact prices + licensing tiers
- `compare-vs` at `/vs/{competitor}` (HTML) — comparison vs Mailchimp/SendGrid/Brevo/Klaviyo/Mautic/Listmonk
- `kb-search` at `/agent/v1/kb-search` (JSON) — searchable knowledge base


---

## /email-marketing.md

# AcelleMail — email marketing

> Canonical HTML: https://acellesend.com/email-marketing
> Content-Type: text/markdown

Run every campaign — newsletter, promotional, transactional, drip — from one self-hosted dashboard, on any SMTP provider, with no contact-tier ceiling.

## Campaign workflow

1. **Build** with the drag-and-drop editor, pick from 100+ templates, or paste raw HTML
2. **Segment** — combine fields, tags, and behavior with AND/OR/NOT
3. **Test** — send to a preview address, run A/B variants, validate spam score
4. **Schedule** by timezone or send immediately
5. **Track** opens, clicks, bounces, unsubscribes, conversions in real time
6. **Re-engage** — resend to non-openers, retarget by segment

## Deliverability

- **SPF / DKIM / DMARC** wizard guides you through every DNS record with copy-paste output
- **Bounce + complaint loops** auto-update subscriber status from SES, SendGrid, Mailgun, Postmark webhooks
- **List-Unsubscribe (RFC 8058 + RFC 2369)** auto-emitted on every send — Gmail and Apple Mail show native one-click unsubscribe
- **Warm-up scheduler** ramps daily send volume gradually on new IPs to build sender reputation
- **Suppression list** (global + per-list) prevents accidental re-sends to opted-out addresses

## Sending engines

Plug in any of these SMTP providers with two clicks:

- Amazon SES (recommended for volume)
- SendGrid · Mailgun · SparkPost · Postmark · Elastic Email
- Mailjet · MailerSend · Sendinblue/Brevo
- Any RFC-compliant SMTP server

Multiple servers can be configured simultaneously with round-robin or priority routing — handy for splitting marketing traffic from transactional.

## Subscriber identity

- **Custom fields** of any type
- **Tags** for fast cross-list segmentation
- **Activity timeline** per subscriber (opens, clicks, automations, list changes)
- **Custom-attribute search** for support / GDPR-DSAR workflows

## What's NOT in scope

AcelleMail is the marketing UI; it does not directly send emails — your SMTP transport does. AcelleMail is **not** a transactional API replacement for SendGrid / SES — it sits on top of them. For 1:1 transactional flows from your app, talk to the SMTP transport directly or hook into the AcelleMail REST API.

## Next

- [Features overview](https://acellesend.com/features.md)
- [Automation](https://acellesend.com/automation.md)
- [Integrations](https://acellesend.com/integrations.md)
- [Pricing](https://acellesend.com/pricing.md)


---

## /features.md

# AcelleMail — features

> Canonical HTML: https://acellesend.com/features
> Content-Type: text/markdown

A complete, self-hosted email marketing application. Every feature ships in the base product — no upsells, no feature gates by license tier.

## Campaign creation

- **Drag-and-drop email builder** with live preview, mobile/desktop toggles, and undo history
- **100+ ready-made templates** spanning newsletters, e-commerce, onboarding, transactional
- **HTML / plaintext / both modes** with auto-generated plaintext fallback
- **Inline image upload** with auto-CDN-friendly paths
- **A/B testing** on subject line, sender name, or full body — auto-winner selection
- **Schedule per timezone** or send immediately
- **Resend to non-openers** with one click

## List management

- **Unlimited contacts** per list (your server's DB capacity is the only ceiling)
- **Custom fields** of any type (text, number, date, dropdown)
- **Segmentation** — combine fields, tags, activity history with AND/OR/NOT logic
- **Tags** with bulk apply/remove
- **CSV / XLS import + export**
- **API-driven sync** via the REST API
- **Subscriber activity log** (opens, clicks, bounces, unsubscribes timestamp)

## Automation engine

- **Trigger-based workflows** — signup, tag added, custom event, date relative, link clicked
- **Drip sequences** with per-step delays (minutes / hours / days / weeks)
- **Conditional branches** based on subscriber attributes or behavior
- **Goal tracking** + workflow analytics
- **Pause / resume / re-enter** per subscriber

## Deliverability tooling

- **SPF / DKIM / DMARC** setup wizard with copy-paste DNS records
- **Bounce handling** auto-cleans hard bounces, retries soft bounces
- **Unsubscribe + List-Unsubscribe header** (RFC 8058 + RFC 2369)
- **Feedback loops** for major ISPs (Yahoo, Microsoft, AOL)
- **Warm-up scheduler** to ramp send volume on new IPs
- **Real-time bounce + complaint webhooks** from major SMTP vendors

## Analytics

- **Open + click maps** with geo data
- **Funnel reports** for automation goals
- **Comparison reports** across campaigns or time windows
- **Subscriber engagement scoring**
- **CSV export** for any report

## SaaS multi-tenant (Extended License)

- **Customer accounts** with isolated lists, campaigns, sending servers
- **Plan + subscription management** with Stripe / PayPal / Braintree integration
- **Per-plan quotas** (sending limit, list cap, automation cap)
- **Reseller dashboard** with revenue, churn, MRR reporting
- **White-label** every screen + email + invoice

## Developer surfaces

- **REST API v1 + v2** covering lists, subscribers, campaigns, automations, reports
- **Webhook events** for subscribe / unsubscribe / open / click / bounce / complaint
- **Plugin system** with hooks at every major UI surface — load custom code without forking
- **Integrations** with Amazon SES, SendGrid, SparkPost, Mailgun, Postmark, Elastic Email

## Try it

- [Live demo (1-click test account)](https://acellesend.com/demo)
- [Pricing — USD 74 Regular / USD 199 Extended](https://acellesend.com/pricing)
- [Buy AcelleMail](https://acellesend.com/go/checkout?plan=regular)


---

## /guide-cost-savings.md

# Guide — email-marketing cost savings

> Canonical HTML: https://acellesend.com/guide/email-marketing-cost-savings
> Content-Type: text/markdown

The single biggest reason customers move from SaaS to self-hosted email marketing is the **cost curve**. SaaS price grows with subscriber count and send volume; self-hosted grows almost flat.

## The math

Take a realistic mid-market scenario: 50,000 contacts, 4 sends per month = 200,000 emails monthly.

### SaaS option (Mailchimp Standard tier)

- Standard tier at 50k contacts: ~USD 350 / month
- Year 1: ~USD 4,200
- Year 5: ~USD 21,000

### Self-hosted option (AcelleMail + Amazon SES)

- AcelleMail Regular License: USD 74 (one-time)
- Server (DigitalOcean basic Droplet or similar): USD 24 / month = USD 288 / year
- Amazon SES: USD 0.10 per 1,000 emails × 200k = USD 20 / month = USD 240 / year
- Year 1: USD 74 + USD 528 = **USD 602**
- Year 5: USD 74 + (5 × USD 528) = **USD 2,714**

### Five-year delta: ~USD 18,000 in savings

That's the average cost of a mid-tier marketing manager's salary for a quarter. For most teams the AcelleMail license pays for itself in **week 1** at this volume.

## When the math shifts

**SaaS wins** below ~5,000 contacts with very light sending. Mailchimp Free tier is 500 contacts / 1,000 sends/mo — fine for a one-person Substack starting out.

**Self-hosted dominates** above 10,000 contacts or any regular weekly sending. The break-even is sharp.

## Hidden costs to watch

The honest comparison includes:

| Cost class | SaaS | Self-hosted |
|---|---|---|
| License / subscription | USD 100-1,000+/mo | USD 74 once |
| SMTP transport | Bundled | USD 0.10/1k via SES (or similar) |
| Server | n/a | USD 5-50/mo VPS |
| Setup time (one-time) | 1 hour | 4-8 hours (or follow a KB guide for ~1 hour with Docker) |
| Maintenance time | n/a | 1-2 hours/quarter (patches, monitoring) |
| Deliverability tuning | Vendor handles | You handle (SPF/DKIM/DMARC wizard built-in) |
| Migration in / out | Easy in, hard out | Easy in + out (CSV) |
| Vendor lock-in risk | High | Zero |

For most teams, the maintenance tax is <USD 100 of engineer time per quarter against ~USD 1,000+/mo of SaaS savings.

## Five tips to maximize savings

1. **Use Amazon SES as transport.** At USD 0.10 / 1,000 emails it's the price floor; deliverability is excellent if you warm up correctly.
2. **Right-size your server.** A 2-vCPU / 4 GB VPS handles 100k subscribers + active sending. Don't over-provision early.
3. **Use the suppression list aggressively.** Bounce + complaint cleanup means lower send volume without losing reach.
4. **Build automations to replace blast campaigns.** Targeted automation often outperforms generic blasts at a fraction of the send volume.
5. **Don't pay for AI features you don't need.** Acelle AI plugin supports a self-hosted Ollama backend at zero per-call cost.

## Next

- [Pricing](https://acellesend.com/pricing.md)
- [Guide — self-hosted overview](https://acellesend.com/guide-self-hosted.md)
- [Guide — deliverability](https://acellesend.com/guide-deliverability.md)
- [vs Mailchimp](https://acellesend.com/vs-mailchimp.md)


---

## /guide-deliverability.md

# Guide — email deliverability

> Canonical HTML: https://acellesend.com/guide/email-deliverability
> Content-Type: text/markdown

Deliverability is the single biggest determinant of whether an email-marketing program succeeds. A USD 350/mo SaaS account with poor sender reputation is worse than a USD 74 self-hosted setup with proper SPF/DKIM/DMARC and a warmed IP.

## The deliverability stack

```
Authentication (SPF + DKIM + DMARC)
        ↓
Sender reputation (IP + domain history)
        ↓
Content quality (no spam triggers, clean HTML)
        ↓
Engagement (opens + clicks + low bounce)
        ↓
List hygiene (no stale addresses, no purchased lists)
        ↓
Recipient inbox
```

If any layer is weak, the whole stack underperforms. AcelleMail's role is to make every layer **operationally easy** on a self-hosted deployment.

## Authentication

### SPF

Tells receiving servers which IPs are allowed to send for your domain. Single TXT record on your DNS. AcelleMail's wizard generates the exact record based on your chosen SMTP transport.

### DKIM

Signs every outgoing message with a cryptographic key proving the sender owns the domain. AcelleMail generates the public/private keypair and outputs the DNS record. Rotate keys quarterly for best hygiene.

### DMARC

Tells receivers what to do with messages that fail SPF and DKIM (quarantine or reject), and where to report aggregated stats. Start with `p=none` to monitor, ramp to `p=quarantine` once you confirm 100% pass rate, then `p=reject` for full enforcement.

## Sender reputation

- **Warm up new IPs** gradually — AcelleMail has a built-in warm-up scheduler that ramps daily volume over 4-8 weeks.
- **Avoid IP-reputation cliffs** — don't switch transports mid-campaign; rotate at quiet windows.
- **Monitor with Postmaster Tools** (Google + Microsoft + Yahoo offer them) and the AcelleMail dashboard's bounce/complaint chart.

## Bounce + complaint handling

- **Hard bounces** (550 / 5xx) — auto-removed from active list after first occurrence
- **Soft bounces** (4xx) — retried with backoff; removed after N consecutive
- **FBL complaints** — auto-update subscriber status to `unsubscribed` via webhook from SES/SendGrid/Mailgun/Postmark

## Unsubscribe handling

- **RFC 8058 one-click** unsubscribe — Gmail and Apple Mail render a native button in their UI
- **RFC 2369 List-Unsubscribe** mailto + URL emitted in every send
- **Signed routes** — unsubscribe URLs are signed via Laravel's URL signing, preventing enumeration attacks

## List hygiene

- **Never buy lists.** It tanks reputation in days and is illegal in most jurisdictions.
- **Run pre-send verification** on new signups via the Athena EVS engine — catches typos and trap addresses.
- **Sunset unengaged subscribers** — automation rule: subscribers with zero opens in 180 days → re-engagement campaign → if still no engagement, suppress.

## Content

- **Plaintext alternative** auto-generated for every HTML send
- **Avoid spam triggers** in subject + body (excessive caps, multiple !!!, "Free money" patterns)
- **Image:text ratio** should stay reasonable — pure-image emails get filtered
- **Personalize** the first paragraph — engagement metrics rise, deliverability follows

## Monitoring

- **Inbox-placement tests** before big sends (paid tools like Litmus, GlockApps; free spot-check by sending to multiple personal accounts across ISPs)
- **Bounce + complaint dashboard** inside AcelleMail
- **DMARC aggregate reports** parsed by a third-party tool (Postmark's free service is a good entry point)

## Next

- [Features (includes deliverability tooling)](https://acellesend.com/features.md)
- [Security](https://acellesend.com/security.md)
- [Guide — self-hosted overview](https://acellesend.com/guide-self-hosted.md)
- [KB — deliverability category](https://acellesend.com/kb/category/sending-deliverability)


---

## /guide-gdpr.md

# GDPR + email marketing — consent, data residency, and compliance

GDPR doesn't make email marketing harder — it makes the sloppy parts illegal.

Informational only — not legal advice. Consult your DPO or counsel for jurisdiction-specific decisions.

## Lawful basis

For unsolicited marketing email to consumers, **consent (Article 6(1)(a))** is the only safe choice in most member states. Soft-opt-in (Article 6(1)(f) "legitimate interest") survives narrowly for existing-customer follow-ups but is defensible-but-fragile. Build for consent.

Consent under GDPR must be: freely given (no pre-ticked boxes), specific, informed, unambiguous, and **documented** — you must demonstrate consent on request.

Operational implication: store consent timestamp + IP + source URL + exact consent text at moment of opt-in. AcelleMail's subscriber model captures these by default.

## Data residency

Article 44+ restricts transfers to "third countries" unless protected by SCCs or adequacy decision. After Schrems II (2020), the bar for US-bound transfers stays high.

Three paths in 2026:

1. **EU-region self-hosting** — Hetzner Germany, OVH France, Scaleway Paris, AWS eu-central-1 / eu-west-1. Data never leaves EEA. No SCCs needed for the app layer; you DO need them for non-EU sending relays.
2. **EU-region SaaS** — Mailchimp EU data center, Brevo (FR), MailerLite (LT). One vendor relationship, one DPA.
3. **US SaaS + SCCs + supplementary measures** — possible but operationally heavy after Schrems II.

For EU audiences, Path 1 — self-hosting AcelleMail on an EU VPS routed through AWS SES Frankfurt — is the lowest-friction GDPR posture.

## Processors & DPAs

Article 28 requires a written DPA with every entity that processes personal data on your behalf.

For a self-hosted AcelleMail stack, the typical processor chain is:

- **VPS provider** (Hetzner / DigitalOcean / OVH / AWS) — they process DB rows.
- **Sending relay** (SES / SendGrid / Mailgun / Postmark) — they process recipient email + subject + body during transit.
- **Analytics / CRM tie-ins** — only if you forward subscriber data downstream.

Sign each. With all-in-one SaaS, you sign the ESP's DPA + verify their sub-processor list.

## Subscriber rights

The five rights that matter most for email marketing:

- **Right to withdraw consent (Art. 7(3))** — one-click unsubscribe in every email.
- **Right of access (Art. 15)** — copy of all data you hold.
- **Right to erasure (Art. 17)** — delete the row + propagate to suppression list. 30-day SLA.
- **Right to data portability (Art. 20)** — JSON or CSV export.
- **Right to object (Art. 21)** — opt out of all marketing OR specific types.

## Operational patterns

- **Double opt-in flow** — insert with `confirmed_at = NULL` → send confirmation email with signed-URL link → on click set `confirmed_at = now() + consent_ip + consent_user_agent + consent_source` → welcome series.
- **Suppression list** — separate table storing email-hashes (not plaintext). Check before every send.
- **Breach notification** — 72 hours to supervisory authority (Article 33). Audit-log + DB access log + written incident-response runbook.

## Where to go next

- [Self-hosted pillar — full architecture](https://acellesend.com/guide-self-hosted.md)
- [Stack guide — picking processors](https://acellesend.com/guide-stack-2026.md)
- [Deliverability fundamentals](https://acellesend.com/guide-deliverability.md)
- [Security & GDPR features](https://acellesend.com/security.md)


---

## /guide-self-hosted.md

# Guide — self-hosted email marketing

> Canonical HTML: https://acellesend.com/guide/self-hosted-email-marketing
> Content-Type: text/markdown

## What "self-hosted email marketing" means

Self-hosted email marketing means the marketing application — the dashboard, builder, automation engine, analytics, subscriber database — runs on infrastructure **you control**, instead of on a SaaS vendor's cloud. The application sends through an SMTP transport (Amazon SES, SendGrid, Mailgun, or any RFC-compliant SMTP) that you also choose and configure.

## Why customers go self-hosted

1. **Data sovereignty.** Subscriber PII stays in your jurisdiction, under your data-protection regime, never transits a vendor.
2. **Cost predictability.** SaaS email-marketing scales cost with subscriber count or send volume. Self-hosted scales cost with the **underlying SMTP rate** (e.g. Amazon SES at USD 0.10 per 1,000 emails) plus a flat infrastructure bill.
3. **Source control.** Source-available self-hosting means full code access — modify any screen, integrate with any system, white-label freely.
4. **Vendor independence.** Switch SMTP transports without touching the marketing UI. Vendor outage on SendGrid? Re-route to SES the same day.
5. **Compliance.** GDPR / CCPA / sector-specific rules are easier when data is under your roof and you control the audit trail.

## Trade-offs (be honest)

| | Self-hosted | SaaS |
|---|---|---|
| First-day setup | Hours (server + SMTP config + DNS) | Minutes |
| Day-to-day ops | You patch + monitor + scale | Vendor handles it |
| Cost at scale | Wins above ~10k contacts or active sending | Wins below ~5k contacts with light sending |
| Deliverability tuning | You own SPF/DKIM/DMARC + FBL handling | Vendor handles it |
| Failover | Your responsibility (or your hosting provider's) | Vendor SLA |

## When self-hosted is the right call

- You have ≥ 10,000 subscribers (or expect to within 12 months)
- You send regularly (at least weekly), not once a quarter
- You have an ops engineer or comfortable sysadmin
- You'd rather pay USD 74 once + USD 5-50/mo server than USD 100-1,000+/mo SaaS

## When SaaS is the right call

- You have a list under 2,000 and send ad-hoc
- You don't have ops capacity at all
- You need bundled integrations (Shopify connector, Salesforce sync) that come pre-wired in SaaS but DIY in self-hosted

## How to start with AcelleMail

1. **Pick a server.** Any modern Linux VPS — DigitalOcean / Hetzner / Linode / AWS EC2. A 2-vCPU / 4 GB box handles 100k subscribers comfortably.
2. **Install AcelleMail.** USD 74 one-time on CodeCanyon. KB has 1-day setup guides for Ubuntu, cPanel, and Docker.
3. **Pick an SMTP transport.** Amazon SES is the price/deliverability sweet spot for most teams.
4. **Configure SPF + DKIM + DMARC.** Built-in wizard walks you through the DNS records.
5. **Import your list.** CSV or REST API.
6. **Send your first campaign.** Start with a re-engagement to warm IPs gradually.

## Next

- [Pricing](https://acellesend.com/pricing.md)
- [Features](https://acellesend.com/features.md)
- [Guide — cost savings](https://acellesend.com/guide-cost-savings.md)
- [Guide — deliverability](https://acellesend.com/guide-deliverability.md)


---

## /guide-stack-2026.md

# The email marketing stack in 2026 — tools, architecture, and tradeoffs

Modern email-marketing systems are stacks of three to five interchangeable layers. Picking the right pieces — and deciding which layers you own versus rent — drives both cost-per-send and deliverability ceiling.

## The five layers

1. **Marketing app (ESP).** List management, campaign authoring, automation flows, segmentation, analytics. The user-facing surface.
2. **Sending pipeline.** The MTA + IP reputation. Most teams hand this to a managed relay (Amazon SES, SendGrid, Mailgun, Postmark, SparkPost).
3. **Deliverability tooling.** SPF / DKIM / DMARC records, FBL processing, postmaster dashboards, seed-list testing.
4. **Analytics + attribution.** UTM tagging, link tracking, downstream revenue tie-back.
5. **Consent + identity.** Opt-in source, double opt-in, preference center, suppression list, GDPR + CAN-SPAM.

The big question: which layers do you OWN vs RENT? Big-SaaS rents all five. Self-hosted operators own 1, 3, 4, 5 — and rent layer 2 from a cloud SMTP. That split — own the data, rent the sending muscle — is the dominant 2026 pattern.

## Three reference architectures

- **A. All-in-one SaaS** (Mailchimp / Brevo / Klaviyo). One signup, all five layers. Right for under 5K contacts with low engineering capacity.
- **B. Marketing app + cloud SMTP** (AcelleMail / Listmonk / Mautic on a $5–$20 VPS, sending through SES). Owner controls the data + campaign UX; relay handles IP reputation. Cost-per-send approaches $0.10 per 1,000 emails at scale.
- **C. Transactional API + custom marketing tooling** (SendGrid API / Postmark / Mailgun + internal app). Used by SaaS products where most email is transactional.

## Buy, build, or compose?

- **Buy (A)** — under 5K contacts, low engineering capacity, ship-speed matters more than cost.
- **Compose (B)** — over 5K contacts, can stand up a VPS, want one-time license + linear cost-per-send. AcelleMail + SES is the canonical example.
- **Build** — only if your business IS the marketing platform. Otherwise composing existing pieces is dramatically faster.

The common 2026 mistake: staying on Architecture A past the cost-curve inflection. Architecture B at 5K–50K contacts pays back the migration time in under a year.

## Tool inventory by layer

- **Self-hosted marketing apps.** AcelleMail (source-available), Listmonk, Mautic, Postal, Sendy.
- **Marketing apps (SaaS).** Brevo, Campaign Monitor, ConvertKit / Kit, Klaviyo, Mailchimp, MailerLite, Sendinblue.
- **Cloud SMTP relays.** Amazon SES, Mailgun, MailerSend, Postmark, SendGrid, SparkPost.
- **Self-managed MTAs.** exim, Haraka, Postal, Postfix.
- **Seed-list testers.** GlockApps, Inbox-Insight, Mail-Tester.
- **Postmaster dashboards.** Apple iCloud Postmaster, Gmail Postmaster Tools, Microsoft SNDS, Yahoo SmartNetwork.

## Where to go next

- [Self-hosted email-marketing pillar](https://acellesend.com/guide-self-hosted.md)
- [Cost-savings deep dive](https://acellesend.com/guide-cost-savings.md)
- [Deliverability fundamentals](https://acellesend.com/guide-deliverability.md)
- [Side-by-side: AcelleMail vs Mailchimp](https://acellesend.com/vs-mailchimp.md)


---

## /home.md

# AcelleMail — self-hosted email marketing platform

> Canonical HTML: https://acellesend.com/
> Content-Type: text/markdown
> Source-of-truth overview: https://acellesend.com/llms.txt

AcelleMail is a Laravel-based, self-hosted email marketing application, first released in 2016 and sold as a one-time license. Trusted by thousands of customers worldwide · **v4.2.0 LTS**.

## Why customers pick AcelleMail

- **One-time payment, no recurring fees.** Regular License USD 74 (single domain) or Extended License USD 199 (SaaS rights).
- **Unlimited contacts + unlimited sends.** No subscriber tier, no monthly send cap. You pay only the underlying SMTP cost (e.g. Amazon SES at USD 0.10 per 1,000 emails).
- **Full source code.** Complete PHP/Laravel source ships to the buyer (source-available, not OSI open-source). Modify, white-label, redeploy.
- **Subscriber data stays on your server.** GDPR-friendly by architecture — nothing transits a vendor.
- **Vendor-agnostic SMTP.** Connect Amazon SES, SendGrid, SparkPost, Mailgun, Postmark, Elastic Email, or any RFC-compliant SMTP.

## What's included

- Drag-and-drop visual email builder + 100+ templates
- Marketing automation: trigger-based workflows, drip sequences, conditional logic
- A/B testing for subject lines and bodies
- Advanced list segmentation + tagging
- Real-time campaign analytics (opens, clicks, bounces, unsubscribes, conversions)
- REST API for custom integrations
- Multi-tenant SaaS mode (Extended License) with Stripe / PayPal / Braintree billing
- SPF / DKIM / DMARC setup helpers + deliverability tooling

## Buy or try

- **Buy (1-click checkout):** https://acellesend.com/go/checkout?plan=regular
- **Live demo (auto-issues a test account):** https://acellesend.com/demo
- **Knowledge base:** https://acellesend.com/kb
- **Contact support:** admin@acellesend.com (typical 24h response on business days)

## How AcelleMail compares (one-line each)

- **vs Mailchimp:** Mailchimp = SaaS, USD 13-350+/mo, contact-tier capped. AcelleMail = USD 74 once, unlimited contacts.
- **vs SendGrid / Mailgun / SES / Postmark:** those are *transport* services. AcelleMail is the *marketing UI* on top of them.
- **vs listmonk:** listmonk is free + dev-oriented (CLI / SQL). AcelleMail is paid (USD 74) but turnkey UI + drag-drop + automation + vendor email support.
- **vs Mautic:** Mautic is free + heavier marketing-automation suite. AcelleMail is simpler, email-focused, faster to deploy.

## Requirements

Self-hosted on your own Linux server with PHP 8.1+, MySQL 5.7+ / MariaDB 10.3+, and nginx or Apache. SSH access required for install. See `/kb/category/getting-started`.

## Deep dives

- [Features](https://acellesend.com/features.md)
- [Email Marketing](https://acellesend.com/email-marketing.md)
- [Automation](https://acellesend.com/automation.md)
- [Integrations](https://acellesend.com/integrations.md)
- [Pricing](https://acellesend.com/pricing.md)
- [Security](https://acellesend.com/security.md)
- [About](https://acellesend.com/about.md)


---

## /integrations.md

# AcelleMail — integrations

> Canonical HTML: https://acellesend.com/integrations
> Content-Type: text/markdown

## SMTP transports (email sending)

Wire up any RFC-compliant SMTP service in two clicks. Verified integrations include:

| Vendor | Notes |
|---|---|
| Amazon SES | Recommended for volume — USD 0.10 / 1,000 emails. Webhook handler for bounces + complaints included. |
| SendGrid | Twilio-owned. Free tier available; usage-based pricing. |
| Mailgun | EU + US regions. Free tier available. |
| SparkPost | High-deliverability infrastructure. |
| Postmark | Premium transactional + marketing. |
| Elastic Email | Cost-effective for medium volume. |
| Mailjet | EU-based, GDPR-friendly. |
| MailerSend | Modern API + analytics. |
| Sendinblue / Brevo | All-in-one with SMS option. |
| Any RFC-compliant SMTP | Self-hosted Postfix, Halon, etc. |

## Payment gateways (Extended License — SaaS mode)

For multi-tenant SaaS deployments charging end-users:

- **Stripe** — card payments, subscriptions, 3DS, dispute handling
- **PayPal** — classic and Braintree-managed flows
- **Braintree** — card + PayPal + Apple Pay
- **Paystack** — Africa-region card payments
- **Razorpay** — India-region card + UPI + netbanking
- **Offline / manual** — admin-approved bank-transfer flow

## Verification engines

For pre-send list cleaning:

- **Athena EVS** — built-in verification engine (self-hosted)
- Bulk verify with CSV export of results
- Webhook-driven per-subscriber verify on signup

## REST API

- **API v1** — covers lists, subscribers, campaigns, automations, reports
- **API v2** — newer, with consistent JSON envelopes and OAuth-style bearer tokens (Laravel Sanctum)
- **Webhook subscriptions** for outbound events: subscribe / unsubscribe / open / click / bounce / complaint / campaign-status

Docs: https://acellesend.com/kb/category/developer-guide

## Plugin system

Drop-in plugins extend AcelleMail without forking core. Two first-party plugins ship:

- **Acelle AI** — chatbox, content rewrite, page generation; Anthropic / OpenAI / Ollama / Acelle AI hub backends
- **Acelle Messenger** — SMS (Twilio) + WhatsApp (Meta Cloud API) + Telegram channels alongside email

Third-party plugins register via the standard hook + service-provider pattern.

## Next

- [Features](https://acellesend.com/features.md)
- [Email Marketing](https://acellesend.com/email-marketing.md)
- [Pricing](https://acellesend.com/pricing.md)
- [Security](https://acellesend.com/security.md)


---

## /official-sources.md

# Official AcelleMail Sources — Buy & Download Safely

> Canonical HTML: https://acellesend.com/official-sources
> Content-Type: text/markdown

The only official source for AcelleMail is the official website **acellesend.com** — you buy a license there and download it from your own account. AcelleMail is paid, source-available software sold under a one-time license — **there is no free, "nulled", or open-source edition**. Any site offering a free or cracked download is distributing an unlicensed copy with no updates, no support, and a real risk of injected malware.

## Official channels

- **Buy (official):** https://acellesend.com/go/checkout?plan=regular — one-time license, full source code, {{terms.license.updates_months}} months of updates, {{terms.license.support_months}} months support.
- **Official website:** https://acellesend.com/
- **Live demo (no purchase):** https://acellesend.com/demo
- **Support:** https://acellesend.com/contact · admin@acellesend.com

## Why avoid "nulled" / cracked / free-download copies

- **Security risk** — pirated PHP apps commonly carry injected backdoors, credential stealers, and spam scripts, running on the same server as your subscriber data.
- **No security updates** — a genuine license gets patches through your account; a nulled copy is frozen at a cracked, often-vulnerable version.
- **No support** — nulled users cannot open tickets or get setup/deliverability help.
- **License violation** — using or redistributing an unlicensed copy breaches the AcelleMail license.

## How to verify a genuine copy

- Purchased through your own account on acellesend.com — the license is listed there.
- Updates apply directly from your account for the licensed product.
- Source is obtained from acellesend.com — not a third-party "free" mirror.

## Common questions

**Is there a free version of AcelleMail?** No. It is paid software with a one-time license; the full source ships to the buyer (source-available), but it is not free or open-source. The closest no-cost option is the live demo.

**Is it safe to use a nulled or cracked AcelleMail?** No — cracked copies routinely contain malware and never receive security updates, a serious risk for software that handles subscriber lists and sends email from your domain.

**Where should I download AcelleMail?** Only from acellesend.com — buy a license and the download becomes available in your account.


---

## /pricing.md

# AcelleMail — pricing

> Canonical HTML: https://acellesend.com/pricing
> Content-Type: text/markdown

One-time payment. No recurring fees. No per-subscriber or per-email charge. You pay the underlying SMTP provider directly (e.g. Amazon SES at USD 0.10 per 1,000 emails) and AcelleMail handles the marketing UI on top.

## License tiers

| Tier | Price | Use case | What's included |
|---|---|---|---|
| **Regular License** | **USD 80** | Single domain, internal or end-client use | Full PHP/Laravel source · {{terms.license.updates_months}} months of updates · {{terms.license.support_months}}-month email support · All features unlocked |
| **Extended License** | **USD 199** | SaaS — you charge end-users | Everything in Regular + multi-tenant rights · Reseller dashboard · Stripe/PayPal/Braintree billing modules |

Pricing verified on CodeCanyon (item id 17796082). Subject to change by the marketplace; click the buy link below for the live rate.

## What's not charged

- ❌ Per-subscriber fee
- ❌ Per-email send fee
- ❌ Monthly subscription
- ❌ Feature gates by tier
- ❌ Support tier upsells

## What you do pay (outside AcelleMail)

- Your server (any Linux VPS — DigitalOcean / Hetzner / AWS EC2 etc., ~USD 5-50/mo depending on volume)
- Your SMTP transport — your choice of vendor. Typical rates:
  - Amazon SES: USD 0.10 / 1,000 emails
  - SendGrid: from USD 0 (free tier) — usage-based after
  - Mailgun: from USD 0 (free tier) — usage-based after
  - Postmark, SparkPost, Elastic Email, or any standards-compliant SMTP also supported

## Why a one-time license beats SaaS

Mailchimp at the 50,000-contact tier costs USD 350+/mo = USD 4,200/year. AcelleMail Regular License pays for itself in **under a week** at that tier; in the second month you have an unlimited-send platform you own, instead of a recurring bill.

## Compare

- [vs Mailchimp](https://acellesend.com/vs-mailchimp.md)
- [vs SendGrid](https://acellesend.com/vs-sendgrid.md)
- [vs Mautic](https://acellesend.com/vs-mautic.md)

## Buy or try

- **Buy (1-click checkout):** https://acellesend.com/go/checkout?plan=regular
- **Live demo:** https://acellesend.com/demo
- **Support plans** (Essential / Plus / Pro / Max — support, updates, plugins, and AI credits for the Acelle AI service from Plus up): https://acellesend.com/support-plans

## Refund policy

Standard CodeCanyon refund policy applies — refund available within the period stated on the listing page if the product cannot deliver as described and support cannot resolve the issue. Contact `admin@acellesend.com` for assistance.


---

## /security.md

# AcelleMail — security

> Canonical HTML: https://acellesend.com/security
> Content-Type: text/markdown

## Self-hosted = data sovereignty

The single largest security difference vs SaaS competitors: **subscriber lists never leave your server**. AcelleMail runs entirely on infrastructure you control. There is no AcelleMail cloud that holds your customer data — the company has nothing to subpoena, breach, or accidentally expose.

## GDPR + privacy posture

- **Self-hosted by design** — subscriber PII stays on your server, in your jurisdiction
- **Subject-access export** built into the subscriber detail screen
- **Right-to-erasure** with one click — full subscriber record + activity log purge
- **Consent logs** for double-opt-in and bulk-invite flows
- **Unsubscribe** RFC 8058 one-click + RFC 2369 list-unsubscribe headers, signed routes
- **No third-party tracking pixels** unless you add them — open + click tracking happens on your server

## Authentication + access control

- **Admin / customer / staff role separation**
- **Two-factor authentication** for admin and customer accounts (TOTP)
- **Password policy** with min length, complexity, breach-list check
- **Session management** with revocable tokens
- **Audit log** of admin actions (login, settings change, subscriber export, plan change)
- **API tokens** via Laravel Sanctum — per-user, per-scope, revocable

## Data at rest + in transit

- **HTTPS only** in production — HSTS supported, HTTP/2 + Brotli recommended (see KB §"Server hardening")
- **Database encryption** for sensitive fields (API keys, OAuth tokens, payment-method secrets) via Laravel Crypt
- **At-rest disk encryption** is your hosting provider's responsibility — instructions in KB
- **Outbound SMTP** via TLS 1.2+ (most major providers default to enforced TLS)

## Email-channel security

- **SPF / DKIM / DMARC** setup wizard with DNS record generator
- **DKIM key rotation** with grace period for in-flight messages
- **Bounce + complaint handling** keeps reputation clean
- **List-Unsubscribe** signed routes prevent enumeration attacks
- **Spam-score preview** before send

## Server hardening

- **HTTPS-only** enforced via nginx config snippet in `/kb/article/server-hardening`
- **Rate limiting** at app + nginx layer for login, registration, password reset, contact form
- **CSRF protection** on every state-changing endpoint
- **CSP + X-Frame-Options + X-Content-Type-Options** headers configurable
- **SQL-injection-proof** — Laravel Eloquent + parameterized queries throughout

## Vulnerability disclosure

Security issues: email `security@acellesend.com` with reproduction steps. Disclosure timeline: typical 14-day patch + public advisory after fix released.

## Compliance

- GDPR: yes (data sovereignty + DSAR tooling)
- CCPA / CPRA: yes (same export + erasure mechanisms)
- CAN-SPAM: yes (unsubscribe + sender identification)
- HIPAA: no — AcelleMail is not HIPAA-certified; do not store PHI in subscriber records

## Next

- [Features](https://acellesend.com/features.md)
- [About](https://acellesend.com/about.md)
- [Pricing](https://acellesend.com/pricing.md)


---

## /vs-mailchimp.md

# AcelleMail vs Mailchimp

> Canonical HTML: https://acellesend.com/vs/mailchimp
> Content-Type: text/markdown

Mailchimp is the canonical SaaS email-marketing platform — easy to start, expensive at scale. AcelleMail is a one-time-license alternative with no contact tiers.

## Headline difference

| | **AcelleMail** | **Mailchimp** |
|---|---|---|
| Pricing model | One-time USD 74 (or 199 SaaS) | Recurring monthly, contact-tier capped |
| Hosting | Self-hosted on your server | Mailchimp's cloud |
| Subscriber cap | None | Tier-capped — overages force tier upgrade |
| Email-send cap | None | Tier-capped |
| Data residency | Your server, your jurisdiction | US-based, subject to Intuit's policies |
| Source code | Full PHP/Laravel ships | Closed-source SaaS |
| Underlying SMTP cost | Vendor of your choice (e.g. SES USD 0.10/1k) | Bundled in tier price |

## Cost at common tiers

For 50,000 contacts with ~4 sends/month:

| Tier | Mailchimp | AcelleMail (Regular) + Amazon SES |
|---|---|---|
| Year 1 | ~USD 4,200 (USD 350/mo Standard) | USD 74 + (200k emails × USD 0.10/1k) = USD 94 |
| Year 5 | ~USD 21,000 | USD 74 (paid once) + USD 400 in SES = USD 474 |

(Mailchimp pricing varies; check live tier page for current rates. Calculation is illustrative.)

## What Mailchimp does better

- **Onboarding** — no install step, sign up and you're in
- **Brand integrations** — Shopify, Squarespace, WooCommerce native connectors out of the box
- **Predictive analytics** — built-in send-time and engagement scoring (Mailchimp has paid features for this)
- **Customer support tier** — paid Premium tier has dedicated support
- **Hosted compliance** — Mailchimp manages the SMTP relationship + deliverability

## What AcelleMail does better

- **Cost at scale** — break-even within weeks of switching from any paid Mailchimp tier
- **Data ownership** — subscriber records never leave your server
- **Unlimited everything** — no list cap, no send cap, no per-feature gates
- **Customization** — full source code; modify any screen, behavior, or integration
- **SaaS mode** (Extended License) — resell email-marketing as your own product, with Stripe/PayPal/Braintree billing built in

## Migration path

1. Export subscribers + lists from Mailchimp as CSV
2. Install AcelleMail on your server (KB has 1-day setup guides for Ubuntu, cPanel, Docker)
3. Configure Amazon SES (or any SMTP) inside AcelleMail
4. Import the CSV → segment + tag mirroring your Mailchimp groups
5. Rebuild key automations (typically 1-2 days for a 5-workflow estate)
6. Update DNS — SPF + DKIM for the new SMTP transport
7. Run a soft-launch warm-up week to maintain reputation

## Try it

- [Live demo](https://acellesend.com/demo)
- [Buy AcelleMail](https://acellesend.com/go/checkout?plan=regular)
- [Pricing](https://acellesend.com/pricing.md)


---

## /vs-mautic.md

# AcelleMail vs Mautic

> Canonical HTML: https://acellesend.com/vs/mautic
> Content-Type: text/markdown

Both are self-hosted marketing platforms — AcelleMail is source-available (full source under a paid one-time license), while Mautic is open-source (free). The big differences are scope, complexity, and how much your team has to operate.

## Headline difference

| | **AcelleMail** | **Mautic** |
|---|---|---|
| License | One-time USD 74 (or USD 199 SaaS) | Free, open-source (community edition) |
| Scope | Focused on email marketing + automation | Full marketing-automation suite (email + landing pages + forms + lead scoring + CRM-lite + social + dynamic content + ABM) |
| Onboarding | Hours to deploy, ready to send | Days to deploy + tune; large surface area to learn |
| Maintenance | Single Laravel app, predictable | Symfony app + many modules + plugin marketplace |
| Vendor support | Included for {{terms.license.support_months}} months with license | Community forums; paid options via Acquia (the corporate sponsor) |
| Build vs install | Drag-drop builder + 100+ templates ship | Builder ships but template library is thinner |
| Best for | Teams who want email done fast | Teams who want a full automation suite and have ops budget |

## When Mautic wins

- You need lead scoring, dynamic content per visitor, marketing-CRM integration, landing pages + forms in one tool
- You have a dedicated marketing-ops engineer who can tune Mautic's surface and keep it patched
- You prefer the freedom of zero license cost and accept the operational tax

## When AcelleMail wins

- You want email marketing done well, fast — no CRM, no landing pages, no lead scoring detour
- You want vendor support included
- You want a multi-tenant SaaS mode (Extended License) with Stripe/PayPal/Braintree billing built in — Mautic does not ship this
- You don't want to operate a complex Symfony deployment

## Migration path (Mautic → AcelleMail)

1. Export contacts from Mautic → CSV
2. Install AcelleMail on a new server (or co-host)
3. Import CSV → AcelleMail lists (preserve tags as custom fields if needed)
4. Recreate automations — Mautic "campaigns" map roughly 1:1 to AcelleMail workflows
5. Point your existing SMTP at AcelleMail
6. Run both in parallel for one cycle, then decommission Mautic

If you also want lead scoring + CRM-lite, the migration is more complex — Mautic may be the better long-term fit.

## Try it

- [Live demo](https://acellesend.com/demo)
- [Buy AcelleMail](https://acellesend.com/go/checkout?plan=regular)
- [Features](https://acellesend.com/features.md)
- [Pricing](https://acellesend.com/pricing.md)


---

## /vs-sendgrid.md

# AcelleMail vs SendGrid

> Canonical HTML: https://acellesend.com/vs/sendgrid
> Content-Type: text/markdown

SendGrid (Twilio) is an **SMTP transport service** — it sends email, hands you delivery stats, exposes an API. AcelleMail is a **marketing UI** that sits on top of any SMTP transport (SendGrid included). They are complementary, not direct competitors.

## When you actually need SendGrid alone

If you already have a custom application that needs to send transactional email (password resets, receipts, alerts) and you want a high-deliverability SMTP API — SendGrid is a fine choice. You don't need AcelleMail for that.

## When AcelleMail (+ SendGrid as transport) wins

If you need a **marketing dashboard** — campaign builder, list segmentation, automation workflows, A/B testing, open/click analytics, drag-and-drop editor — SendGrid itself is thin. The "Marketing Campaigns" tier exists but is expensive and contact-tier-capped:

| Marketing tier | SendGrid Marketing Campaigns (Advanced) | AcelleMail (Regular) + SendGrid Email API (Free 100/day) or Essentials |
|---|---|---|
| 50,000 contacts | USD 250+/mo | USD 74 once + USD ~20/mo SendGrid Essentials = USD 314 year 1, then USD 240/yr from year 2 |
| 100,000 contacts | USD 700+/mo | USD 74 once + USD ~90/mo SendGrid Pro = USD 1,154 year 1 |
| Unlimited contacts | n/a (tier-capped) | USD 74 once + your SMTP rate |

(Rates illustrative; see live SendGrid pricing.)

## Hybrid setup

The popular pattern: use AcelleMail as the marketing application, plug it into SendGrid (or Amazon SES, or any SMTP) as the transport. Best of both worlds:

- AcelleMail gives you the marketing UX, automation, segmentation, reporting
- SendGrid handles deliverability infrastructure, IP warm-up, FBL handling
- You pay USD 74 once for the UI + usage-based SMTP via SendGrid

## Migration path (SendGrid Marketing → AcelleMail + SendGrid transport)

1. Export contacts from SendGrid Marketing Contacts → CSV
2. Install AcelleMail on your server
3. Add SendGrid as an SMTP transport inside AcelleMail (paste the API key)
4. Import the CSV
5. Rebuild templates + automations
6. Update DNS — your SPF/DKIM is already pointed at SendGrid, so deliverability stays intact

## Try it

- [Live demo](https://acellesend.com/demo)
- [Buy AcelleMail](https://acellesend.com/go/checkout?plan=regular)
- [Integrations — SendGrid + other SMTP](https://acellesend.com/integrations.md)
- [Pricing](https://acellesend.com/pricing.md)


---

# Sender Reputation Explained: IP, Domain and Warm-up

> What mailbox providers mean by sender reputation, IP and domain reputation, what moves it, how to warm up, how to check it, and the AcelleMail tools.

- Canonical URL: https://acellesend.com/kb/articles/sender-reputation-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Sender Reputation, Inbox Placement, Bounce Handling
- Published: 2026-10-18 · Last updated: 2026-10-21
- Reading time: 15 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Every mailbox provider keeps a record of your earlier emails and of how its users responded to them.
That record is your sender reputation. It helps the provider decide whether to accept your next email
and whether to put it in the inbox or the spam folder.
[Microsoft](https://sendersupport.olc.protection.outlook.com/snds/FAQ.aspx) says: "Deliverability to
Outlook.com is based on your reputation."

You cannot set your reputation or buy it. You build it slowly through the emails you send and how people
respond to them, but you can damage it quickly.

This guide covers:

1. What sender reputation is
2. IP reputation and domain reputation
3. What builds and what damages reputation
4. Your sending service keeps a record too

5. Warming up a new IP address or domain
6. How to see your reputation
7. Repairing a damaged reputation
8. Reputation tools in AcelleMail

It ends with the most common mistakes and a one-screen summary.

## 1. What sender reputation is

Google explains sender reputation in its help for [Postmaster
Tools](https://support.google.com/mail/answer/14668346?hl=en): "Reputation is a rating of the quality of
domains and IP addresses used to send email, and is determined by the sending behavior of a domain or IP
address." It adds: "Messages from senders with a high reputation are more likely to be delivered to
Gmail’s Inbox instead of to Spam."

Gmail shows four ratings in Postmaster Tools:

| Rating | Google's description |
|---|---|
| High | "History of very low spam rates, and complies with Gmail's sender guidelines." |
| Medium | "History of sending legitimate email, but occasionally sends spam." |
| Low | "History of sending a significant volume of spam regularly." |
| Bad | "History of sending a high volume of spam regularly." |

There is no single score shared by every provider. Gmail, Microsoft and Yahoo each keep their own
record. They base it on the emails they receive from you and how their users respond. So your reputation
can be good with one provider and weaker with another. In Microsoft's words, "Reputation is always the
responsibility of the sender."

![Who keeps a record of your sending. Mailbox providers such as Gmail, Outlook.com and Yahoo each keep their own record, and Gmail's Postmaster Tools rates a domain or IP address High, Medium, Low or Bad. Your sending service, such as Amazon SES, watches your account and places it under review at a 5% bounce rate or a 0.1% complaint rate. If you send for several customers, AcelleMail's Sending health can warn or suspend a customer whose rates are too high.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sender-reputation-explained/01-where-reputation-is-kept.png)

## 2. IP reputation and domain reputation

Providers usually track two kinds of reputation for your email:

- **IP reputation** belongs to the IP address of the server that sends your email.
- **Domain reputation** belongs to your domain. In Gmail's Postmaster Tools, it covers the domain that
  signs the email with DKIM, or the SPF domain when there is no DKIM signature.

[Yahoo](https://senders.yahooinc.com/best-practices/) says: "Each IP and DKIM domain has a reputation,
which can impact the delivery of your email."

| | IP reputation | Domain reputation |
|---|---|---|
| What it belongs to | The sending IP address | Your domain, as used in DKIM or SPF |
| Who shares it | Everyone who sends from the same IP address | Only email authenticated with your domain |
| When you change IP address or service | You leave the old IP reputation behind | It stays with your domain |

When you use a shared IP address, emails from other senders also affect its reputation. Google explains
in its [email sender guidelines](https://support.google.com/a/answer/81126?hl=en): "The IP address quota
is shared for all senders that use that IP address. When the IP address hits its quota, all domains that
send from that IP address stop sending emails."

This does not mean you need a dedicated IP address. [Amazon
SES](https://docs.aws.amazon.com/ses/latest/dg/dedicated-ip.html) advises: "If you don't plan to send
large volumes of email on a regular and predictable basis, we recommend that you use shared IP
addresses." Shared IP addresses are ready to use immediately. Dedicated IP addresses keep your
reputation apart from other customers, but they need setup and a warm-up. [Dedicated vs Shared Sending
IP](https://acellesend.com/kb/articles/dedicated-vs-shared-ip-address) compares the two.

It also helps to keep different types of email separate. Yahoo advises against sending bulk or marketing
email from the same IP addresses as personal email, transactional email and alerts. [Transactional vs
Marketing Email](https://acellesend.com/kb/articles/transactional-vs-marketing-email) explains why.

## 3. What builds and what damages reputation

Your reputation depends on how people respond to your emails. A few signals have the greatest effect.

- **Complaints.** Every spam report counts against you. Gmail asks every sender to keep the spam rate
  reported in Postmaster Tools below 0.3%, and Yahoo sets the same limit. [Email Marketing Metrics
  Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained) shows how the rate is calculated, and [Spam
  Filters Explained](https://acellesend.com/kb/articles/spam-filters-explained) shows how readers' reports train the filters.
- **Email nobody asked for.** Google asks senders to avoid practices such as "Don't purchase email
  addresses from other companies." and "Don't send messages to people who didn't sign up to get messages
  from you."
- **Bounces and spam traps.** Sending emails to addresses that do not exist or to spam traps suggests
  that you have not kept your list clean. [Email List Quality
  Explained](https://acellesend.com/kb/articles/email-list-quality-explained) covers both.
- **Sudden jumps in volume.** [Amazon
  SES](https://docs.aws.amazon.com/ses/latest/dg/manage-sending-quotas.html) says that its sending
  quotas help "decrease the likelihood that email providers block your emails because of sudden,
  unexpected spikes in your email sending volume or rate." Section 5 explains how to increase your
  volume gradually.
- **Authentication.** Gmail's reputation dashboards only include emails authenticated with DKIM or SPF.
  This means emails without authentication do not count towards a domain's reputation in Postmaster
  Tools. That does not make them any safer to send. [Email Deliverability
  Explained](https://acellesend.com/kb/articles/email-deliverability-explained) explains the records.

![What builds and what damages reputation. Email that people asked for, authenticated email, few complaints with a spam rate below 0.3%, and steady volume build it. Spam reports, purchased or unsolicited lists, bounces and spam traps, and sudden spikes in volume damage it. Each provider keeps its own record.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sender-reputation-explained/02-what-moves-reputation.png)

## 4. Your sending service keeps a record too

Mailbox providers are not the only ones that watch your email. Your sending service, which [Email
Sending Explained](https://acellesend.com/kb/articles/email-sending-explained) describes, also watches your account because
your emails affect its IP addresses and its own reputation. [Amazon
SES](https://docs.aws.amazon.com/ses/latest/dg/faqs-enforcement.html) publishes the levels it uses to
decide when to take action:

| Rate | Amazon SES's advice | Account placed under review | Sending may be paused |
|---|---|---|---|
| Bounce rate | below 2% | 5% or more | 10% or more |
| Complaint rate | below 0.1% | 0.1% or more | 0.5% or more |

So a single bad list, for example an old or purchased one, can put the whole account at risk. If you run
AcelleMail for several customers, the customers who use your shared sending servers all send through the
same sending service accounts. That is why AcelleMail can watch each customer's bounces and complaints
(section 8).

## 5. Warming up a new IP address or domain

A new IP address or domain has no history, so providers are careful with senders they do not know yet.
Amazon SES explains: "Email providers are less likely to accept mail from new IP addresses that have
little or no history." The solution is to increase your sending volume step by step.
[SendGrid](https://www.twilio.com/docs/sendgrid/ui/sending-email/warming-up-an-ip-address) describes
this process as follows: when you add a dedicated IP address, "gradually increase the volume of email
messages sent through the IP address".

Google's sender guidelines name three factors that affect how quickly you can increase your volume:

- **The amount of email.** The more email you send, the more slowly you should increase the volume.
- **How often you send.** You can increase the volume more quickly when you send daily instead of
  weekly.
- **Recipient feedback.** Send only to people who subscribed, and give them a way to unsubscribe.

Google also says: "As you gradually increase your sending email volume, you should monitor your email
delivery with Postmaster Tools."

Sending services can handle part of the work. [Amazon
SES](https://docs.aws.amazon.com/ses/latest/dg/dedicated-ip-warming.html) can warm up standard dedicated
IP addresses for you. Its warm-up percentage "steadily increases over 45 days, independently from your
sending volume". Afterwards, Amazon SES says that you "should send around 1,000 emails every day to each
email provider that you want to maintain a positive reputation with". That advice is for a dedicated IP
address, which is one more reason why small senders do better on shared IP addresses (section 2).

A new dedicated IP address needs a warm-up, as does a new domain or subdomain. An IP address that has
been inactive also needs one. SendGrid advises warming it up again when no email has gone through it "in
more than 30 days". The shared IP addresses of a sending service are already in use, so you do not need
to warm them up yourself. However, a new domain using those IP addresses still starts without a sending
history. Start by sending to contacts who are most likely to open your emails. [IP Warmup
Schedule](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) and [Domain
Warmup](https://acellesend.com/kb/articles/domain-warmup) give example schedules.

![What a warm-up looks like: a bar chart of emails per day that starts small, grows step by step and then stays at full volume. In the first days, send small amounts to your most engaged contacts. During the ramp, increase the volume and slow down if bounces or complaints rise. At full volume, keep sending regularly. Google says that the more email you send, the more slowly you should increase it, and Amazon SES can warm up a dedicated IP address automatically over 45 days.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sender-reputation-explained/03-a-warm-up-ramp.png)

## 6. How to see your reputation

Each provider shows only what it sees of your email. No provider shows the full picture.

- **Gmail.** Postmaster Tools has dashboards for the spam rate, IP reputation and domain reputation. It
  also shows your compliance status against Gmail's sender requirements. [Gmail Postmaster Tools
  Walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough) shows how to set it up.
- **Outlook.com.** Microsoft's Smart Network Data Services (SNDS) provides data about the IP addresses
  you control. It includes the Junk Email Reporting Program, which sends you a report when users mark
  your messages as junk. A new SNDS portal is replacing the old site, according to an announcement
  updated on 22 September 2026. [Microsoft SNDS Walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)
  explains the setup.
- **Your own reports.** You can check each campaign's bounces and complaints after every send. These can
  help you assess your sending reputation.

These tools do not combine their results into one score. Each tool shows only what its own provider
sees, so read the results one by one.

## 7. Repairing a damaged reputation

When your numbers drop, find the cause before you change anything. Common causes include a purchased or
very old list, a sudden increase in volume, missing authentication, or a type of email that people did
not expect.

Then change how you send while your reputation recovers. Stop sending to people who have not engaged for
a long time, clean the list, and fix the authentication. Send fewer emails and send only to your most
engaged contacts until the complaint and bounce rates come down. This process takes time. Microsoft
says: "Building & maintaining good reputation is a long-term proposition."

Avoid moving to a new IP address or domain to escape the problem. The new one starts without a sending
history. If the cause is still there, the same problem can happen again.

## 8. Reputation tools in AcelleMail

### Warm-up strategies

A warm-up strategy tells AcelleMail how many emails a sending server may send each day during its
warm-up. Open **Sending → Warmup strategies** to create one.

![The Add warmup strategy form in AcelleMail, with four marks: 1 Preset Cautious and Growth strategy Linear, 2 Starting volume 15, 3 Daily increment 8, and 4 Limit type Per day cap with a cap of 500. The Strategy preview on the right estimates the full warm-up at 86 days.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sender-reputation-explained/04-warmup-strategy.png)

1. **Preset** and **Growth strategy**: start with **Balanced**, **Cautious** or **Aggressive**, and
   choose **Linear** or **Exponential** growth.
2. **Starting volume**: how many emails the server may send on day 1.
3. **Daily increment**: how many emails the server adds each day with linear growth.
4. **Limit type** and **Per day cap**: what ends the warm-up, for example a daily cap.

The **Strategy preview** on the right shows the plan as a chart. In this example, the **Cautious**
preset starts at 15 emails a day, adds 8 a day and stops growing at 500 a day. If the server does not
send at weekends, the preview estimates that the full warm-up will finish on calendar day 86.

AcelleMail applies the starting volume, daily increase, cap and weekend setting to the campaigns and
automations that the server sends. It counts the plan by calendar day for the whole server, not
separately for each mailbox provider. The warm-up starts the first time a campaign uses the server.
After the last planned day, the server sends at full speed.

To use a strategy, open the sending server. In its **Warmup** card, choose the strategy under **Select
strategy** and click **Assign strategy**. The card explains: "Ramp this server's sending volume up
gradually so mailbox providers build trust before you send at full speed."

### Sending health, for administrators

If you run AcelleMail for several customers, **Sending health** watches each customer's hard bounces and
complaints. Administrators set it up under **Settings** → **Sending health**.

![The Sending health settings for administrators in AcelleMail, with three marks: 1 Mode, set to Monitor, with the meaning of Off, Monitor and Enforce, 2 Hard bounces, with a warning rate of 2%, a suspension rate of 5%, a window of 2000 emails and at least 20 events, and 3 Complaints, with a warning rate of 0.1%, a suspension rate of 0.3%, a window of 20000 emails and at least 15 events.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sender-reputation-explained/05-sending-health-settings.png)

1. **Mode**: **Off**, **Monitor** or **Enforce**. In **Monitor** mode, "Rates are measured and
   administrators are told who would be suspended. Customers are neither warned nor suspended." In
   **Enforce** mode, "Customers are warned and suspended automatically when they reach the marks."
2. **Hard bounces**: by default, a warning at 2% and a suspension at 5%, based on a customer's last
   2,000 emails, with at least 20 bounces.
3. **Complaints**: by default, a warning at 0.1% and a suspension at 0.3%, based on the last 20,000
   emails, with at least 15 complaints.

These defaults follow the levels in sections 3 and 4. The warnings start at Amazon SES's advice: 2%
bounces and 0.1% complaints. In **Enforce** mode, a customer is suspended at 5% hard bounces or 0.3%
complaints. Amazon SES places an account under review at 5% bounces, and Gmail and Yahoo ask senders to
keep the spam rate below 0.3%. Both levels are lower than the 10% and 0.5% at which Amazon SES may pause
the shared account.

## 9. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| You move a large list to a new dedicated IP address on the first day. | A new IP address has no history, and providers are less likely to accept its email. | Warm it up, starting with your most engaged contacts. |
| You buy a dedicated IP address for a small, irregular newsletter. | Amazon SES recommends shared IP addresses unless you send large volumes regularly. | Stay on shared IP addresses. |
| Marketing email and order emails come from the same IP address and domain. | Complaints about the marketing email can then affect the order emails too. | Keep the two kinds of email apart, as Yahoo advises. |
| You ignore complaints because the rate looks small. | Gmail and Yahoo want the spam rate below 0.3%, and Amazon SES reviews an account from 0.1%. | Watch the complaints for each campaign. |
| One customer on your platform imports an old list. | If customers share a sending server, that customer's bounces and complaints count against the whole sending account. | Watch each customer's rates with **Sending health** before the sending service acts. |
| You switch to a new domain after a reputation problem. | The new domain starts without history, and the cause is still there. | Fix the cause first, then rebuild slowly. |
| You stop sending for months, then email everyone at once. | SendGrid advises a new warm-up when an IP address has sent nothing for more than 30 days. | Increase the volume gradually again. |

## Summary

1. Sender reputation is each provider's record of your earlier emails and of how its users responded to
   them. Gmail rates it from High to Bad.
2. IP reputation belongs to the sending IP address and is shared by everyone using a shared IP address.
   Domain reputation stays with your domain.
3. Complaints, unwanted emails, bounces and sudden jumps in volume damage it. Wanted, authenticated
   emails help build it.
4. Your sending service keeps a record too. Amazon SES reviews an account at 5% bounces or 0.1%
   complaints.
5. Warm up a new IP address or domain slowly and steadily, starting with engaged contacts.
6. Check your standing in Gmail's Postmaster Tools and Microsoft's SNDS.
7. Repair takes time. Fix the cause, then rebuild slowly.
8. In AcelleMail, use a warm-up strategy for each new server, and **Sending health** if you run
   AcelleMail for several customers.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Spam Filters Explained: How Mail Ends Up in Spam

> How spam filters decide: blocklists, authentication, content scores and reader feedback, what spam words can do, and how to check an email in AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/spam-filters-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Inbox Placement, Spam Score, Sender Reputation
- Published: 2026-10-18 · Last updated: 2026-10-21
- Reading time: 17 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

A spam filter decides which incoming emails are unwanted. It does not read your email in the way a
person does. It checks who is sending the email, how the message is built, which links it contains and
how readers responded to similar emails in the past. It then decides whether to accept the email and
where to put it.

Many senders worry most about "spam words". Large mailbox providers describe their filters differently.
They focus mainly on the sender and on how their own users respond to emails. Each filter also uses its
own scoring system, so a message can pass one filter and fail another.

The sections below explain each layer of filtering in the order it happens. They also cover what you can
check before you send.

This guide covers:

1. Where filtering happens
2. Blocklists
3. Authentication and reverse DNS
4. Content scores: how a rule-based filter adds up points
5. What about spam words?
6. Readers train the filter
7. Checking before you send

It ends with the most common mistakes and a one-screen summary.

## 1. Where filtering happens

Filtering happens in layers, and each layer checks something different.

1. **At the connection.** Before the receiving server reads the message, it knows which IP address is
   connecting. It can check that address against blocklists and look up its reverse DNS.
   [Spamhaus](https://www.spamhaus.org/blocklists/spamhaus-blocklist/) notes that "The majority of
   malicious email is dropped at the SMTP transaction", which is the conversation between the two
   servers.
2. **On the message.** The filter checks whether the message actually comes from the domain it names,
   using SPF, DKIM and DMARC. It also checks the headers, links and content.
3. **In the mailbox.** The filter can learn from how readers respond to emails.
   [Gmail](https://support.google.com/mail/answer/1366858?hl=en) explains: "As you report more spam,
   Gmail identifies similar emails as spam more efficiently."

![Where filtering happens, in three layers. At the connection, before the content is read, the filter checks the sending IP address against blocklists and looks up its reverse DNS. On the message, it checks SPF, DKIM and DMARC, reads the headers and the links, and scores the content. In the mailbox, readers report spam, mark email as not spam, add senders to their contacts or unsubscribe, and the filter learns from this. The email ends up in the inbox or the spam folder, or it is refused.](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-filters-explained/01-where-filtering-happens.png)

Large mailbox providers, such as Gmail, Outlook.com and Yahoo, run their own filtering systems.
Companies that run their own mail servers can use an open-source filter such as [Apache
SpamAssassin](https://spamassassin.apache.org/). These systems work differently. Google, for example,
uses machine learning together with rules. When it announced a new layer in 2019,
[Google](https://workspace.google.com/blog/product-announcements/ridding-gmail-of-100-million-more-spam-messages-with-tensorflow)
wrote: "These new protections complement existing ML and rules-based protections".

## 2. Blocklists

A blocklist contains IP addresses or domains that its operator considers risky for email, for example
because they sent spam or should not send email directly. Most blocklists are published in DNS, so a
receiving server can check an address in a fraction of a second. The standard for these lists, [RFC
5782](https://www.rfc-editor.org/rfc/rfc5782), says: "A DNSxL is a zone in the DNS". DNSxL is its name
for both blocklists and allowlists.

To check an IP address, the receiving server reverses it and queries the list. RFC 5782 gives an
example. To look up the address 192.0.2.99 on a list called bad.example.com, the server asks for the
name 99.2.0.192.bad.example.com. If the list returns an answer, the address is listed, apart from the
error codes described in section 7. If the name does not exist, it is not.

![How a server checks a blocklist. A server with the IP address 192.0.2.99 connects. The receiving server reverses the address and asks DNS for 99.2.0.192.bad.example.com. If an answer comes back, the address is on the list. If the name does not exist, it is not. Spamhaus publishes several lists: SBL for malicious network ranges, XBL for compromised addresses, PBL for addresses that should not send directly, CSS for addresses seen sending spam, and DBL for domains with a low reputation, which also covers the domains of links.](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-filters-explained/02-a-blocklist-lookup.png)

[Spamhaus](https://www.spamhaus.org/blocklists/), one of the best-known operators, publishes several
lists. Each list targets a different problem:

| List | What it contains |
|---|---|
| Spamhaus Blocklist (SBL) | Network ranges that Spamhaus considers malicious |
| Exploits Blocklist (XBL) | Compromised IP addresses, for example hijacked devices |
| Policy Blocklist (PBL) | IP ranges that should not send email directly, such as home connections |
| Combined Spam Sources (CSS) | IP addresses seen sending spam |
| Domain Blocklist (DBL) | Domains with a low reputation |

Spamhaus ZEN combines the IP lists, so a server can check all of them with one query. A domain list such
as the DBL checks domains, including those found in a message's links. A link to a domain with a bad
reputation can therefore hurt an email from a sender with a good reputation.

How you get off a list depends on the list. Spamhaus handles removals from the XBL on its checker site,
check.spamhaus.org. An address is listed again if the problem behind the listing is not fixed. For the
SBL, Spamhaus contacts the network or hosting company. Its [SBL
page](https://www.spamhaus.org/blocklists/spamhaus-blocklist/) says that "only these companies can deal
with SBL removals."

If you send through a sending service, the IP addresses being checked usually belong to the service, as
[Email Sending Explained](https://acellesend.com/kb/articles/email-sending-explained) describes. The service needs to fix any
listing, so report it to them. If you run your own server, check its address yourself.

### In AcelleMail

The **Blacklist** under **Sending** is not a public blocklist. It is your own suppression list. Its page
says: "Blacklisted email addresses are permanently blocked from receiving any campaigns." AcelleMail
adds an address there when the address hard-bounces, and you can add addresses yourself.

## 3. Authentication and reverse DNS

SPF, DKIM and DMARC each help a filter check that a message actually comes from the domain it names.
When Gmail cannot check the sender, it can show a warning above the message: "Gmail can't confirm who
sent you the email." This text appears on the [Gmail Help
page](https://support.google.com/mail/answer/1366858?hl=en) about spam.

Large providers also expect valid reverse DNS for the sending IP address.
[Yahoo](https://senders.yahooinc.com/best-practices/) asks every sender to "Have a valid forward and
reverse DNS record for your sending IPs", and Google asks for the same.

In a rule-based filter, passing authentication earns very few points. In SpamAssassin's default scores,
a valid DKIM signature (the rule DKIM_VALID) is worth -0.1, and a passing SPF check (SPF_PASS) is worth
-0.001. Authentication mainly protects you from being treated as a forgery. Large providers also require
it. [Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained) covers the three
records, and [How to Set Up SPF, DKIM, and DMARC
Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) explains how to set them up.

## 4. Content scores: how a rule-based filter adds up points

A rule-based filter such as SpamAssassin runs many tests on the headers and body of a message. Each test
that matches adds points. A test with a negative score removes points instead. If the total reaches a
threshold, the filter treats the message as spam.

SpamAssassin's own [configuration
reference](https://spamassassin.apache.org/full/4.0.x/doc/Mail_SpamAssassin_Conf.html) sets the default
threshold at 5. It describes the setting this way: "Set the score required before a mail is considered
spam." The same page calls 5.0 "quite aggressive" and suggests that an ISP might set a more conservative
value, "like 8.0 or 10.0". This means the same score can count as spam on one server and pass on
another.

The points for each rule come from SpamAssassin's [default score
file](https://github.com/apache/spamassassin/blob/trunk/rules/50_scores.cf). Many rules have four scores
because the points depend on whether the server also runs network tests, such as blocklist lookups, and
a learning filter, which is explained below. The following examples use network tests with the learning
filter turned off:

| Rule | What it means | Points |
|---|---|---|
| SUBJ_ALL_CAPS | The subject is written in capital letters | 0.5 |
| MISSING_HEADERS | The message has no To header | 1.207 |
| RDNS_NONE | The sending server has no reverse DNS | 1.274 |
| HTML_IMAGE_ONLY_08 | The HTML has images and only 400 to 800 bytes of text | 1.781 |
| URIBL_DBL_SPAM | A link points to a domain on the Spamhaus DBL | 2.5 |
| RCVD_IN_PBL | The message came directly from an address on the Spamhaus PBL | 3.558 |

These are SpamAssassin's defaults as of October 2026. Each site can change them.

![How a content score adds up. An example email matches four SpamAssassin rules: SUBJ_ALL_CAPS for 0.5, RDNS_NONE for 1.274, HTML_IMAGE_ONLY_08 for 1.781 and URIBL_DBL_SPAM for 2.5, a total of 6.055. That reaches SpamAssassin's default threshold of 5, so a server with the default treats the email as spam. It stays below 8, so a more conservative server does not.](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-filters-explained/03-a-content-score-adds-up.png)

The learning filter, called Bayes, learns from emails that a site has already marked as spam or not
spam. When it is on and almost certain that a message is spam (BAYES_99), it adds 3.5 points. When it is
almost certain that a message is not spam (BAYES_00), it removes 1.9 points. Bayes learns from each
site's own mail, so the same content can score differently from one site to another and from one week to
the next.

## 5. What about spam words?

Words can add points in a rule-based filter. SpamAssassin has rules for phrases often found in spam. A
few of these phrases carry at least as many points as a link to a domain on the Spamhaus DBL. With
network tests on and the learning filter off, a greeting of "Dear Friend" adds 2.604 points
(DEAR_FRIEND), and "100% guaranteed" adds 2.699 (GUARANTEED_100_PERCENT). A footer that tells people how
"to be removed from" your mailings adds 2.992 (EXCUSE_REMOVE). Each of these rules checks for a whole
phrase, not a single word. Many other phrase rules add 0.1 points or nothing.

Large mailbox providers do not publish lists of spam words. Their filters check the content too, but the
signals that Google names in section 6 are about senders and readers, not words.

Content still matters in other ways:

- **An empty message looks suspicious.** Gmail explains: "To check whether email addresses are valid,
  spammers often send emails with no content in the body or subject."
- **An email made mostly of images, with little text,** scores higher in SpamAssassin, and readers see
  little when images are not shown.
- **Links are checked**, because domain blocklists such as the DBL also cover the domains in links.

Write clearly for people who asked for your email, and make the subject line match the message. Leave
out common spam phrases, keep real text next to your images, and link to domains you trust. You do not
need to rewrite good copy just to avoid single words.

## 6. Readers train the filter

Google describes its filters in a post from May 2022, [Understanding Gmail's spam
filters](https://workspace.google.com/blog/identity-and-security/an-overview-of-gmails-spam-filters):
"These filters look at a variety of signals, including characteristics of the IP address,
domains/subdomains, whether bulk senders are authenticated, and user input. User feedback, such as when
a user marks a certain email as spam or signals they want a sender’s emails in their inbox, is key to
this filtering process, and our filters learn from user actions."

In Gmail, readers teach the filter in several ways:

- **Report spam** moves the message to Spam. Later emails from the same sender may go there too.
- **Not spam** moves the message back to the inbox. Later emails from that sender should reach the
  inbox.
- **Adding the sender to contacts** also stops Gmail from sending their messages to Spam.
- **Unsubscribing** counts too. Gmail says: "If someone sends an email after you unsubscribe, their
  emails go directly to Spam."

![What readers teach the filter. In Gmail, after Report spam, later email from the sender may go to Spam. Not spam and adding the sender to contacts help it reach the inbox, and email that still arrives after an unsubscribe goes directly to Spam. In Microsoft 365, the bulk complaint level runs from 0, not a bulk sender, through 1 to 3 for few complaints and 4 to 7 for a mixed number, to 8 and 9 for many complaints. The default threshold is 7.](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-filters-explained/04-what-readers-teach-the-filter.png)

Honour unsubscribes immediately. [Yahoo](https://senders.yahooinc.com/best-practices/) asks bulk senders
to "Honor unsubscribes within 2 days" and to keep their spam rate below 0.3%.

Microsoft gives bulk email its own number, the bulk complaint level (BCL). This number reflects how many
complaints a bulk sender generates. Its
[documentation](https://learn.microsoft.com/en-us/defender-office-365/anti-spam-bulk-complaint-level-bcl-about)
describes the scale as follows:

| BCL | What it means |
|---|---|
| 0 | The message is not from a bulk sender. |
| 1, 2, 3 | The message is from a bulk sender that generates few complaints. |
| 4, 5, 6, 7 | The message is from a bulk sender that generates a mixed number of complaints. |
| 8, 9 | The message is from a bulk sender that generates a high number of complaints. |

The default threshold in Microsoft's anti-spam policies is 7. It is 6 in the Standard preset and 5 in
the Strict preset. A message that meets or exceeds the threshold is marked as bulk email. Microsoft
still adds an older value, the spam confidence level (SCL), to each message. However, its [SCL
page](https://learn.microsoft.com/en-us/defender-office-365/anti-spam-spam-confidence-level-scl-about)
says that this value "no longer holds the same meaning in cloud organizations". The SCL does not decide
where a message goes.

These reactions also build or damage your sender reputation, explained in [Sender Reputation
Explained](https://acellesend.com/kb/articles/sender-reputation-explained). Yahoo's guidance for senders says: "The best way
to ensure your messages are delivered is to send timely and relevant email to an active and engaged
audience."

## 7. Checking before you send

A content score tells you how one rule-based filter reads your message. It cannot see your sending IP
address, so it cannot check that address against blocklists. It also cannot see your reputation or
predict how your readers will respond. Before a campaign, it helps to check a few other things:

- Check that your sending domain is authenticated.
- Check that your sending service account is in good standing.
- If you run your own server, look up its IP address on check.spamhaus.org.
- Send a test to your own Gmail and Outlook.com addresses.

If you run SpamAssassin yourself, note one detail from
[Spamhaus](https://www.spamhaus.org/resource-hub/dnsbl/using-our-public-mirrors-check-your-return-codes-now/).
Queries that arrive through public DNS resolvers get the answer 127.255.255.254. This is an error code,
not a listing. Use your own DNS resolver, or Spamhaus's Data Query Service, instead.

### In AcelleMail: Check spam score

On a campaign's **Confirm** step, click **Check spam score** to see how SpamAssassin reads the email
before you send it.

![The Spam score result in AcelleMail, with three marks: 1 the verdict Looks good and the score 1.0 of 5.0, 2 the note that the check scores the content only and cannot see the sending IP reputation, SPF, DKIM or blocklists, and 3 the rule FONT_INVIS_MSGID, Invisible text + suspicious message ID, which adds 1.0.](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-filters-explained/05-spam-score-result.png)

1. The verdict and the score. **Looks good** means that "This message scores below the spam threshold."
   The number after "of" is the threshold of the SpamAssassin server that AcelleMail uses.
2. The scope note explains what the check cannot see: "This scores your content only. It cannot see your
   sending IP reputation, SPF, DKIM or blocklists — those depend on the sending server, not on what you
   wrote."
3. **What the filter noticed** shows each rule that matched, along with its points. Here, a rule for
   invisible text adds 1.0. Rules with 0.0 are only for information.

AcelleMail does not store the score, so check again after you change the email. The same content can
also score differently after the rules are updated.

The check appears only when an administrator has switched it on. Administrators can find it under
**Settings** → **Spam Score**:

![The Spam Score settings for administrators in AcelleMail, with three marks: 1 Spam scoring set to Enabled, 2 Engine set to Direct connection, and 3 the Connection card with the host 127.0.0.1 and the port 783.](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-filters-explained/06-spam-score-settings.png)

1. **Spam scoring**: **Enabled** shows the button to customers. "When disabled, the check button is not
   shown to customers at all."
2. **Engine**: this controls how AcelleMail reaches SpamAssassin. All three engines run the same
   SpamAssassin rules.
3. **Connection**: this is the address of the SpamAssassin server.

## 8. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| You rewrite good copy to remove "spam words". | The heavier word rules look for stock spam phrases, not single words, and the providers say they rely on sender and reader signals. | Leave out stock spam phrases, write clearly for people who asked for the email, and watch your complaints. |
| You treat a content score under 5 as a promise of the inbox. | 5 is SpamAssassin's default threshold, other servers use others, and the score cannot see reputation or readers. | Use the score as one hint among several. |
| A link in your email points to a domain with a bad reputation, for example a shared link shortener. | Domain blocklists also check the domains of links, and a listed link adds points. | Link to your own domain. |
| You keep sending to people who unsubscribed. | Gmail sends later email from that sender straight to Spam. | Honour unsubscribes immediately. Yahoo asks bulk senders to do it within 2 days. |
| Your server's IP address is on the Spamhaus XBL, and you only ask for removal. | The address is listed again if the problem behind it is not fixed. | Fix the compromised machine first, then use check.spamhaus.org. |
| You read the answer 127.255.255.254 from Spamhaus as a listing. | It is an error code for queries that come through public DNS resolvers. | Use your own DNS resolver, or Spamhaus's Data Query Service. |
| You confuse AcelleMail's **Blacklist** with a public blocklist. | The **Blacklist** is your own suppression list. | Check public blocklists separately. |
| You send an email made mostly of images, with little text. | It scores higher in SpamAssassin, and readers see little when images are not shown. | Keep real text in the email. |

## Summary

1. Filtering happens in layers: at the connection, on the message and in the mailbox.
2. Blocklists check sending IP addresses and the domains of links. How you get off a list depends on the
   list.
3. Large providers expect authentication and valid reverse DNS.
4. A rule-based filter adds up points to a threshold. SpamAssassin's default is 5, and many sites change
   it.
5. Most word rules carry little weight, and the heavier ones look for stock spam phrases. Sender
   reputation and reader feedback carry much more.
6. Readers train the filter through spam reports, Not spam and unsubscribes. Microsoft rates bulk mail
   by its complaints (BCL).
7. Check before you send, but treat a content score as one hint among several.
8. In AcelleMail, use **Check spam score** on the campaign's **Confirm** step, once an administrator has
   switched it on.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email Sending Explained: SMTP, APIs and Sending Services

> What happens after you click Send: SMTP, reply codes, retries and bounces, SMTP or API, the three ways to send, and the AcelleMail settings.

- Canonical URL: https://acellesend.com/kb/articles/email-sending-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Smtp, Amazon SES, Bounce Handling, Self Hosted
- Published: 2026-10-18 · Last updated: 2026-10-21
- Reading time: 22 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

When you click Send, your email marketing platform prepares an email for each contact. In most setups,
it does not deliver the emails itself. Instead, it hands each email to a sending server, which passes it
on to the mail server of the recipient's mailbox provider, such as Gmail or Outlook.com.

This hand-off uses SMTP, the Simple Mail Transfer Protocol. Mail servers have used it for decades, and
many sending services also accept email through their own web API.

Understanding this process helps explain several things you may see in practice. An email can be counted
as delivered and still bounce a day later. Some emails are retried for days. Gmail and Microsoft 365
mailboxes have daily limits, so they are usually not a good fit for newsletters. When you add a sending
server in AcelleMail, you can choose between several ways to connect. Each option has its own strengths.

This guide covers:

1. What happens after you click Send
2. SMTP: the conversation between mail servers
3. Three kinds of answer: accepted, try again later, refused
4. Retries, bounces and complaints
5. Two ways to connect: SMTP or an API
6. Who runs the sending server: three options
7. Sending limits
8. Setting up a sending server in AcelleMail

It ends with the most common mistakes and a one-screen summary.

## 1. What happens after you click Send

AcelleMail builds one message for each contact. It fills in the merge tags and the unsubscribe link,
then adds any tracking you have switched on. It then hands the message to a sending server. From there,
the email goes through four steps.

1. **Submission.** AcelleMail passes the message to the sending server. Over SMTP, this normally happens
   on port 587, after AcelleMail logs in with a username and password. The standard for this step, [RFC
   6409](https://www.rfc-editor.org/rfc/rfc6409), says: "Port 587 is reserved for email message
   submission as specified in this document." Some sending services can also accept the message through
   an API instead (section 5).
2. **Finding the recipient's server.** The sending server looks up the recipient's domain in DNS to find
   its mail server. An MX record is a DNS record that names the mail servers for a domain. The SMTP
   standard, [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321), says: "The lookup first attempts to
   locate an MX record associated with the name."
3. **Relay.** The sending server connects to that mail server over SMTP and passes the message on. An
   email may pass through more than one server on the way.
4. **The mailbox provider's decision.** The recipient's provider accepts or refuses the email. If it
   accepts the email, it then decides where to put it, such as the inbox, a tab or the spam folder. The
   articles [Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained) and [Spam
   Filters Explained](https://acellesend.com/kb/articles/spam-filters-explained) explain this second decision.

![The journey of one campaign email. AcelleMail builds one message for each contact and hands it to a sending server, such as Amazon SES or your own mail server. The sending server finds the recipient's mail server through its MX record and passes the message on over SMTP. The mailbox provider then puts it in the inbox, in a tab or in the spam folder. Bounces and complaints come back later, as a report from the sending service or in a bounce mailbox.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/01-the-journey-of-one-email.png)

Reading email is a different job. Mail apps use SMTP to send email, but they fetch messages from a
mailbox with IMAP or POP. AcelleMail uses IMAP or POP for one task: reading bounce messages and
complaint reports from a mailbox (section 8).

## 2. SMTP: the conversation between mail servers

SMTP works like a short conversation. The sending server connects to the receiving server and greets it
with the EHLO command. It names the sender address with MAIL FROM and each recipient with RCPT TO. After
the DATA command, it sends the message itself. The receiving server replies to each step with a
three-digit reply code.

[RFC 5321](https://www.rfc-editor.org/rfc/rfc5321) describes what passes between the servers: "SMTP
transports a mail object. A mail object contains an envelope and content."

- **The envelope** is for the servers. It holds the sender address where error reports go, called the
  envelope sender, as well as the recipient addresses.
- **The content** is for the reader. It holds the header section, with fields such as From, To, Subject
  and Date, followed by the body of the email.

![An SMTP conversation. The sending server says EHLO, then MAIL FROM with a bounce address and RCPT TO with the recipient, and the receiving server answers 250 to each. After DATA and the answer 354, the message follows with its From, To and Subject headers, and the server answers 250 OK, queued. MAIL FROM and RCPT TO form the envelope for the servers. The headers and the body form the content for the reader.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/02-an-smtp-conversation.png)

The envelope sender and the From address can differ. The reader may see news@shop.example in the From
field, while the envelope sender is a bounce address at your sending service. Bounces go to the envelope
sender, not to the From address. When the email reaches its final server, that server writes the
envelope sender into a Return-Path header at the top of the message. [Return-Path vs
Envelope-Sender](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained) explains what each one does in
AcelleMail.

## 3. Three kinds of answer: accepted, try again later, refused

The first digit of a reply code tells the sending server what happened and what to do next.

| First digit | Meaning | What RFC 5321 says |
|---|---|---|
| 2 | Accepted | "The requested action has been successfully completed." |
| 4 | Try again later | "However, the error condition is temporary, and the action may be requested again." |
| 5 | Refused | "The SMTP client SHOULD NOT repeat the exact request (in the same sequence)." |

Many servers add an enhanced status code after the reply code, such as 5.1.1. These codes come from [RFC
3463](https://www.rfc-editor.org/rfc/rfc3463), and their first digit means the same thing. The standard
calls 4.X.X a "Persistent Transient Failure" and 5.X.X a "Permanent Failure". For example, the code
5.1.1 stands for "Bad destination mailbox address". RFC 3463 explains: "The mailbox specified in the
address does not exist."

![Three kinds of answer. A reply that starts with 2, such as 250 OK, means accepted, and the provider's filtering comes next. A reply that starts with 4, such as 451, means try again later, and the sending server retries for days. A reply that starts with 5, such as 550 5.1.1, means refused, and a hard bounce goes to the envelope sender. A bounce can arrive after the campaign report has counted the message as sent.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/03-three-kinds-of-answer.png)

Accepted does not mean delivered to the inbox. A 250 reply means that the receiving server has taken
responsibility for the message. When your sending service accepts an email from AcelleMail, it takes
over the job of delivering it. The recipient may not have received the email yet, and the mailbox
provider still has to decide where to put it.

Mailbox providers also use these replies to enforce their own rules. [Google's sender guidelines
FAQ](https://support.google.com/a/answer/14229414?hl=en) says: "Starting November 2025, Gmail is ramping
up its enforcement on non-compliant traffic. Messages that fail to meet the email sender requirements
will experience disruptions, including temporary and permanent rejections."

## 4. Retries, bounces and complaints

After a temporary reply, the sending server keeps the message in its queue and tries again later. [RFC
5321](https://www.rfc-editor.org/rfc/rfc5321) says that "the retry interval SHOULD be at least 30
minutes" and that "the give-up time generally needs to be at least 4-5 days." Each sending service
chooses its own schedule. For example, [Amazon
SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-concepts-process.html) says: "SES retries the
email for an extended period of time."

A message bounces when the receiving server refuses it permanently. It also bounces when the sending
server gives up after retrying a temporary failure. A bounce is a delivery status notification, a
standard report message described in [RFC 3464](https://www.rfc-editor.org/rfc/rfc3464). The report goes
back to the envelope sender. A hard bounce is a permanent failure, usually with a status code that
starts with 5. A soft bounce is a temporary failure, usually with a code that starts with 4. [Hard
Bounce vs Soft Bounce](https://acellesend.com/kb/articles/understanding-email-bounce-types) explains how to read these reports
in AcelleMail.

A bounce can arrive minutes or days after your sending service accepts the message. A complaint may
arrive even later. This happens when a recipient marks the email as spam and the mailbox provider
reports it back through a feedback loop. The article [Feedback Loops
(FBL)](https://acellesend.com/kb/articles/feedback-loops-fbl-setup) explains how this works.

How this information reaches your platform depends on the way you connect:

- **A sending service can tell the platform directly.** Amazon SES says: "In order to send email using
  Amazon SES, you must have a system in place for managing bounces and complaints." It can send a
  notification email, notify an Amazon SNS topic or publish sending events, as its [notification
  guide](https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity-using-notifications.html)
  explains.
- **A plain SMTP server does not report later bounces to the platform.** Its bounce messages arrive by
  email in the envelope sender's mailbox, so something has to read that mailbox.

Section 8 shows how AcelleMail handles both cases.

## 5. Two ways to connect: SMTP or an API

Most sending services offer two ways to connect. The service and the delivery stay the same. What
changes is how the platform hands the message over, and often how bounces and complaints come back.

With an SMTP connection, the platform logs in to the service's SMTP server with a username and password.
It usually connects on port 587. The connection starts unencrypted and then switches to encryption with
a command called STARTTLS. The alternative is port 465, where the connection is encrypted from the
start. [RFC 8314](https://www.rfc-editor.org/rfc/rfc8314) recommends encryption from the start for this
step. It calls this Implicit TLS. For example, Amazon SES accepts STARTTLS on ports 25, 587 and 2587. It
also supports encryption from the start, which it calls TLS Wrapper, on ports 465 and 2465.

The SMTP login is often different from your account login. [Amazon
SES](https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html) says: "Your SMTP password is
different from your AWS secret access key." SendGrid uses the word apikey as the SMTP username and an
API key as the password.

With an API connection, the platform sends each message to the service as a web request. It signs the
request with an access key or an API key. This method does not use the SMTP ports.

| | SMTP connection | API connection |
|---|---|---|
| What you enter | The SMTP host, port, encryption, username and password | The service's access key or API key, and for Amazon SES the region |
| Works with | Almost any mail server or sending service | Only the service the API belongs to |
| Bounces and complaints | Arrive by email, unless the service also reports them another way | The service can report them back to the platform |
| Blocked ports | Some hosting companies block SMTP ports (section 6) | Not affected by blocked SMTP ports |

## 6. Who runs the sending server: three options

Every option does the same job. It hands each message to the recipient's mail server. The main
difference is who runs the server that does this.

![Three ways to send your campaigns. A sending service such as Amazon SES runs the servers and the retries, and its limits start low and grow. Your own mail server, such as Postfix, is the most work: many hosts block port 25, and you need reverse DNS, TLS and a warm-up. A mailbox account such as Gmail or Microsoft 365 is made for one person's email, with limits of 2,000 messages a day in Google Workspace and 10,000 recipients a day in Microsoft 365.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/04-three-ways-to-send.png)

### A sending service

Services such as Amazon SES, SendGrid, Mailgun, SparkPost and Elastic Email run the sending servers and
manage the IP addresses and retries for you. You verify your domain with the service and connect with an
API key or an SMTP login. The limits start low and grow with your account (section 7). For most senders,
this is the simplest option.

### Your own mail server

You can also run a mail server yourself, for example Postfix on a server you rent. You then manage the
queue, retries and IP address yourself. Several things can make this difficult.

- **Many hosting companies block outgoing SMTP.** [Amazon
  EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html) says: "By default,
  Amazon EC2 allows outbound traffic over port 25 only to private IPv4 addresses." It adds: "You can
  request that this restriction be removed." [Google
  Cloud](https://cloud.google.com/compute/docs/tutorials/sending-mail) says: "Due to the risk of abuse,
  connections to destination TCP Port 25 are blocked when the destination is external to your VPC
  network." [Microsoft
  Azure](https://learn.microsoft.com/en-us/azure/virtual-network/troubleshoot-outbound-smtp-connectivity)
  says: "We recommend you use authenticated SMTP relay services to send email from Azure VMs or from
  Azure App Service."
- **Some hosts also block the ports used to reach a sending service.**
  [DigitalOcean](https://docs.digitalocean.com/support/why-is-smtp-blocked/) says: "SMTP ports 25, 465,
  and 587 are blocked on Droplets to prevent spam and other abuses on our platform." If AcelleMail runs
  on a DigitalOcean server, this also blocks SMTP connections to a sending service. Use an API
  connection instead.
- **Gmail expects reverse DNS and encryption.** [Google's email sender
  guidelines](https://support.google.com/a/answer/81126?hl=en) ask every sender to "Ensure that sending
  domains or IPs have valid forward and reverse DNS records, also referred to as PTR records." They also
  say: "Use a TLS connection for transmitting email."
- **Home and office connections are a poor place to send from.**
  [Spamhaus](https://www.spamhaus.org/blocklists/policy-blocklist/) lists these address ranges in its
  Policy Blocklist: "The PBL detects end-user IP address ranges which should not be attempting to
  directly deliver unauthenticated SMTP email to any Internet mail server."
- **A new IP address has no history.** Mailbox providers judge a sender by its reputation, explained in
  [Sender Reputation Explained](https://acellesend.com/kb/articles/sender-reputation-explained). A new IP address needs a slow
  warm-up, explained in [IP Warmup Schedule](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)
  and [Dedicated vs Shared Sending IP](https://acellesend.com/kb/articles/dedicated-vs-shared-ip-address).

### A mailbox account

Gmail, Google Workspace and Microsoft 365 are designed for individual mailboxes. Their limits reflect
that.

- **Google Workspace** allows 2,000 messages a day per user account, or 500 for trial accounts,
  according to [Gmail sending limits in Google
  Workspace](https://support.google.com/a/answer/166852?hl=en). A personal Gmail account can reach its
  limit after more than 500 emails in a day, as [Gmail sending
  limits](https://support.google.com/mail/answer/22839?hl=en) explains.
- **Microsoft 365** (Exchange Online) allows 10,000 recipients a day per mailbox and 30 messages a
  minute.
  [Microsoft](https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-service-description/exchange-online-limits)
  also says: "Exchange Online customers who need to send legitimate bulk commercial email (for example,
  customer newsletters) should use third-party providers that specialize in these services."
- **Password sign-in is being phased out.** [Google](https://support.google.com/a/answer/14114704?hl=en)
  says: "Starting March 14, 2025, you and your users must use OAuth with third-party apps to access
  Gmail, Google Calendar, and Google Contacts." On 27 January 2026, [Microsoft
  announced](https://techcommunity.microsoft.com/blog/exchange/updated-exchange-online-smtp-auth-basic-authentication-deprecation-timeline/4489835)
  that at the end of December 2026, SMTP AUTH with Basic Authentication, which means signing in with a
  password, will be switched off by default for existing Microsoft 365 organisations. Administrators
  will still be able to switch it back on. Microsoft will announce the final removal date in the second
  half of 2027.
- **One limit that is often quoted does not apply.** Microsoft had announced a limit of 2,000 external
  recipients a day for Exchange Online mailboxes. Its [update of 6 January
  2026](https://techcommunity.microsoft.com/blog/exchange/exchange-online-to-introduce-external-recipient-rate-limit/4114733)
  says: "The implementation of this limit has now been CANCELLED and will not go into effect."

## 7. Sending limits

Every way of sending has limits. These limits come from two sides.

Your sending service sets limits. Amazon SES has two limits. The sending quota is the most emails you
can send in a rolling 24-hour period. The maximum send rate is the number of emails it accepts per
second. Each AWS Region has its own quotas. A new SES account starts in a sandbox. In the sandbox, it
can send only to verified addresses and domains, or to the SES mailbox simulator. It can send at most
200 messages per 24-hour period and 1 message per second. The account stays in the sandbox until you
[request production access](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html).

Mailbox accounts have the limits described in section 6.

The receiving side also sets limits. [Google](https://support.google.com/a/answer/81126?hl=en) says: "To
protect users from spam, there are limits on the number of messages you can deliver within a certain
timeframe." A mailbox provider may reply "try again later" when a sender sends too much too quickly. The
sending server then retries the message.

Both sides can affect how quickly you should send. In AcelleMail, each server has its own **Sending
limit**, which you set to stay within these limits (section 8). The article [Sending Throttling
Strategies](https://acellesend.com/kb/articles/sending-throttling-strategies) suggests limits for different situations.

## 8. Setting up a sending server in AcelleMail

Open **Sending → Sending servers** and click **Add server**. This menu appears when your plan lets you
add your own servers. If you run AcelleMail yourself, you can manage the shared servers in the admin
area under **Sending Servers**. The same area also contains the mailboxes for bounces and complaints
(**Bounce Handlers** and **FBL Handlers**).

### Choose how to connect

The **Choose a server type** page lists every connection that AcelleMail offers.

![The Choose a server type page in AcelleMail, with four marks: 1 Amazon Web Service API, 2 Amazon Web Service SMTP, 3 SMTP and 4 Gmail (Google OAuth). Mailgun, SendGrid, ElasticEmail, SparkPost and Blastengine are also listed twice, once for the API and once for SMTP.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/05-choose-a-server-type.png)

1. **Amazon Web Service API** connects to Amazon SES through its API, using an access key.
2. **Amazon Web Service SMTP** connects to the same service over SMTP. Mailgun, SendGrid, Elastic Email,
   SparkPost and Blastengine are also listed twice, once for each way to connect.
3. **SMTP** connects to any standard SMTP server or relay.
4. **Gmail (Google OAuth)** sends through a Gmail account and uses OAuth to sign in, as Google has
   required since 14 March 2025. Gmail's own sending limits still apply.

**Sendmail (UNIX)** sends through a mail server running on the same machine as AcelleMail.

### Enter the connection details and test them

For an SMTP server, the **Connection settings** card holds the details provided by your sending service.

![The Connection settings card of an SMTP sending server in AcelleMail, with four marks: 1 SMTP host and SMTP port 587, 2 Encryption TLS, with a note to use TLS with port 587 or SSL with port 465, 3 SMTP username and SMTP password, and 4 the Test connection and Send test email buttons.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/06-smtp-connection-settings.png)

1. **SMTP host** and **SMTP port**: enter the server name and port provided by your sending service.
2. **Encryption**: use **TLS** with port 587, where the connection is upgraded with STARTTLS, or **SSL**
   with port 465, where the connection is encrypted from the start.
3. **SMTP username** and **SMTP password**: enter the SMTP login provided by your sending service. This
   is often different from your account password.
4. **Test connection** and **Send test email**: check the server before using it in a campaign.

An API connection asks for keys instead. For Amazon SES, enter the **AWS access key ID**, the **AWS
secret access key** and the **AWS region**. You do not need a host or port.

### Bounces, complaints and the sending limit

The **Configuration** card controls what happens when you send emails.

![The Configuration card of a sending server in AcelleMail, with three marks: 1 Bounce handler and 2 Feedback loop handler, both set to None, and 3 Sending limit, set to 5,000 emails per 1 hour.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-sending-explained/07-bounce-handling-and-sending-limit.png)

1. **Bounce handler**: this is the mailbox AcelleMail reads bounce messages from. It is an IMAP or POP3
   mailbox that the administrator adds under **Bounce Handlers**.
2. **Feedback loop handler**: this is the mailbox for complaint reports. It works in the same way.
3. **Sending limit**: this is the most this server may send, such as 5,000 emails per 1 hour, or a
   **Custom** limit.

Whether you need the first two settings depends on the connection:

- The API connections of Mailgun, SendGrid, SparkPost and Elastic Email set up the service's webhooks
  before sending. A webhook is a web address that the service calls to report an event, so the service
  then reports bounces and complaints to AcelleMail automatically. Their SMTP connections do not do
  this. Blastengine's API connection can receive these reports too, but it does not set up the webhook
  itself.
- Amazon SES reports bounces and complaints through Amazon SNS for both its API and SMTP connections.
- For any other SMTP server, and for Sendmail, choose a **Bounce handler** and a **Feedback loop
  handler**. Without these settings, AcelleMail never sees those bounces and complaints.

AcelleMail counts a bounce with a status code that starts with 5 as a hard bounce and adds the address
to the **Blacklist**. It records other bounces as soft bounces, and the contact's status does not
change. AcelleMail does not resend soft-bounced messages because retrying is the sending server's job.

The **Sending limit** keeps AcelleMail within your service's quota. AcelleMail counts the messages each
server sends. When a server reaches its limit, AcelleMail tries another server in the campaign's pool.
If every server reaches its limit, the campaign waits and continues later. Set the limit to match what
your sending service allows.

The campaign report follows the same split as section 3. On a sent campaign's **Insights** tab, the
**Delivery mix** shows **Delivered** and **Failed**. Hold the pointer over each one to see what it
means:

- **Delivered**: "The sending server accepted these messages without an error. Bounces reported
  afterwards are listed separately in the bounce log."
- **Failed**: "The sending server rejected these messages. Each row shows the error it returned, such as
  an unknown domain or a 550 response."

What happens after a failure depends on **Skip failed messages**, under **Advanced Settings** on the
campaign's **Setup** step: "Continue sending the campaign when an individual delivery fails. With this
off, the first hard error halts the whole run so you can investigate before retrying."

### More than one server

The **Sending servers** card on the same **Setup** step shows which servers a campaign uses: "By default
this campaign uses all sending servers available to your subscription, picked randomly per message." To
choose servers for one campaign, turn off **Use all servers randomly**. [Multiple Sending Servers in
AcelleMail](https://acellesend.com/kb/articles/configuring-multiple-sending-servers) explains how to combine servers and set
their weights.

## 9. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| You send a newsletter through your Gmail or Microsoft 365 mailbox. | Mailbox accounts have daily limits: 2,000 messages a day in Google Workspace and 10,000 recipients a day in Exchange Online. Microsoft tells bulk senders to use a specialist provider. | Connect a sending service. |
| A campaign shows messages as **Delivered**, and bounces appear a day later. | **Delivered** means that the sending server accepted the messages. Delivery to the recipient happens afterwards, and failures come back as bounces. | Check the bounce log after each campaign as well as the **Delivered** figure. |
| AcelleMail on a DigitalOcean server cannot connect to the sending service on port 587. | DigitalOcean blocks ports 25, 465 and 587 on Droplets. | Use the service's API connection. |
| You run your own mail server, and Gmail refuses or junks the mail. | Gmail expects valid reverse DNS and TLS. A new IP address has no reputation, and many hosts block port 25. | Use a sending service, or set up reverse DNS and TLS and warm the IP address up slowly. |
| A server connected over SMTP never shows any bounces. | A plain SMTP server does not report bounces to AcelleMail, so they go to a mailbox that AcelleMail does not read. | Choose a **Bounce handler** and a **Feedback loop handler** for the server. |
| You connected a Microsoft 365 mailbox as an SMTP server with its password. | Microsoft will switch off password sign-in for SMTP by default at the end of December 2026. | Move that sending to a sending service before then. |
| Test emails work, but a campaign fails after you connect a new Amazon SES account. | A new account is in the sandbox: 200 messages a day, 1 a second and verified recipients only. | Request production access. |
| The sending service starts refusing messages after a large send. | AcelleMail sent faster than the service's quota allows. | Set the server's **Sending limit** to the service's quota. |

## Summary

1. AcelleMail prepares each email, and a sending server delivers it. AcelleMail hands the email over
   using SMTP or the service's API. The next hand-off uses SMTP, after an MX lookup.
2. SMTP carries an envelope for the servers and content for the reader. Bounces go to the envelope
   sender.
3. A reply code that starts with 2 means accepted, 4 means try again later, and 5 means refused.
   Accepted does not mean that the email has reached the inbox.
4. Sending servers retry temporary failures, often for days. Permanent failures come back as bounces,
   and complaints arrive later still.
5. SMTP and API connections lead to the same service. API connections can report bounces back
   automatically, while a plain SMTP server needs a bounce mailbox.
6. There are three ways to send: a sending service, your own mail server or a mailbox account. Mailbox
   accounts have small daily limits, and password sign-in for SMTP is being phased out.
7. Every sending path has limits. Set each server's **Sending limit** to match what the service allows.
8. In AcelleMail, open **Sending → Sending servers**, choose how to connect, test the server, and set
   the bounce handling and the sending limit.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email Tracking and Privacy Explained: Opens, Clicks and Consent

> How open and click tracking work, why the numbers are not exact, what the 2026 CNIL, Garante and ICO guidance asks, and what to set in AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/email-tracking-and-privacy-explained
- Author: Daniel Reyes, Technical Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Privacy, GDPR, Open Rates, Click Rates
- Published: 2026-10-12 · Last updated: 2026-10-21
- Reading time: 30 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Most email tools record opens and clicks for individual contacts. An open is recorded when an email
loads a small image from the sender's server. A click is recorded when a link passes through the
sender's server before reaching the page.

In April 2026, three data protection regulators published guidance on email tracking pixels: France's
[CNIL](https://www.cnil.fr/fr/recommandation-pixel-suivi-courriels), Italy's
[Garante](https://www.garanteprivacy.it/home/docweb/-/docweb-display/docweb/10241943) and the UK's
[ICO](https://ico.org.uk/about-the-ico/media-centre/news-and-blogs/2026/04/final-storage-and-access-technologies-guidance-published/).
This guide explains what tracking records and why the numbers do not match real readers. It also
explains when consent is needed and what to change in AcelleMail.

This guide covers:

1. What an open and a click record
2. Why opens and clicks do not match real readers
3. Two permissions: sending and tracking
4. France: the CNIL recommendation
5. Italy and the rest of the EU
6. The United Kingdom
7. Outside Europe
8. Asking for consent and honouring a no
9. Tracking in AcelleMail

It ends with the most common mistakes and a one-screen summary.

> **Not legal advice.** This guide describes the rules in general terms and links to the official texts. It reflects texts published up to 12 October 2026.

## 1. What an open and a click record

An open pixel is a small image included in an email. The
[ICO](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/what-are-storage-and-access-technologies/)
describes it this way: "Tracking pixels are small pieces of code, usually an image file, embedded into a
piece of content like a website or an email."

When an email app loads the image, it makes a request to the sender's server. The image address
identifies the email, so the sender can connect the request to a contact.

The request can also reveal an IP address and details about the app and system. [Mozilla's Thunderbird
guidance](https://support.mozilla.org/en-US/kb/remote-content-in-messages) explains that remote content
can reveal when a message is viewed. It can also reveal a rough location from the IP address.

A tracked link first takes the reader to the sender's tracking server. That server records the click and
sends the reader to the intended page. The [European Data Protection
Board](https://www.edpb.europa.eu/system/files/documents/2024-10/edpb_guidelines_202302_technical_scope_art_53_eprivacydirective_v2_en_0.pdf)
(EDPB), where the EU's data protection regulators work together, explains: "Tracking links can function
in the same way, but the identifier is appended to the website address."

A UTM tag works differently. It is a label added to a link for website analytics. A tag such as
`utm_campaign=spring_sale` is the same for every contact who receives the email. It tells your analytics
which campaign sent the visitor, not which person clicked.

That difference does not answer every privacy question. The
[ICO](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/what-are-storage-and-access-technologies/)
says: "The key consideration for compliance is the purpose the techniques are used for."

A plain-text email has no images, so it cannot contain an open pixel.

### In AcelleMail

AcelleMail adds a hidden image at the end of each tracked HTML email. Its address follows this format:
`/p/<message id>/open`.

Tracked links are changed to `/p/click?url=<target>&message_id=<message id>`. If AcelleMail records a
click and has no open for that email yet, it records an open too. For each open or click, AcelleMail
stores the time, IP address and full user agent. The user agent describes the app and system. AcelleMail
also looks up a rough location from the IP address on your own server.

The diagram shows the requests that record an open and a click.

![What one open and one click tell the sender. For an open, the email loads a hidden image from your server, and the server records the time, the IP address, the app and system, and a rough location. For a click, the link passes through your server, which records the click and sends the reader on to the page. A UTM tag is the same for every recipient and does not identify the person.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/01-what-an-open-and-a-click-send.png)

## 2. Why opens and clicks do not match real readers

An image request does not always mean that someone read an email. A link request does not always mean
that someone chose to click the link.

Apple Mail's Mail Privacy Protection can download images before the reader opens the email. On a Mac,
the setting is called Protect Mail Activity.
[Apple](https://support.apple.com/guide/mail/protect-email-privacy-mlhlp1205/mac) says: "remote content
is privately downloaded in the background when you receive a message (instead of when you view it)".

This can record an open without anyone reading the email. The sender also sees an IP address that is not
the reader's. People choose whether to turn on the feature. Apple does not say how many people turn it
on.

The protection does not cover everything. [Apple's iPhone
guide](https://support.apple.com/guide/iphone/use-mail-privacy-protection-iphf084865c7/ios) says: "Mail
Privacy Protection does not extend to links or email attachments such as PDF or other files."

[Gmail serves email images through Google's proxy
servers](https://knowledge.workspace.google.com/admin/gmail/advanced/set-up-an-image-url-proxy-allowlist?hl=en)
when the reader opens the email. A proxy server fetches the image on the reader's behalf. The open is
still recorded, but the request comes from Google rather than from the reader's device. [Gmail
Help](https://support.google.com/mail/answer/145919?hl=en) says: "Senders can’t use image loading to get
information about your computer or location."

[Outlook.com](https://support.microsoft.com/en-us/outlook/external-image-protection-in-outlook-com) also
uses an image proxy. However, [classic Outlook on
Windows](https://support.microsoft.com/en-us/outlook/block-or-unblock-automatic-picture-downloads-in-classic-outlook-email-messages),
[Outlook for
Mac](https://support.microsoft.com/en-us/outlook/automatically-download-pictures-from-the-internet-in-outlook-for-mac)
and [Thunderbird](https://support.mozilla.org/en-US/kb/remote-content-in-messages) block remote images
by default. Someone can read an email without recording an open unless they choose to load its images.

[Proton Mail](https://proton.me/support/email-tracker-protection) loads remote images on its servers
when the email arrives, rather than when it is opened.

Security tools can also make requests. [Microsoft Defender's Safe
Links](https://learn.microsoft.com/en-us/defender-office-365/safe-links-about) scans links before the
email is delivered. [Microsoft's email-marketing
documentation](https://learn.microsoft.com/en-us/dynamics365/customer-insights/journeys/bot-protection)
explains that security services and privacy features can open emails and follow links automatically.

The numbers are still useful for comparing similar campaigns over time. Microsoft's own advice is to use
opens and clicks for these comparisons, not as exact counts of readers.

[Google's email sender guidelines](https://support.google.com/a/answer/81126?hl=en) say: "Google doesn't
track open rates." They also say: "Low open rates aren't necessarily an accurate indicator of
deliverability or spam classification issues."

The diagram sums up who loads the image in each case.

![Who loads the image. The reader's mail app loads it when the email is opened. Apple Mail with Mail Privacy Protection loads it when the email arrives, so an open is recorded even if nobody reads it. Gmail and Outlook.com load it through a proxy. Classic Outlook, Outlook for Mac and Thunderbird block it by default, so nothing is recorded even if the email is read. Security scanners can create clicks or opens without a person.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/02-who-loads-the-image.png)

### In AcelleMail

AcelleMail records every request to its pixel as an open. It has no filter for opens caused by automated
requests, including requests from privacy services or security scanners.

Because a click also creates an open, a scanner that follows a link can add both. Clicks on the
unsubscribe link and the view-in-browser link count as clicks.

[Email Marketing Metrics Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained) explains how to read
campaign results.

## 3. Two permissions: sending and tracking

In the EU, permission to send marketing email and permission to track it involve different rules.

[Article 13 of the EU's ePrivacy
Directive](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02002L0058-20091219) sets the rules
for marketing email. It generally requires consent, with one exception for existing customers called the
"soft opt-in". A business may email its customers about its own similar products or services without
asking first. The customer must have been able to refuse when they gave their address, and every email
must offer a way to opt out.

Article 5(3) of the same directive sets the rules for storing information on a person's device or
accessing information stored there. It generally requires consent after the person has received clear
information. It has two narrow exemptions. One covers storage or access needed only to carry a
communication over a network. The other covers what is strictly necessary to provide a service the
person explicitly asked for.

The consent wording dates from 2009. The 2026 guidance explains how it applies to email pixels. It does
not create this rule.

The [EDPB's technical
guidance](https://www.edpb.europa.eu/system/files/documents/2024-10/edpb_guidelines_202302_technical_scope_art_53_eprivacydirective_v2_en_0.pdf)
applies Article 5(3) to email pixels and tracking links. It says delivering them to a device counts as
storing information, at least through the mail app's cache. The guidance does not decide which
exemptions apply. National regulators address those questions.

Consent must meet the standard of the
[GDPR](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32016R0679), the EU's data protection
law. It must be freely given, specific, informed and unambiguous. In [Planet49, decided on 1 October
2019](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:62017CJ0673), the EU Court of Justice
rejected pre-ticked boxes. It also confirmed that the device rule protects information whether or not it
is personal data.

The [CNIL
recommendation](https://www.cnil.fr/sites/default/files/2026-05/recommandation_tracking_pixels_emails.pdf)
states: "The consent regime for tracking pixels is independent of that applicable to the sending of the
email in question".

The
[ICO](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-direct-marketing-using-electronic-mail/what-else-do-we-need-to-consider/)
enforces the UK's Privacy and Electronic Communications Regulations (PECR). It says: "The electronic
mail marketing rules in PECR apply to the email itself, not to the tracking pixels."

The CNIL gives examples of how this works. An order confirmation can be sent without marketing consent,
but its pixel may still need consent. The same applies to a promotion sent to existing customers under
the soft opt-in.

A newsletter may be sent if the person signed up for it. Whether it may also be tracked depends on the
country.

The diagram separates these two decisions.

![Two permissions, two separate decisions. An order confirmation needs no marketing consent to send, but its pixel may still need consent. A newsletter the person signed up for may be sent, and whether it may be tracked depends on the country. An offer sent to an existing customer under the soft opt-in may be sent, but tracking it needs consent in France.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/03-two-separate-permissions.png)

For more background, see [GDPR for Email Marketing
Explained](https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained) and [Email Consent
Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained).

## 4. France: the CNIL recommendation

The CNIL adopted Deliberation No. 2026-042 on 12 March 2026. It was [published in the Journal officiel
on 14 April 2026](https://www.cnil.fr/fr/recommandation-pixel-suivi-courriels). The CNIL provides an
[English courtesy
translation](https://www.cnil.fr/sites/default/files/2026-05/recommandation_tracking_pixels_emails.pdf),
but the French version takes priority. A [French
Q&A](https://www.cnil.fr/fr/faq-recommandation-pixels-courriers-electroniques) followed on 22 July 2026.

The recommendation is guidance, not a new law. It says that it "is neither regulatory nor exhaustive".
The binding rules are Article 82 of the French Data Protection Act and the GDPR. The CNIL said it would
check whether these rules were being followed during inspections.

Under the recommendation, pixels generally need consent before use unless an exemption applies. Consent
is needed to measure campaign performance, personalise content and create profiles. Consent is also
needed for pixels that detect fraud or bots. Securing a login is treated differently, as the next list
shows.

The recommendation describes two exemptions, each for one narrow purpose:

- A pixel used only to secure a login, for example to check that the email with a login code is opened
  on a device known to belong to the user.
- A pixel used only to measure opens for deliverability, which means helping your emails reach the
  inbox. Its use must be limited to what is strictly needed to adjust how often inactive contacts are
  emailed, or to stop emailing them.

Both exemptions apply only to emails the recipient requested, or emails relating to a service they
requested. These include transactional emails and emails the person agreed to receive. Emails sent to
carry out a public service mission also fall within their scope.

For the deliverability exemption, the CNIL says that, in principle, only the date of the last known open
should be kept. This means keeping the day, without the time.

The [Q&A](https://www.cnil.fr/fr/faq-recommandation-pixels-courriers-electroniques) explains several
limits. There is no separate exemption for an overall open rate. Article 82 applies even when
information is not personal data. This means anonymising the information does not settle the question.

Collecting an IP address, user agent or opening time means the deliverability exemption no longer
applies. This remains true if that information is anonymised or deleted soon afterwards.

Marketing sent under the soft opt-in is not treated as requested email for this exemption.
Abandoned-cart reminders cannot use this exemption either.

When consent is needed, the CNIL recommends asking for it separately for each purpose. One consent may
cover marketing emails and their pixels only when the purposes are related. The CNIL's example is
marketing that is clearly presented as personalised, with pixels that help to personalise the content or
adjust how often emails are sent.

The recommendation applies to every kind of sender and recipient, including customers, potential
customers and employees. The CNIL says it can act against senders based in France, elsewhere in the EU
or outside it, when French law applies. For a sender outside the EU, that includes tracking people in
France.

The recommendation does not directly cover click tracking. The Q&A says Article 82 and the GDPR still
apply. The purpose determines how the tracking is assessed. In our reading, tracking each person's
clicks to measure campaign performance raises the same consent question as open tracking. This is our
interpretation, not the regulator's.

For addresses collected before 14 April 2026, the transition period allowed tracking to continue if
recipients were given a notice. The usual notice period ended on 14 July 2026. That date has passed, so
the full rules apply to old addresses too, unless a justified short extension was documented.

The diagram applies these checks to a pixel that measures opens.

![Does an open pixel need consent in France? Three checks for a pixel that measures opens. Is the pixel used only to adjust how often inactive contacts are emailed, or to stop emailing them? Did the person ask for this email? Do you keep only the date of the last open, with no time, IP address or device details? One no means consent is needed, and three yeses mean the pixel may be exempt. A pixel used only to secure a login has its own exemption. In our reading, AcelleMail's pixel needs consent in France.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/04-does-this-pixel-need-consent-in-france.png)

### In AcelleMail

In our reading, AcelleMail's pixel does not fit France's deliverability exemption because it records the
time, IP address and user agent. It therefore needs consent in France.

## 5. Italy and the rest of the EU

Italy's Garante adopted its
[guidelines](https://www.garanteprivacy.it/home/docweb/-/docweb-display/docweb/10241943) on 17 April
2026. They were published in the Gazzetta Ufficiale on 29 April 2026.

The [compliance deadline is 29 October
2026](https://www.garanteprivacy.it/home/docweb/-/docweb-display/docweb/10303585). Checks are also
included in the Garante's [inspection plan for August to December
2026](https://www.garanteprivacy.it/home/docweb/-/docweb-display/docweb/10296923).

In ordinary cases, the Garante says email pixels need consent before they are used. It lists examples of
pixels that may not need consent:

- One shared pixel can count an overall open rate, with IP address and client data anonymised.
- Pixels can support login security.
- Pixels can accompany service messages that the sender is legally required to send.

Measuring each person's opens to see how campaigns perform needs consent. Consent is also needed to
change how often emails are sent or to stop sending them based on how interested a person seems. This
differs from France's exemption for inactivity, which applies only in very limited cases.

Italy allows a single consent for marketing emails and their pixels if the request is worded neutrally,
without pressure. People must still be able to stop the tracking while continuing to receive the emails.
Refusing tracking must not limit the service in any way.

The guidelines do not explain whether they apply outside Italy. We suggest planning to follow them if
you send from Italy or to people in Italy.

In Germany, the [BfDI](https://www.bfdi.bund.de/DE/Fachthemen/Inhalte/Telemedien/Newsletter.html) says
newsletter consent does not cover counting pixels. People who refuse tracking must receive the
newsletter without pixels. That page concerns newsletters sent by public bodies. Private companies are
mostly supervised by state authorities.

On 20 December 2022, Denmark's
[Datatilsynet](https://www.datatilsynet.dk/afgoerelser/afgoerelser/2022/dec/anvendelse-af-spy-pixels-i-forbindelse-med-udsendelse-af-nyhedsbrev)
strongly criticised a state fund for using newsletter pixels without valid consent or information. There
was no fine.

In June 2023, Spain's [AEPD fined Mailtrack 100,000
euros](https://www.aepd.es/documento/ps-00328-2022.pdf) under the GDPR for open tracking using pixels.
The fine concerned the tool provider, not a newsletter sender.

Elsewhere in the EU, Article 5(3) applies through national law. Check your national regulator.

The November 2025 Digital Omnibus proposal would move these rules into the GDPR. As of 12 October 2026,
it remains a proposal. The [European Parliament's
file](https://oeil.europarl.europa.eu/oeil/en/procedure-file?reference=2025/0360(COD)) says "Awaiting
committee decision".

## 6. The United Kingdom

The
[ICO](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/what-are-storage-and-access-technologies/)
says PECR regulation 6 applies to email pixels when they store or access information on a device.
Regulation 22 covers the marketing email itself.

The [ICO's email
guidance](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-direct-marketing-using-electronic-mail/what-else-do-we-need-to-consider/)
says: "If the email includes tracking pixels, you must comply with the rules on storage and access
technologies."

These rules also apply to business recipients. The [ICO's business-to-business
guidance](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/business-to-business-marketing/)
says: "The rules on cookies and similar technologies apply to all types of subscriber."

The [general
rule](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/what-are-the-pecr-rules/)
is: "You must obtain prior consent to the UK GDPR standard for their use, unless an exception applies."

Since 5 February 2026, the Data (Use and Access) Act 2025 has allowed an exception for statistical
purposes. [PECR Schedule A1](https://www.legislation.gov.uk/uksi/2003/2426/schedule/A1) sets five
conditions:

1. You provide an online service.
2. The only purpose is to collect statistics about how people use the service to help improve it.
3. You share data only with those helping to improve the service.
4. You provide clear information.
5. People have an easy, free way to object.

The [ICO reads this
narrowly](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/what-are-the-exceptions/):
"It is not for identifying, tracking or monitoring people or groups of people who use your service."

The ICO has not said whether this exception covers email opens or clicks. In our reading, it does not
cover using each person's opens and clicks to build segments, trigger automations or choose who gets a
resend.

When someone withdraws consent, the [ICO
says](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-the-use-of-storage-and-access-technologies/how-do-we-manage-consent-in-practice/):
"You must interpret a withdrawal of consent as a request for erasure".

Since 5 February 2026, the [ICO can impose PECR
fines](https://ico.org.uk/about-the-ico/media-centre/news-and-blogs/2026/02/statement-on-the-commencement-of-the-data-use-and-access-act-duaa/)
of up to £17.5 million or 4% of global turnover. We found no ICO action aimed at email pixels as of 12
October 2026. Its published tracking priorities cover websites, online advertising and apps, not email.

The table compares the guidance described above.

| Topic | France | Italy | United Kingdom |
|---|---|---|---|
| The rule that applies | Article 82 of the French Data Protection Act applies, with the CNIL recommendation of 14 April 2026. | Article 122 of the Privacy Code applies, with Garante guidelines of 17 April 2026. | PECR regulation 6 applies, with ICO guidance of 29 April 2026. |
| Tracking each person's opens for marketing | Consent is needed. | Consent is needed. | Consent is needed unless an exception applies. No exception has been confirmed for email. |
| An overall open rate | There is no separate exemption. | One shared, anonymised pixel may be exempt. | The guidance does not address this for email. |
| Removing inactive contacts based on opens | This is exempt only under strict limits. | Consent is needed. | The guidance does not address this. |
| Click tracking | The recommendation does not directly cover it. Article 82 still applies. | The guidelines do not address it. | The assessment depends on the purpose. |
| One consent for marketing and tracking | This is possible only for related purposes, such as personalised marketing. | One neutrally worded consent is allowed if people can stop only the tracking. | The guidance does not address this for email. |
| The key date | The usual notice period for old addresses ended on 14 July 2026. | The compliance deadline is 29 October 2026. | Final guidance was published on 29 April 2026. |

## 7. Outside Europe

In the United States, the [FTC's CAN-SPAM compliance
guide](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business) covers
email content and unsubscribing. CAN-SPAM does not address tracking.

California's [CCPA definition of a unique
identifier](https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CIV&sectionNum=1798.140)
includes "beacons, pixel tags". Businesses above the CCPA's thresholds must give notice at or before
they collect information.

In Arizona, class actions that challenged email pixels under a phone-records law failed. In [Smith v.
Target, decided on 13 November
2025](https://www.azcourts.gov/Portals/0/OpinionFiles/Div1/2025/CV25-0120%20Smith%20v.%20Target%20OP%20Final%20(11112025).pdf),
the Court of Appeals ruled that the law covers records of communication services, "not marketing metrics
collected by retailers about email engagement".

Canada's [CASL rule on installing computer
programs](https://laws-lois.justice.gc.ca/eng/acts/E-1.6/FullText.html) does not mention email pixels.
This guide does not cover other countries' privacy laws. Check with your local regulator.

## 8. Asking for consent and honouring a no

The steps below follow the GDPR's rules on consent. Where advice comes from one regulator, we name it.

At sign-up, the [CNIL recommends asking for tracking consent at the same time as the email
address](https://www.cnil.fr/sites/default/files/2026-05/recommandation_tracking_pixels_emails.pdf). Our
advice is to give tracking its own choice, without a pre-ticked box, because some regulators expect a
separate consent. The CNIL also recommends the following:

- Refusing is as easy as accepting.
- The form names the email address the choice applies to.
- The form explains that tracking covers every device the person uses to read those emails.
- The form gives a short summary of the purpose, with a link to more detail.

Our example wording is: *Yes, you may record when I open your emails and which links I click, to measure
how your emails perform.* Name every purpose you use the data for. If you also use opens and clicks to
decide who receives which email, say that too.

For an existing list, send an email without tracking. Link to a page where the person confirms consent
by taking an action, such as pressing a button. The CNIL recommends this because some mail apps load
links automatically. A click on its own should therefore not count as consent.

If the person does nothing, treat it as a no. The
[CNIL](https://www.cnil.fr/sites/default/files/2026-05/recommandation_tracking_pixels_emails.pdf) says:
"the recipient’s inactivity must be analysed as an expression of a refusal to consent to the use of
tracking pixels."

Record each refusal, so that you do not ask the same person again too soon. The CNIL calls a pause of 6
months good practice.

Keep evidence of each person's consent and how it was given. [GDPR Article
7(1)](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32016R0679) requires you to be able to
show that the person gave consent.

It must be easy to withdraw consent. [GDPR Article
7(3)](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32016R0679) says: "It shall be as easy
to withdraw as to give consent."

The CNIL recommends a link to withdraw consent in every email footer. [Italy's
Garante](https://www.garanteprivacy.it/home/docweb/-/docweb-display/docweb/10241943) suggests a footer
link or icon that stops only tracking.

After a person refuses or withdraws consent, stop tracking future emails. You also need to consider
older emails. The [CNIL's Q&A](https://www.cnil.fr/fr/faq-recommandation-pixels-courriers-electroniques)
says requests from pixels linked to withdrawn consent must be ignored. In the UK, the ICO treats
withdrawal as a request to delete data.

Several features that rely on opens stop working for contacts whose emails are not tracked. These
include resending to non-openers, [A/B tests judged on opens](https://acellesend.com/kb/articles/email-ab-testing-explained),
automations that branch on opens, and list cleaning based on opens.

Our advice is to use other signals, such as replies, purchases or sign-ins. Unsubscribes and bounces
also help guide sending decisions. For inactive contacts, consider sending less often or asking whether
they still want your emails.

## 9. Tracking in AcelleMail

AcelleMail can send tracked and untracked emails. It has no per-contact tracking switch, so you organise
the split yourself, as described below. Two limits matter before you start:

- AcelleMail keeps each person's current answer, but not when or how they gave it. The part on keeping
  evidence below explains what you can do.
- The split affects future emails only. Emails a person received while tracking was on keep recording
  their opens and clicks, and AcelleMail cannot ignore those requests for one person. In our reading,
  that falls short of what the CNIL's Q&A asks after a withdrawal.

### What AcelleMail records and where to switch it off

For each recorded open or click, AcelleMail stores the time, the IP address and the full user agent. It
looks up an approximate location from the IP address on your own server, without an outside service.

It has no filter for machine opens, no consent record and no retention setting. Opens and clicks remain
until you delete the contact or the campaign.

On a campaign's **Setup** step, find **Advanced Settings**. Both **Track opens** and **Track clicks**
are ticked by default.

Untick **Track opens** and AcelleMail adds no open-tracking pixel, but a click still creates an open
while **Track clicks** is on. Untick **Track clicks** and links are left as they are. With both switches
off, an HTML email still loads a small transparent image from your AcelleMail server. This image has the
same address for every recipient, so AcelleMail does not record it as an open.

The screenshot marks **Track opens** (1), **Track clicks** (2) and **UTM tracking** (3).

![The Advanced Settings card on a campaign's Setup step in AcelleMail, with three marks: 1 Track opens and 2 Track clicks, both ticked by default, and 3 the UTM tracking card.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/05-tracking-switches-in-campaign-setup.png)

The **UTM tracking** card works independently through **Enable UTM tracking**. Its tags are the same for
every recipient. They stay on links when **Track clicks** is off, so website analytics can still
identify the campaign.

Each automation **Send email** node has separate **Track opens** and **Track clicks** switches. Find
them under **Tracking & delivery** on its **Setup** tab. Both are on by default.

A/B test campaigns have no tracking switch and always use a whole list. Do not use them for contacts who
did not agree to tracking.

The list's double opt-in confirmation, welcome and goodbye emails contain no tracking pixel or tracked
links.

### Track only the people who agreed

First, record each person's answer:

1. Open **Manage list fields** on your list. Under **Add field**, choose **Radio**. Give it a label such
   as "Email tracking". Under **Options (label — value)**, add *Yes, you may record my opens and clicks*
   with the value `yes`, and "No" with the value `no`. Leave **Default value** empty, so that nothing is
   pre-selected, and keep **Visible** on. Click **Save fields**. Use **Radio**, not **Checkbox**,
   because the update-profile page does not keep a ticked checkbox.
2. The hosted sign-up and update-profile pages show the new field. Under **Forms / Pages**, add a short
   summary of the purpose, with a link to your privacy policy, to the **Public signup page** and the
   **Update form**. If your website uses an embedded form, copy its code again. The code you pasted
   earlier does not contain the field.
3. Put the **Update profile URL** merge tag in your email footer. People can then change their answer.
   To ask an existing list, send an untracked campaign with the same link. Opening the link is not
   consent. The person has to choose Yes and save the form.

The screenshot marks **Radio** (1), the empty **Default value** (2) and **Options (label — value)** (3).

![A new Radio field called Email tracking on the Manage list fields page in AcelleMail, with three marks: 1 the Radio button under Add field, 2 the Default value left empty so that nothing is pre-selected, and 3 the two options with their values, Yes, you may record my opens and clicks (yes) and No (no).](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/06-tracking-consent-field.png)

Then send each campaign as two campaigns with the same content, one tracked and one untracked:

4. Create a segment with the condition set to your field, **equal**, `yes`.
5. For the tracked campaign, choose the list on **Recipients**. Under **Segment (optional)**, choose
   that segment. Leave **Track opens** and **Track clicks** on.
6. Make the untracked campaign with **Replicate**, in the tracked campaign's menu on the campaigns page.
   The copy keeps the content and the tracking settings.
7. On the copy's **Recipients** step, choose the same list and **All subscribers**. Under **Exclude
   lists & segments**, add the same list with the same segment. Use the exclusion rather than a second
   segment with **not equal** `yes`, which would leave out people who never answered.
8. On the copy's **Setup** step, untick **Track opens** and **Track clicks**. Then send both campaigns
   at the same time.
9. In automations, add a **CONDITION** node before each email. Set it to **Subscriber field**, pick your
   tracking field, choose **is** and type `yes`. On the YES branch, add a **Send email** node with
   tracking on. On the NO branch, add a second **Send email** node with **Track opens** and **Track
   clicks** off.

The screenshot marks **Segment (optional)** (1) and **Exclude lists & segments** (2).

![The Recipients step of a campaign in AcelleMail, with two marks: 1 the Segment picker, where the tracked email goes to the segment of people who said yes, and 2 the Exclude lists and segments card, where the untracked email excludes the same segment.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-tracking-and-privacy-explained/07-tracked-and-untracked-recipients.png)

### What to expect in your reports

An untracked campaign's report looks like a campaign nobody opened. It shows 0.00% and the **Room to
grow** banner. Only the campaign details show that tracking was off.

Segments based on "last opened" treat every untracked contact as inactive. Do not use open-based
cleaning on those contacts.

### Keeping evidence of consent

AcelleMail keeps only each person's current answer. It does not record when or how they gave it, so on
its own it cannot show that a person agreed. If you collect answers through your own website or another
tool that logs each submission, keep that log. Also keep the wording of your consent request and the
date you started using it.

For a dated snapshot of everyone's answers, open **Subscribers** on your list and choose **Export**. The
CSV file shows each answer on the day you export it, but not when it was given.

### When someone changes their answer to no

From the next send, the person gets the untracked email. Emails they received while tracking was on keep
reporting their opens and clicks, and this continues after they unsubscribe. Changing the field does not
stop it, and AcelleMail cannot ignore old pixels for one person.

Deleting the contact stops it, but it also deletes their history and removes them from the list.
Deleting the campaign stops it for everyone who received that campaign.

## 10. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| You assume the rules do not apply because you are not in France. | The CNIL says French law can reach senders outside the EU who track people in France. Other EU countries apply the same Article 5(3), and the UK has its own rules. | Check the rules for the countries your contacts live in. |
| You anonymise the IP address and treat the pixel as exempt. | In France, the deliverability exemption does not apply if you collect the IP address, user agent or time. This remains true even if you anonymise the information later. | Ask for consent, or switch tracking off. |
| You treat newsletter consent as consent to tracking. | Germany's federal commissioner says it is not, in guidance for public bodies. Italy allows one neutrally worded consent only if people can stop tracking separately. | Ask for tracking consent separately. |
| You untick **Track opens** and think nobody is tracked. | **Track clicks** may still be on, and a click also creates an open. Older emails also continue to report activity. | Switch off both settings and take older emails into account. |
| You treat a click as proof that a person read the email. | Security scanners follow links. Clicks on the unsubscribe and view-in-browser links are counted too. | Compare clicks across similar email campaigns rather than treating one click as proof. |
| You ask for consent in a tracked campaign, or treat a link click as consent. | The request would track people before they agree. Some mail apps load links automatically. | Send an untracked email linking to a page with a confirmation button. |
| You rely on the UK statistics exception for email tracking. | The ICO interprets the exception narrowly and has not applied it to email tracking. | In our reading, treat campaign tracking for each person as needing consent. |
| You remove contacts who have not opened emails without checking the rules. | France allows this only under strict limits. Italy requires consent to track each person's inactivity. | Use other signals for contacts who are not tracked. |

## Summary

1. An open is usually a request for a hidden image, and a click is a request that passes through a
   tracking server. In AcelleMail, a click also creates an open. Neither proves that a person read the
   email.
2. Privacy services and security scanners can create requests. Blocked images can also hide the fact
   that someone read the email.
3. Permission to send and permission to track are separate decisions. A newsletter subscription does not
   automatically mean someone has agreed to tracking.
4. France, Italy and the UK apply different exemptions. In our reading, AcelleMail's pixel does not meet
   France's deliverability exemption.
5. Ask people to make a clear choice about tracking and keep evidence of their choice. For existing
   lists, use an untracked request and a confirmation button.
6. AcelleMail enables **Track opens** and **Track clicks** by default. Campaign-wide **UTM tracking**
   can remain enabled without per-person click tracking.
7. Use a **Radio** field and manually divide contacts into tracked and untracked groups. People who have
   never answered belong in the untracked group.
8. Changing the field does not stop tracking from older emails. AcelleMail has no per-person way to
   ignore those requests.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email A/B Testing Explained: How to Run a Test You Can Trust

> How to run an email A/B test you can trust: what to measure, how many contacts you need, how to read a p-value, and the settings in AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/email-ab-testing-explained
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, A/B Testing, Click Rates, Analytics
- Published: 2026-10-11 · Last updated: 2026-10-21
- Reading time: 20 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

An A/B test compares two versions of an email. Each version goes to a separate group of contacts. It is
the most direct way to learn which subject lines, layouts and offers work for your readers.

However, one version almost always gets a few more clicks, even when the two emails are equally good.
When the groups are small, that "winner" is often just luck. This guide explains how to run a test whose
result you can trust. It also explains where to find each setting in AcelleMail.

This guide covers:

1. What an A/B test is
2. What to test, and which number decides
3. A fair test: random groups, sent together
4. How many contacts a test needs
5. Reading the result
6. Two habits that create false winners
7. Learning from your tests
8. A/B testing in AcelleMail

It ends with the most common mistakes and a one-screen summary.

## 1. What an A/B test is

An A/B test, also called a split test, sends two versions of an email to two groups of contacts at the
same time. The groups are chosen at random. The test compares the versions using one measure that you
chose before sending.

The version you would normally send is the control. The new version is the variant.

A good test has five parts:

1. You write down the question in advance, for example whether a shorter subject line brings more
   clicks.
2. The two versions differ in one thing only.
3. The contacts are split into groups at random.
4. One measure, chosen in advance, decides the result.
5. The rule for deciding is also set before you send.

There are two common test designs. In a test-and-roll-out design, you send the versions to part of the
list and wait. Then you send the better version to everyone else. In a whole-list split, each version
goes to half of the list. You use the result to plan the next campaign.

The numbers for both versions are affected by chance. Even when the two versions perform exactly the
same, one of them will usually get a few more clicks. A [statistical
test](https://www.itl.nist.gov/div898/handbook/prc/section1/prc13.htm) tells you how often chance alone
would produce a difference that large.

The diagram below shows the steps of a test-and-roll-out campaign. Section 5 explains the p-value it
shows.

![Test first, then send the winner to the rest. Two random groups of 10,000 contacts from a list of 50,000 get versions A and B at the same time. After 24 hours, version B has the higher unique click rate, 4.8% against 4.0% with a p-value of 0.006, and goes to the other 30,000 contacts.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/01-how-an-email-ab-test-works.png)

## 2. What to test, and which number decides

Each part of an email matters at a different stage. People see the From name, subject line and preheader
in the inbox, before they open the email. The content, layout, images and call to action matter after
people open it. The offer and the page the link leads to matter after the click.

Choose a measure that your change can affect. The report must also record that measure reliably.

### Why opens can mislead

Opens are not reliable. Apple's [Mail guide for
Mac](https://support.apple.com/guide/mail/protect-email-privacy-mlhlp1205/mac) explains what happens
when Mail Privacy Protection is on. Remote content, which includes the small image that records an open,
"is privately downloaded in the background when you receive a message (instead of when you view it)".
Its [iPhone guide](https://support.apple.com/guide/iphone/use-mail-privacy-protection-iphf084865c7/ios)
says the feature "prevents senders from seeing if you've opened the email message they sent you".

So your report includes opens from people who never read the email. A higher open rate for one version
may only mean that its group happened to include more Apple Mail readers. [Email Marketing Metrics
Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained) and [Open Rates + Apple
MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp) explain this in more detail.

### Why clicks are a better guide

Clicks are more reliable. The same iPhone guide says: "Mail Privacy Protection does not extend to links
or email attachments such as PDF or other files."

Clicks are not perfect either. Some company mail systems use security filters that follow links to check
them. Microsoft's guide to [Safe Links in Defender for Office
365](https://learn.microsoft.com/en-us/defender-office-365/safe-links-about) says that "URLs are scanned
prior to message delivery". Even so, unique clicks are the most reliable measure in an email report. A
unique click counts each contact once, however many times they click.

Use clicks to judge a subject line, From name or preheader test as well. A subject line that brings more
opens but no more clicks has not helped. When the goal is a sale, the winner is the version that brings
more orders. Tag the links with UTM parameters so that your shop or analytics tool can count the orders
from each version. [UTM Parameters in
AcelleMail](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking) shows how.

Change one thing at a time. If the subject line and the button both change, you cannot tell which of
them made the difference.

The diagram below shows which measure you can use to judge each part of an email.

![Judge every test by clicks or orders. The From name, subject line and preheader work in the inbox, the content after opening, and the offer and landing page after the click. Opens reflect only the inbox and include people who never read the email. Unique clicks reflect the inbox and the content. Orders reflect every part of the email.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/02-which-number-each-test-can-move.png)

## 3. A fair test: random groups, sent together

Every contact should have the same chance of being assigned to each group. The email is then the only
difference between the groups that is not due to chance.

If you assign contacts to groups in order, such as putting the oldest contacts first, the groups can
differ for reasons unrelated to the email. Contacts who joined years ago often behave differently from
contacts who joined last week. The statistical tests that compare two rates, such as those in the [NIST
e-Handbook](https://www.itl.nist.gov/div898/handbook/prc/section3/prc33.htm), assume that the groups
were chosen at random. They also assume that one contact's response does not affect another's.

Send all versions at the same time. This way, the time of day and the day of the week affect each
version equally.

If you test versions and then send the winner to everyone else, the test groups should fairly represent
the whole list. The winning version needs to work for those contacts too.

## 4. How many contacts a test needs

The number of contacts a test needs depends on four things:

1. One factor is your current rate, for example the click rate of similar campaigns.
2. Another is the smallest difference that would be worth acting on.
3. The significance level is the risk you accept of reporting a difference when the versions perform the
   same. The usual choice is 5%, which means about one test in 20.
4. The power is the chance that the test finds a real difference of the size you chose. A common choice
   is 80%.

The table shows how many contacts each version needs to detect an increase of 10%, 20%, 30% or 50%. The
figures are for a two-sided test, which looks for a difference in either direction. They use the 5%
level with 80% power and are rounded up to the nearest 100.

| Current click rate | +10% | +20% | +30% | +50% |
|---|---|---|---|---|
| 2% | 80,700 | 21,200 | 9,800 | 3,900 |
| 4% | 39,500 | 10,400 | 4,800 | 1,900 |
| 6% | 25,800 | 6,800 | 3,200 | 1,300 |

The increases are relative. +20% means that a 4% click rate rises to 4.8%, and +10% means that a 2%
click rate rises to 2.2%.

If you want to detect half the difference, you need roughly four times as many contacts. At a 4% click
rate, detecting +20% needs 10,400 contacts per version. Detecting +10% needs 39,500 contacts per
version.

### What a smaller test can detect

You can also look at the smallest difference a test of a given size can find. At a 4% click rate, with
80% power, these are the smallest increases each group size can detect:

| Contacts per version | Smallest increase it can detect |
|---|---|
| 500 | About +106% (4% → 8.2%) |
| 1,000 | About +71% |
| 2,500 | About +43% |
| 5,000 | About +29% |
| 10,000 | About +20% |
| 25,000 | About +13% |

A list of 5,000 contacts with a 20% test audience gives two groups of 500. A test of that size can
reliably detect only a very large difference, roughly a doubling of the click rate.

With a smaller list, you still have options. You can test bigger changes, such as a different offer
rather than one word in the subject line. Another option is to send each version to half of the whole
list and use the result for the next campaign. You can also run the same kind of test over several
campaigns.

We worked out these figures with the standard formula for comparing two rates. To check your own
numbers, try [Evan Miller's sample size
calculator](https://www.evanmiller.org/ab-testing/sample-size.html). Calculators use slightly different
formulas, so their answers can differ from this table by a few per cent.

The diagram below shows how the number of contacts grows as the difference you want to detect gets
smaller.

![Smaller differences need far more contacts. At a 4% click rate, each version needs 1,900 contacts to detect an increase of 50%, 4,800 for 30%, 10,400 for 20% and 39,500 for 10%. Two groups of 500 can reliably detect only an increase of about 106%.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/03-contacts-needed-per-version.png)

## 5. Reading the result

The p-value tells you how likely a difference at least as large as yours would be if both versions
performed exactly the same. The assumption that both versions perform the same is called the null
hypothesis. The [NIST e-Handbook](https://www.itl.nist.gov/div898/handbook/prc/section1/prc131.htm)
defines the p-value as "the probability of the test statistic being at least as extreme as the one
observed given that the null hypothesis is true".

A small p-value means that chance alone would rarely produce your result. The usual threshold is 5%,
which is a p-value of 0.05. The same handbook explains that this level "implies that the null hypothesis
is rejected 5 % of the time when it is in fact true". This means that when two versions perform the
same, about one test in 20 will still show a "significant" difference.

Set the threshold before the test. The handbook says: "It is good practice to decide in advance of the
test how small a p-value is required to reject the test."

A result that is not significant does not prove that the versions are equal. It means that the test
could not tell them apart. The handbook's page on [statistical
tests](https://www.itl.nist.gov/div898/handbook/prc/section1/prc13.htm) describes such a result as
"possibly indicating we may not yet have enough data".

### The same difference, different certainty

Suppose one version gets a 5% click rate and the other gets 4%. That is a 25% increase in every row
below. Only the size of the groups changes.

| Contacts per version | Clicks at 5% | Clicks at 4% | p-value |
|---|---|---|---|
| 500 | 25 | 20 | 0.45 |
| 2,000 | 100 | 80 | 0.13 |
| 5,000 | 250 | 200 | 0.016 |
| 10,000 | 500 | 400 | below 0.001 |

Only the last two rows have a p-value below 0.05. The p-values come from the [standard z-test for two
proportions](https://www.itl.nist.gov/div898/handbook/prc/section3/prc33.htm). AcelleMail uses the same
test in its report.

Statistical significance is not the same as practical value. With very large groups, even a tiny
difference can be significant without being worth acting on. Decide before the test what size of
difference would change what you do.

The diagram below shows the four rows of the table side by side.

![The same difference, four different answers. Version A gets a 4% click rate and version B gets 5% in every row. With 500 contacts per version the p-value is 0.45, and with 2,000 it is 0.13, so the difference could be chance. With 5,000 it is 0.016, and with 10,000 it is below 0.001, so the difference is significant.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/04-same-difference-different-certainty.png)

## 6. Two habits that create false winners

### Checking again and again, and stopping early

It is tempting to watch the results and stop the test as soon as one version looks like a clear winner.
The problem is that the significance calculation assumes "that the sample size was fixed in advance", as
[Evan Miller](https://www.evanmiller.org/how-not-to-run-an-ab-test.html) explains.

His example shows how much this matters. A website tests a new logo that makes no difference at all. The
test is checked after every visitor and stopped at the first significant result, or after 150 visitors.
It wrongly finds a significant result 26.1% of the time, instead of 5%. He calls this a worst case,
because the test is checked after every single visitor.

The researchers [Johari, Pekelis and Walsh](https://arxiv.org/abs/1512.04922) describe ordinary p-values
and confidence intervals as "wholly unreliable" when users decide when to stop by "continuously
monitoring" their tests.

For an email test, decide before sending how long you will wait. Judge the result once, at the end.
Miller advises: "Decide on a sample size in advance and wait until the experiment is over".

### Testing too many versions

Each extra version takes contacts from the others, so every group gets smaller. It also adds
comparisons, and each comparison is another chance of a false winner.

With three versions, there are three pairs to compare. Consider a test where all three versions perform
equally well and each pair is judged at the 5% level. The chance that at least one pair shows a
"significant" difference is about 12%. With four versions, there are six pairs, and the chance is about
20%. We worked out these figures with a simulation of 200,000 tests.

Statisticians make adjustments for this. The [Bonferroni
method](https://www.itl.nist.gov/div898/handbook/prc/section4/prc473.htm), for example, divides the 5%
by the number of comparisons. For most lists, two versions are enough.

## 7. Learning from your tests

Keep a simple log of every test. Write down the question, what changed, the measure used to decide the
result, the size of each group, the result and the p-value.

A result that was not significant does not show that one version is better. Record it as a test that
could not show a difference between the versions, and do not turn it into a rule for future campaigns.

Before you adopt a change as standard practice, repeat the test to see whether you get the same result.

Over time, the log shows which kinds of change matter to your readers. It also keeps uncertain results
separate from changes that have performed better more than once.

## 8. A/B testing in AcelleMail

AcelleMail can run both types of test. You can test part of the list and then send the winner to
everyone else, or split the whole list. To start, open **Campaigns** → **Email** and click **Create
campaign**. On the **A/B Test** card, click **Create**. The test is set up in three steps: **Campaign**,
**Variants** and **Winner Rules**.

A new A/B campaign starts with a test audience of 100%, **Exact split** and **Highest open rate**. You
will change all three settings in the steps below.

### Step 1: Campaign

1. Under **Split configuration**, set the **Test audience size** (1). The slider runs from 1% to 100% of
   the list. The test audience is shared between the variants, and everyone else receives the winner. A
   test audience of 100% means the whole list is split, with no winner sent afterwards.
2. Under **Assignment strategy** (2), choose **Hash split**. It puts each contact in a group using a
   code calculated from the contact's unique ID. This mixes old and new contacts in every group. The
   screen describes the group sizes as "Approximate (±3%)".
3. Check the **Rollout summary** (3). It shows the test audience, who will receive the winner, the rules
   for choosing the winner and how long AcelleMail waits before deciding.

![The Campaign step of an A/B test in AcelleMail with three marks: 1 Test audience size at 20%, 2 Assignment strategy with Hash split selected, 3 the Rollout summary. A note under Exact split says that it fills the groups in the order contacts were added.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/05-ab-test-split-settings.png)

**Exact split** fills the groups in the order the contacts were added to the list. The test audience
contains the oldest contacts. Variant A gets the first group of these contacts, and Variant B gets the
next. These groups can differ before either email is sent. The screen calls **Exact split** "Recommended
for most lists" because its groups have exactly the size you set. For a fair test, it matters more that
the groups are chosen at random than that their sizes match to the last contact.

### Step 2: Variants

1. Add the first version with **Add variant**. It becomes the control and carries the **Control** badge.
   Each variant is a complete email with its own subject line, design and content.
2. To make the second version, click the copy button on the control's card (**Duplicate variant**). Then
   change one thing in the copy.
3. Each new variant gets an even share of the test audience, and **Balance splits** lets you change the
   shares. Keep them equal, because two groups of the same size give the test its best chance of finding
   a real difference.

### Step 3: Winner Rules

After the wait you set in this step, AcelleMail picks the variant with the highest rate and sends it to
the rest of the audience. The rate is unique clicks or unique opens divided by the emails the report
counts as **Delivered**. AcelleMail does not check whether the difference is statistically significant,
so the chosen winner can be ahead by chance. For this campaign, that does little harm, because two
versions that differ only by chance are about equally good. The harm comes later, if you treat that
winner as a lesson.

1. Under **Win condition** (1), choose **Highest click rate**. The other options are **Highest open
   rate** and **Manual selection**.
2. Set **Wait before deciding** (2) before you send. This sets how many hours AcelleMail waits after
   sending the test emails before choosing the winner. With a short wait, the variants are judged only
   on the people who respond quickly. These people may not behave like the rest of your readers. A long
   wait delays the email for everyone else. Choose a wait that covers most of the clicks your past
   campaigns received.

![The Winner Rules step in AcelleMail with two marks: 1 the Highest click rate win condition, selected, and 2 Wait before deciding, set to 72 hours. A note says to decide the wait before you send.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/06-ab-test-winner-rules.png)

With **Manual selection**, the rest of the audience waits until you click **Choose as winner** on one of
the variants in the report. Use it when orders decide your test. Compare the orders from each version in
your shop or analytics tool, then choose the winner yourself. To check whether the difference is
significant, enter the contacts who ordered and the emails delivered for each version in Evan Miller's
[chi-squared test](https://www.evanmiller.org/ab-testing/chi-squared.html).

AcelleMail sends all variants in the same run, at the same time. An A/B test therefore compares the
emails themselves, not send times. To compare send times, use separate campaigns.

### Read the report

To see the results, click the campaign's name in the campaign list, or open its **A/B Report** tab.
While AcelleMail waits to choose a winner, a banner says "Awaiting winner determination". Once a winner
is chosen, the banner (1) names it and reports the improvement over the runner-up.

With exactly two variants, the **Statistical confidence** card (2) shows the **Z-score**, the
**P-value**, the **Lift** and the **Sample size**. It also shows one of two sentences: "This result is
statistically significant (p < 0.05)." or "Not enough data for statistical significance yet."

The percentage the card calls "confident" is 1 minus the p-value. It is not the chance that the winner
is better, so read the **P-value** itself. The card compares the rate used by the **Win condition**.
When you choose **Manual selection**, it compares open rates.

The report in the screenshot below is an example. Variant A had 26 clicks from 596 delivered emails, and
Variant B had 21 clicks from 591. The banner says "Click rate improved by 22.8% over the runner-up." The
card shows a p-value of 0.4741 and "Not enough data for statistical significance yet." To detect a
difference of that size reliably, each variant would need about 9,100 contacts.

![An A/B Report in AcelleMail with two marks: 1 the banner that names Variant A the winner because its click rate improved by 22.8%, and 2 the Statistical confidence card with a p-value of 0.4741 and the message that there is not enough data for statistical significance yet. Notes say that 52.6% confident is 1 minus the p-value, and that the result should be treated as a tie.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-ab-testing-explained/07-ab-test-report-significance.png)

Treat a result like this as a tie. Sending either variant to the rest of the list was fine, but do not
adopt the change as a rule.

The **Engagement over time** chart shows the opens and clicks for each variant. It tells you how long
your readers usually take to respond. This helps you choose the wait for the next test.

## 9. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| You choose the winner by open rate. | With Apple Mail Privacy Protection, opens are recorded for emails nobody has read. | Compare the versions by unique clicks. |
| You use 500 contacts for each version to test a small change. | A test of that size can reliably detect only very large differences. | Work out how many contacts you need first. Test a bigger change, or use the whole list. |
| You declare a winner with 26 clicks compared with 21 clicks. | A difference that small can easily be due to chance. | Read the p-value, and treat the result as a tie. |
| You check the result again and again and stop when it looks good. | Stopping early creates many more false winners. | Before sending, decide how long to wait. Judge the result once. |
| The subject line and the content change together. | You cannot tell which change made the difference. | Change one thing at a time. |
| A small list is split between four versions. | The groups get smaller, and each extra comparison increases the chance of a false winner. | Test two versions. |
| **Exact split** is selected. | The groups follow the order in which contacts joined the list. | Choose **Hash split**. |

## Summary

1. An A/B test compares a control and a variant. Before you send, decide on the question, the measure
   and how you will choose a winner.
2. Choose a measure that the change can affect. Prefer unique clicks to opens, or orders when the goal
   is a sale.
3. Assign contacts at random and send all versions at the same time.
4. Work out how many contacts the test needs before you send. Small differences need far more contacts
   than large ones.
5. Read the p-value as well as the difference. A result that is not significant does not prove the
   versions are equal.
6. Decide in advance how long to wait, and judge the result once. Two versions are enough for most
   lists.
7. Keep a record of your tests, and repeat a promising result before you make it standard practice.
8. In AcelleMail, choose **Hash split** and **Highest click rate**. Check the **P-value** in the report,
   because the automatic winner does not need to be significant.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Collect Feedback with a Survey Funnel

> Build a survey page from the Survey Funnel template, add your questions, publish it and read every answer on the Survey results screen.

- Canonical URL: https://acellesend.com/kb/articles/collect-feedback-with-a-survey-funnel
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Funnels — Funnel pages, opt-in forms, surveys, products and checkout, custom domains
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail, Funnels, Surveys, Personalization
- Published: 2026-10-06 · Last updated: 2026-10-06
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

A survey funnel is a page with your questions, followed by a thank-you page. You share its address, or send each subscriber a personal link in a campaign, and every answer lands on one results screen.

## Before you start

- **FUNNELS** in the left menu. If it is not there, see *Common issues*.
- For personal links (Step 10): a mailing list with subscribers, and a campaign to send them. [Your first email campaign](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail) walks through one.

## Step 1 — Open Funnels and click Create funnel

In the left menu, under **FUNNELS**, click **Funnels**. The page lists your funnels. Click **Create funnel** in the top right.

![The Funnels page with the Create funnel button](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/01-funnels-list.png)

## Step 2 — Pick the Survey Funnel template and name it

The **Create funnel** page shows one card per template. Click **Survey Funnel** — *Collect feedback or qualify leads with a survey page, then a thank you page.* A window opens with the template you picked. Type a name in **Funnel name**, for example *Customer Feedback Survey*, and click **Create**.

![Naming a funnel made from the Survey Funnel template](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/02-name-the-funnel.png)

The name also becomes the funnel's address: *Customer Feedback Survey* is served at `/f/customer-feedback-survey`.

## Step 3 — Look at the funnel's steps

The funnel opens on its **Steps** tab with two pages: **Survey** and **Thank You**. Under the Survey step, a line counts its questions and responses — *3 questions · no responses yet* — next to two buttons, **Manage questions** and **Survey results**. Click **Manage questions**.

![The Survey step with its question count and the Manage questions button](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/03-funnel-steps.png)

The funnel is still a **Draft**, so nobody can open its pages yet. You publish it in Step 7.

## Step 4 — Review the sample questions

The **Survey questions** page lists what visitors answer, in order. The template starts you with three: *What is your name?* (Short text), *How satisfied are you with our service?* (Single choice, Required) and *Any suggestions for us?* (Long text). Click a question to change its text, its help text, whether it is required, or its answer options. Drag the dotted handle on the left to change the order.

![The Survey questions page with the template's three questions](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/04-survey-questions.png)

Under **Add a question** are the five types you can add: **Short text**, **Long text**, **Single choice**, **Dropdown** and **Multiple choice**.

## Step 5 — Add your own question

Click **Multiple choice**. A new question opens at the end of the list. In **Question**, type *Which features do you use?* Under **Answer options**, fill in the two empty options — *Campaigns* and *Automations* — then click **Add option** for a third, *Forms*. Switch on **Required** if every visitor must answer it.

![A new Multiple choice question with three answer options and Required switched on](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/05-add-question.png)

Visitors can tick several options on a **Multiple choice** question. **Single choice** and **Dropdown** take one answer.

## Step 6 — Save the questions

Click **Save questions** at the bottom. The message *Survey questions saved.* appears, and the new question shows in the list with its type and the **Required** label.

![The saved list with the new question and the Survey questions saved message](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/06-questions-saved.png)

A question you added but did not save is gone when you leave the page.

## Step 7 — Publish the funnel

Click the funnel's name at the top of the page to go back to it, then click **Publish** in the top right. The status changes to **Published**, with the funnel's address next to it.

![The published funnel with its public address](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/07-publish.png)

## Step 8 — Answer the survey as a visitor

Click **View live page**. The survey opens in a new tab with every question; a star marks the required ones. Fill it in and click **Submit**.

![The public survey page, filled in](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/08-fill-survey.png)

A required question left empty stops the form: the browser points at it, or a message above the questions names it (see *Common issues*).

## Step 9 — See the thank-you page

After **Submit**, the visitor lands on the funnel's **Thank You** page.

![The thank-you page a visitor sees after submitting the survey](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/09-thank-you.png)

The text on it comes with the template and asks the visitor to confirm a subscription, which does not fit a survey. To write your own, go back to the funnel's **Steps** tab and click **Design page** on the **Thank You** step.

## Step 10 — Send subscribers a personal link

Open **Manage questions** again. At the top, **Personalized link for campaigns** holds a tag like `{{ SUBSCRIBER_SURVEY['…'] }}`. Click **Copy** and paste the tag into your campaign email where the link should appear, for example after *Tell us what you think:*.

![The Personalized link for campaigns box with the Copy button](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/10-personal-link.png)

When the campaign is sent, the tag becomes each recipient's own link to the survey. Answers sent through it carry the subscriber's email on the results screen. Answers sent from the plain address show as *Anonymous*.

## Step 11 — Read the answers

On the funnel's **Steps** tab, click **Survey results** under the Survey step. Three numbers sit at the top: **Responses** (every survey submitted), **Identified** (those submitted through a personal link) and **Questions**. Below, each question has its own card. Text questions list the most recent answers. Choice questions show a bar for each option with its share and count.

![Survey results: the totals and the answers to each question](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/11-survey-results.png)

The two dates at the top limit the page to a period, the last 30 days by default. Change them and click **Apply**.

## Step 12 — Look at each response, or export them

At the bottom of the same page, every response has its own row: when it was sent, who sent it (the subscriber's email, or *Anonymous*) and all its answers. Type an email in the search box to find one respondent.

![One row per response, with the subscriber's email when it is known](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/12-responses-list.png)

To work with the answers in a spreadsheet, click **Export CSV** at the top of the page. The file has one row per response and one column per question, for the dates you chose.

## Step 13 — Check the funnel report

Back on the funnel, click **Report** at the top. The **Survey responses** card counts the surveys submitted in the chosen dates, next to page views and visitors. Click the card to open **Survey results**.

![The funnel report with the Survey responses card](https://acellesend.com/plugins/acelle/landing/images/help/kb/collect-feedback-with-a-survey-funnel/13-funnel-report.png)

## Common issues

| What you see | What to do |
|---|---|
| **FUNNELS** is missing from the left menu | Funnels are switched off on your installation. Ask whoever manages your server to add `FUNNEL_ENABLED=true` to the application's `.env` file. |
| The survey's address shows a 404 page | The funnel is still a **Draft**. Open it and click **Publish** (Step 7). |
| "Survey unavailable — this step has no questions yet. Add them in the funnel editor." on the survey page | The survey has no questions. Open **Manage questions**, add at least one and click **Save questions**. Until then, the Survey step on the **Steps** tab says *No questions yet*. |
| "The Which features do you use? field is required." above the questions | A required question was left empty. Answer it and click **Submit** again. To make a question optional, open it in **Manage questions** and switch off **Required**. |
| A subscriber's answer shows as *Anonymous* | They opened the plain address, for example from a forwarded email, not their personal link. Also, when the funnel's **Settings** tab has a **Subscriber list** chosen, only subscribers of that list are recognized: choose the list your campaign goes to. |
| The thank-you page asks visitors to confirm a subscription | That text comes with the template. Click **Design page** on the **Thank You** step and write your own. |

## What to do after

- Put the personal link in your next campaign: [Your first email campaign](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail).
- Read [Personalization beyond first name](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags) for the other tags you can place next to it.

---

# Install AcelleMail and Get Started

> Run the installation wizard, then add a sending server, set up a plan and subscribe a first user, so your AcelleMail is ready to send email.

- Canonical URL: https://acellesend.com/kb/articles/install-acellemail-and-get-started
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail, Self Hosted, Amazon SES, Pricing Plans
- Published: 2026-10-05 · Last updated: 2026-10-21
- Reading time: 19 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

AcelleMail is installed through a browser-based wizard. Once the application files are on your server, the wizard checks the server requirements, collects the site and administrator settings, connects to your database, and prepares the background job configuration.



Installation is only the first part. Before a user can send a campaign, AcelleMail also needs a real sending server, a plan that makes that server available, and a user subscribed to that plan.



This guide takes you through the complete setup in two stages:



1. Install AcelleMail with the installation wizard (steps 1 to 6).

2. Get the first sending setup working: connect a sending server, configure a plan, make it available, and subscribe a user to it (steps 7 to 11).



## Before you start



Before opening the installer, make sure the server and the services it depends on are ready. You will need:



- A server with PHP 8.3 or newer, MySQL and a web server (Apache or Nginx). [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) helps you choose one.

- An empty MySQL database, and a MySQL user with a password that can access it. The wizard does not create the database.

- The AcelleMail package, a zip file.

- A way to add a cron job on the server: SSH access or a hosting control panel.

- An account with an email sending service, or the details of an SMTP server. This guide uses Amazon SES as the example.



## 1. Upload AcelleMail to your server



Upload the AcelleMail package to the server first. The installer will take over from there.



On Apache, the `.htaccess` file included with AcelleMail forwards requests to the `public` folder, so the package can be placed directly in the document root. Make sure `mod_rewrite` is enabled.



On Nginx, point the site's `root` to the `public` folder inside the AcelleMail package.



Once the files are in place, open your domain in a browser. Because AcelleMail has not been installed yet, the application sends you to the installation wizard:



```text

https://your-domain.com/install

```



## 2. Check the system



The installer guides you through five stages: **System Check**, **Configuration**, **Database**, **Background Jobs** and **Finish**. The first screen checks whether the server is ready before you make any configuration changes.



The first screen, **System Requirements**, checks the PHP version, required extensions and functions, and the directories AcelleMail needs to write to.



| Checked | Items |
|---|---|
| PHP version | 8.3.0 or higher |
| PHP extensions | MySQLi, OpenSSL, Multibyte String, PHP PDO, Tokenizer, PHP ZIP, IMAP, SQLite3, PHP GD, PHP FileInfo, PHP Curl, PHP XML |
| PHP functions | `proc_close()`, `escapeshellarg()` |
| Writable folders, under **Directory Permissions** | `storage/app`, `storage/framework`, `storage/logs`, `storage/job`, `bootstrap/cache` |



![The System Requirements screen of the installation wizard with every requirement checked](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/01-system-check.png)



Once every requirement has a green check, you can continue with the installation.



### If a requirement is not met



If a requirement is missing, the installer identifies the problem on the same screen. For example, a missing PHP extension may be shown as:



```text

IMAP extension is required

```



A directory permission problem is reported similarly:



```text

The directory must be writable by the web server

```



Fix the missing extension or directory permission on the server, then click **Check Again**. Continue only after all required checks pass.



## 3. Enter the site and admin details



The **Configuration** step collects the information AcelleMail needs for the site, the first administrator, and the system email account.



### General Settings



Enter the **Site Name** and **Site Description**. These values identify the installation to your users.



**License Key (Optional)** can be left empty during installation. You can add a license key later from the application settings. If you enter one here, the wizard verifies it before continuing.



### Admin Account



Enter the **Account Name**, **First Name**, **Last Name**, **Email Address**, **Password** and **Timezone** for the first administrator.



This account is the administrator you will use to log in after installation, so use an email address and password you can access.



Keep **Create customer account** enabled. This also creates a user account for the administrator, which lets you test the customer-side plan and subscription flow later in this guide.



![The Configuration step with the General Settings and Admin Account sections](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/02-site-and-admin-info.png)



### System Email



The **System Email** section controls emails sent by AcelleMail itself, such as account activation and password reset messages. This is separate from the sending servers you will configure later for campaign delivery.



Choose a **Mail Driver** that the application can use for these system messages:



- **SMTP**: enter **SMTP Host**, **Port**, **Encryption**, **Username** and **Password**. The wizard tests the connection and does not continue if it fails.

- **Sendmail**: uses the mail program on the server. Check the **Sendmail Path**.



For a production site, SMTP is generally the safer choice. The installer also warns that Sendmail may have deliverability issues.



Enter **From Email** and **From Name**, then click **Save & Continue**.



## 4. Connect the database



On **Database Configuration**, enter the connection details for the MySQL database you prepared before starting the installation: **Database Host**, **Port**, **Username**, **Password** and **Database Name**.



If MySQL is running on the same server as AcelleMail, the host and port are usually:



```text

Database Host: localhost

Port:          3306

```



**Table Prefix** is optional. Leave it empty unless you are sharing the database with another application. If you use one, it can contain up to 12 letters, numbers and underscores.



![The Database Configuration screen with host, port, username, password, database name and table prefix](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/03-database-config.png)



Click **Save & Continue** to test the database connection. If AcelleMail cannot connect, the MySQL error appears at the top of the page and you can correct the connection details without leaving this step.



### Initialize the database



The **Set Up Database** screen confirms that the connection succeeded and is ready for initialization.



Pay attention to **Warning: All existing data will be erased**. The installer removes every table in the selected database before creating AcelleMail's tables, so use an empty database or make sure its existing data can be discarded.



Click **Initialize Database** to create the tables, load the default data, and create the administrator account configured in the previous step.



Database initialization may take several minutes. Leave the browser tab open until the installer moves to the next screen.



![The Set Up Database screen with the data warning and the button showing Processing](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/04-database-initializing.png)



## 5. Set up background jobs



AcelleMail handles campaign delivery in the background rather than sending everything during a browser request. A cron job runs the scheduler every minute to process tasks such as sending campaigns, handling bounces, and managing subscriptions.



This job is essential: without it, queued campaign work will not be processed. Set it up before moving on.



### Choose the PHP path



Under **PHP Binary Path**, the wizard lists the PHP binaries it found on the server. Select the binary that matches the PHP version used by your AcelleMail site.



If the correct binary is not listed, choose **Other path** and enter the path manually under **Enter manually**, for example:



```text

/usr/local/bin/php

```



![The Background Jobs screen with the list of PHP paths and the Check & Save button](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/05-background-jobs-php-path.png)



Click **Check & Save**. The wizard tests the selected PHP binary and reports an error if it cannot be used.



### Add the cron job to your server



Once the PHP path passes the check, the wizard displays the cron command under **Add this cron job to your server:**. The exact command depends on your PHP path and AcelleMail installation directory.



```text

* * * * * /usr/bin/php -q /var/www/acellemail/artisan schedule:run 2>&1

```



Use **Copy** to copy the exact command generated for your server rather than retyping it.



![The Background Jobs screen showing the cron job line with the Copy button](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/06-background-jobs-cron-command.png)



Add the command to the crontab of the user that owns the AcelleMail files. If you have SSH access, log in as that user and run:



```bash

crontab -e

```



If you manage the server through a hosting control panel, add the same command from its cron jobs interface.



The wizard only generates the cron command; it does not install the cron job for you. **Continue** will work even if you have not added it, but campaigns will not run until the cron job is actually configured. Add it now, then click **Continue**.



## 6. Finish the installation



The **Installation Complete!** screen confirms that the application has been installed and shows the address of the admin panel.



![The Installation Complete screen with the admin panel address and the Go to Admin Panel button](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/07-installation-complete.png)



Click **Go to Admin Panel** and sign in with the administrator email address and password you created in step 3.



## 7. Open the admin panel



At this point, the AcelleMail application is installed. The next steps connect it to an email delivery service and configure the first plan that a user can subscribe to.



AcelleMail has two main areas. The user area is where customers work with lists, campaigns, and automations. The admin panel is where you configure the infrastructure and commercial settings, including sending servers, plans, and customers.



If you are currently in the user area, click your name at the bottom left and choose **Admin panel**.



![The account menu at the bottom left of the user area with Admin panel highlighted](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/08-open-admin-panel.png)



## 8. Add a sending server



AcelleMail does not deliver campaign messages by itself. It passes them to a sending server, which can be an SMTP server or an integrated service such as Amazon SES, Mailgun, or SendGrid.



In the admin panel, open:

**Sending → Sending Servers**



A new installation includes a **Local Sendmail** entry as a placeholder. Its sendmail path is not a working delivery configuration, so add a real sending server before you try to send a campaign.



Click **Add Server**.



![The Sending Servers page in the admin panel with the Add Server button](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/09-sending-servers-add-server.png)



### Choose the server type



For this guide, use SMTP or an API-based provider that is already configured and ready to accept mail. The example below uses Amazon SES.



**SMTP** works with any standard SMTP account.



![The Choose Server Type window listing the built-in server types](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/10-choose-server-type.png)



### Example: Amazon SES



Select **Amazon Web Service API**.



Under **Connection details**, enter:

- **AWS Access Key ID**

- **AWS Secret Key**

- **AWS region**



Click **Save Changes**.



The saved server opens on its **Connection** tab, which has two tests. **Test Connection** checks the key and its permissions. **Send Test Email** sends a real email through the server.



![The Connection tab of an Amazon SES sending server with the Test Connection and Send Test Email buttons](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/11-amazon-ses-connection.png)



Run **Test Connection** before you attach the server to a plan.



[How to Set Up Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) covers the AWS side: creating the access key, the permissions it needs and verifying your sending domain.



## 9. Set up a plan



A plan is the package a user subscribes to. It sets the price, how much the user can send and store, which features are switched on, and which sending server delivers their email.


A user cannot create lists or send campaigns until they are subscribed to a plan.



Open:



**Plans & Billing → Plans**



A new installation comes with five plans: **Free Forever**, **Starter**, **Growth**, **Pro** and **Enterprise**. You can edit them, or click **Create Plan** to add your own.


![The Plans page listing the default plans with their price, sending credits and status](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/12-plans-list.png)



This guide edits **Starter**. Click **Edit** on its row. The plan opens with its settings grouped in a menu on the left.



Click **Save Changes** after you edit a section.



### General



**General** holds the **Plan Name** and **Description**. Users see both on the plan card when they choose a plan.


![The General Settings of the Starter plan with the plan name and description](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/13-plan-general-settings.png)






### Pricing



Open **Pricing Options** to set what the plan costs:


- **Price** and **Currency**.

- **Billing Cycle**: how often the user is charged, for example **Monthly**.

- **Free Trial**: turn on **Enable free trial period**, then set **Trial Duration** and **Trial Period**.



In the example, Starter costs 19.00 USD a month and starts with a 14-day trial.



![The Pricing Options of the Starter plan with price, currency, billing cycle and free trial](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/14-plan-pricing-options.png)



For a plan with price 0, also turn on **No payment method required when free**. Users can then take the plan without entering payment details.



### Quotas



**Quotas** limits how much of each resource a user on this plan can have: **Max Contacts**, **Max Lists**, **Max Campaigns**, **Max Automations**, **Max Users** and more.



Each quota has a switch, a number and an **Unlimited** checkbox:

- The number is the limit. In the example, Starter allows 5,000 contacts, 10 lists, 50 campaigns and 5 automations.

- **Unlimited** removes the limit.

- Switching a quota off removes that resource from the plan, and its menu is hidden for users.

![The Quotas of the Starter plan with Max Contacts, Max Lists, Max Campaigns and Max Automations](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/15-plan-quotas.png)



The number of emails a user can send in each billing cycle is not a quota. It is set under **Credits**, as **Send Email**. The plan list shows that number as sending credits.



### Entitlements



**Entitlements** are on/off switches for features. A quota sets how many of something a user can have. An entitlement sets whether a feature is available at all.

Examples are **API Access**, **White Labeling**, **Bulk Send (>1,000 recipients)**, **List Import** and **List Export**.



Turn on what users of this plan should be able to use, and leave the rest off.



![The Entitlements of the Starter plan with a switch for each feature](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/16-plan-entitlements.png)



### Sending servers



**Sending Servers**, under **Delivery** in the left menu, decides which server delivers the email of the users on this plan. Check this section on every plan: a plan without a working sending server cannot send.



There are two options:



| Option | What it means |
|---|---|
| **System's Sending Servers** | Users send through the servers you added in the admin panel. They set up nothing themselves, and every user on the plan shares the same servers. |
| **Own Sending Servers** | Each user adds and manages their own sending servers in their account. You choose which server types they may use and how many. |



Use **System's Sending Servers** when you provide the sending as part of your service. Use **Own Sending Servers** when users bring their own Amazon SES, SMTP or other sending account.






This example uses **System's Sending Servers** with the server from step 8:



1. Select **System's Sending Servers**. If you changed the option, click **Save Changes**.

2. Under **System Sending Servers**, click **Add Server**.

3. In the **Add Sending Server** window, choose the Amazon SES server and click **Add Server**.



On a new installation the plan already lists **Local Sendmail** as **Primary**. Remove it with the **×** on its row. Email is split between all the servers on a plan, so part of it would otherwise go to the placeholder server.



![The Sending Servers section of a plan with System's Sending Servers selected and the list of servers](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/17-plan-sending-servers.png)



The screenshot shows a plan with four servers that each take 25% of the traffic. **Load Balance** changes that split, and the star on a row makes that server the primary one.



[Sending Servers for a Plan: Shared Pool or Bring-Your-Own](https://acellesend.com/kb/articles/sending-server-for-plan) compares the two options in detail.



## 10. Make the plan visible



Go back to **Plans & Billing → Plans**. Make sure the plan you configured is visible by switching it on.



![The plan rows with the visibility switch at the left of each row](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/18-plan-visible-toggle.png)



Switch off any plans you do not want to offer.



A plan that uses the system's servers cannot be switched on while it has no sending server.



## 11. Subscribe to the plan as a user



The plan is ready. Subscribe a user to it and check the result from the user's side.



There are two ways to do this:



1. Switch to the user view with your own account.

2. Register a new user.



### Switch to the user view



Click your name at the bottom left of the admin panel and choose **Back to app**.



You are now in the user view, with the user account that was created during the installation. That is the account from **Create customer account** in step 3.



This account has no plan yet, so AcelleMail shows **Find the right plan for your business** with every visible plan.

![The plan selection page in the user view with the Free Forever, Starter, Growth and Pro plans](https://acellesend.com/plugins/acelle/landing/images/help/kb/install-acellemail-and-get-started/19-customer-choose-a-plan.png)



Find the plan you set up in steps 9 and 10, and click the button at the bottom of its card.



In the example, **Starter** has a 14-day trial, so its button reads **Get started free**. A free plan shows the same button. A paid plan without a trial shows **Subscribe now**.



AcelleMail then takes you through checkout: billing details and, when the plan requires it, a payment.

Once the subscription is active, the account can create lists and send campaigns through the plan's sending server.



### Register a new user



To see what a new customer sees, log out and click **Create an account** on the login page.



By default, a new user has to confirm their email address first: AcelleMail sends an activation link through the system email you set up in step 3. If that email does not arrive, check the system email in the admin panel under **Settings → System Email**.



After the user confirms and logs in, AcelleMail shows the same **Find the right plan for your business** page. The user subscribes to the plan the same way.



### Use AcelleMail only for yourself



If you only send your own email and do not sell access, subscribe your own account to a free plan.



Set the plan's price to 0, turn on **No payment method required when free**, raise its credits and quotas to what you need, and switch off the other plans.

## Common problems



| What you see | What it usually means |
|---|---|
| `Some requirements are not met. Please fix them and try again.` | A PHP extension or function is missing, or a folder is not writable. Each failed row names it. Fix it on the server and click **Check Again**. |
| A MySQL error at the top of the **Database** step, such as `Access denied for user` | The host, username, password or database name is wrong, or the database does not exist yet. The wizard does not create it. |
| `The table prefix must be 12 characters or fewer.` | Shorten the **Table Prefix** or leave it empty. |
| `The [/usr/bin/php] path does not exist or is not executable` | The selected PHP path is wrong for this server. Select another one, or use **Other path**. |
| A line starting with `ERROR:` after **Check & Save** | The PHP at that path is too old or lacks an extension. Select the path of the PHP version your website runs on. |
| `Please enable PHP exec() function for automatic cron job detection.` | `exec()` is disabled in PHP, so the wizard cannot detect or test PHP paths. Enable it, or enter the path yourself under **Other path**. |
| `Installation is already running — please wait a moment and do not submit twice.` | **Initialize Database** was submitted twice. Wait for the first run to finish. |
| A campaign stays in sending and nothing goes out | The cron job from step 5 is not running. See [My Campaign Is Stuck in "Sending"](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status). |
| `Cannot make this plan visible — no sending server is configured. Please assign a sending server first.` | The plan uses the system's servers and has none. Add one in the plan's **Sending Servers** section. |
| An entitlement shows `Needs "…" set to at least 1 (or Unlimited) before it can be turned on.` | That feature depends on a quota. Set the quota under **Quotas** first. |
| `No plans available` on the user's plan page | No plan is visible. Switch one on in **Plans & Billing → Plans**. |
| A new user never receives the activation email | The system email from step 3 is not working. Check it in the admin panel under **Settings → System Email**, or create the user yourself under **Customers**. |



## What to do next



AcelleMail is the application that manages your lists and campaigns; the actual delivery is handled by a sending server. You can use SMTP or one of the supported providers such as Amazon SES, Mailgun, or SendGrid.

- Connect a payment gateway under **Plans & Billing → Payment Gateways** before you offer paid plans: [Connecting Stripe for Subscription Billing](https://acellesend.com/kb/articles/connecting-stripe-for-subscription-billing).

- Go through every plan setting: [How to create a subscription plan for your customers](https://acellesend.com/kb/articles/creating-subscription-plans-for-your-customers).

- Send the first campaign from the user area: [How to create and send your first email campaign](https://acellesend.com/kb/articles/create-your-first-email-campaign).

- Review the security settings of the new installation: [Securing Your AcelleMail Install: A Post-Install Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist).



## Summary



To install AcelleMail and get it ready to send:



1. Unzip the package into the document root and open the domain.

2. Pass the **System Check**.

3. Enter the site, administrator, and system email settings.

4. Enter the MySQL details and click **Initialize Database**.

5. Select the PHP path and add the cron job line to the server.

6. Log in and open the **Admin panel**.

7. Add a sending server and run **Test Connection**.

8. Edit a plan: price, quotas, entitlements and its sending server.

9. Make sure the plan is visible.

10. Switch to the user view with **Back to app**, or register a new user, and subscribe to the plan.

---

# GDPR for Email Marketing Explained: What the Law Asks of Senders

> What the GDPR and the ePrivacy rules ask of anyone emailing people in the EU: lawful basis, privacy notice, rights, tracking, processors and deadlines.

- Canonical URL: https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained
- Author: Daniel Reyes, Technical Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, GDPR, Data Protection, Privacy
- Published: 2026-10-05 · Last updated: 2026-10-21
- Reading time: 19 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

The General Data Protection Regulation (GDPR) is the European Union's main data protection law. It does
not have a chapter on newsletters, but it still covers most of what happens to a mailing list. An email
address is personal data, and collecting, storing or using it is processing.

This guide follows the points at which those rules tend to matter for a sender: before an address goes
onto the list, while you keep it, when the person asks what you hold, and when something goes wrong. It
covers:

1. Whether the GDPR applies to you
2. Who is who: controller, processor and data subject
3. How ePrivacy and the GDPR work together
4. The seven principles
5. A lawful basis for every use
6. Telling people what you do
7. People's rights and the one-month deadline
8. Tracking opens and clicks
9. Processors, transfers and security
10. Records, breaches and fines

Where the guide connects a rule to something you can do in AcelleMail, the relevant screen is shown. It
ends with the mistakes senders make most often and a one-screen summary.

> **Not legal advice.** This guide explains the EU rules in general terms. National laws add detail,
> above all for email marketing, and the official texts are linked throughout. For a decision that
> matters, ask a lawyer or your data protection authority.

## 1. Does the GDPR apply to you?

Citizenship is not the deciding factor. What matters is where your organisation is based and, in some
cases, whether you are offering services to or monitoring people in the EU ([GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj), Article 3):

- **You are established in the EU.** The GDPR covers the personal data you handle, wherever the people
  on your list live. The same framework also applies in Iceland, Liechtenstein and Norway.
- **You are outside the EU but offer goods or services to people in it, or track their behaviour there.**
  The GDPR can still apply to their data, "irrespective of whether a payment of the data subject is
  required". A free newsletter can therefore fall within its scope. The question is whether you are
  clearly targeting people in the EU. Selling in euros, offering goods in a language of an EU country
  other than your own, or referring to EU customers can all be signs of that. A website that simply
  happens to be accessible from Europe is not enough by itself.

A sender in that second group may also have to appoint a representative in the EU under Article 27,
unless its processing is occasional and low-risk.

The United Kingdom has its own, closely related law, the UK GDPR. This guide uses the EU texts and, where
useful, the UK regulator (the ICO) to explain a rule in plainer language.

**Personal data** means "any information relating to an identified or identifiable natural person"
(Article 4(1)). In a mailing list, that can include the email address, the person's name, the IP address
recorded when they signed up, and their history of opens and clicks. A named work address such as
`anna.schmidt@company.com` is personal data too. A shared address such as `info@company.com` usually is
not.

## 2. Who is who: controller, processor and data subject

The GDPR uses these roles to work out who is responsible for what:

![The data subject is the person on the list; the controller is the sender who decides why and how the data is used; processors are services such as hosting, email delivery and verification that handle the data on the sender's behalf.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/01-who-is-who.png)

- The **controller** "determines the purposes and means of the processing" (Article 4(7)). If you decide
  who belongs on the list and what you send them, you are the controller.
- A **processor** "processes personal data on behalf of the controller" (Article 4(8)). Your hosting
  provider, sending service and email verification service are typical examples.
- The **data subject** is simply the person the data relates to.

Two situations are worth separating. With a self-hosted AcelleMail installation, the software runs on
your own server and its maker does not receive your contacts, so the software vendor is not one of your
processors. If you run AcelleMail as a service for other businesses, the roles are different: each client
is the controller for its own list, while you are that client's processor.

## 3. ePrivacy and the GDPR: two parts of the same job

The two sets of rules come into play at different points. The [ePrivacy
Directive](https://eur-lex.europa.eu/eli/dir/2002/58/oj), implemented through each EU country's national
law, deals with whether marketing email may be sent in the first place. The GDPR then governs how the
personal data behind that activity is handled. Both matter.

![The ePrivacy rules deal with whether marketing email may be sent; the GDPR deals with how the personal data is handled, including lawful basis, transparency, rights, retention, security and accountability.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/02-two-laws.png)

Under Article 13(1) of the ePrivacy Directive, marketing by email "may be allowed only in respect of
subscribers or users who have given their prior consent". There is a common exception, usually called the
soft opt-in. A business that obtained an address from its own customer "in the context of the sale of a
product or a service" may use it for direct marketing of its own similar products or services, provided
the customer had a chance to refuse when the address was collected and can refuse in every subsequent
message (Article 13(2)).

Those consent rules mainly protect individuals. The treatment of company addresses is left to each
country under Article 13(5), so business-to-business email rules are not identical across Europe. The
national law of the country you are mailing to therefore matters.

What counts as valid consent, and how to collect and prove it, is covered in [Email Consent
Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained).

## 4. The seven principles

Article 5 sets out seven principles. The rest of the GDPR largely works out what those principles mean in
practice:

![The seven GDPR principles for a mailing list: lawfulness, fairness and transparency; purpose limitation; data minimisation; accuracy; storage limitation; integrity and confidentiality; accountability.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/03-seven-principles.png)

Three are especially relevant to day-to-day email work:

- **Data minimisation.** Every field on a signup form becomes data you need to justify, protect and,
  potentially, provide to the person it belongs to. If you do not use a phone number or birthday, there is
  little reason to collect it.
- **Storage limitation.** Decide how long you keep a contact who has gone silent, and what happens to the
  data of someone who unsubscribed. Keeping everything "forever" is not a retention policy. The routine
  for removing silent contacts is covered in [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained), section 6.
- **Accountability.** The controller must be responsible for compliance and able to demonstrate it. In
  practice, that means more than following the rules: you need evidence of consent, a record of your
  processing, and contracts with the providers you rely on (sections 9 and 10).

## 5. A lawful basis for every use

Article 6(1) says that processing is lawful only when at least one of the six legal grounds applies. For a
sender, three usually do most of the work:

| Basis | Fits | What it asks of you |
|---|---|---|
| **Consent**, Article 6(1)(a) | Newsletters and promotions to people who signed up | Consent that meets the GDPR standard, proof that you have it, and an easy way to withdraw it |
| **Legitimate interests**, Article 6(1)(f) | Marketing to your own customers under the soft opt-in, and to company addresses where national law allows it | A documented balancing check showing that your interest is not outweighed by theirs, plus an immediate stop when they object |
| **Contract**, Article 6(1)(b) | Receipts, delivery notices, password resets | Only the data and messages needed for the contract, without mixing marketing into them |

The GDPR itself says that "the processing of personal data for direct marketing purposes may be regarded as
carried out for a legitimate interest" (Recital 47). That does not override the ePrivacy rules. Where those
rules require consent, legitimate interests cannot be used instead. The ICO makes the same point for the
UK, where the ePrivacy rules are called PECR: "If PECR require consent, you must use consent as your lawful
basis as well"
([ICO](https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/lawful-basis/legitimate-interests/when-can-we-rely-on-legitimate-interests/)).

The lawful basis should be chosen before you collect the data and identified in your privacy notice. It is
not something to swap later if the first basis turns out to be inconvenient. The European Data Protection
Board (EDPB) says a controller "cannot swap from consent to other lawful bases" when consent itself is
flawed ([Guidelines 05/2020 on
consent](https://www.edpb.europa.eu/our-work-tools/our-documents/guidelines/guidelines-052020-consent-under-regulation-2016679_en),
paragraph 123).

The difference between marketing and service email, and why it is useful to keep them separate, is covered
in [Transactional vs Marketing Email](https://acellesend.com/kb/articles/transactional-vs-marketing-email).

## 6. Telling people what you do

When you collect an address, Article 13 requires you to give the person a set of basic information. For a
mailing list, the privacy notice should cover:

1. Who you are and how to contact you.
2. What you will use the address for, and the lawful basis for that use.
3. Who else receives the data, such as your sending service, hosting provider or other tools you copy it into.
4. Whether the data leaves the EU, and what safeguard is used when it does (section 9).
5. How long you keep the data, or how that period is decided.
6. Their rights, including access, correction and deletion, the right to object, and the right to withdraw consent at any time.
7. Their right to complain to a data protection authority.

The law calls for this information to be provided "in a concise, transparent, intelligible and easily
accessible form, using clear and plain language" (Article 12(1)). In practice, that can be a short line
beside the signup form with a link to the full privacy notice.

The right to object to direct marketing deserves special attention. Article 21(4) says it must be
"explicitly brought to the attention of the data subject", at the latest in the first message. A clear
unsubscribe link in every email is a straightforward way to do that.

## 7. People's rights and the one-month deadline

Anyone on your list can ask what data you hold, ask for it to be corrected or deleted, or tell you to
stop using it for marketing. You must respond "without undue delay and in any event within one month of
receipt of the request" (Article 12(3)). There is normally no fee unless the request is manifestly
unfounded or excessive (Article 12(5)).

![A request arrives: record the date, verify the requester, find the relevant data, act on the request and respond within one month. The diagram covers access, rectification, objection, erasure and portability.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/04-a-request-arrives.png)

Two rights are particularly easy for senders to mishandle.

**The right to object is absolute.** When someone objects to processing for direct marketing, the personal
data "shall no longer be processed for such purposes" (Article 21(3)). No reason is needed, and there is
no exception that lets you continue marketing. The ICO describes this as "an absolute right" with no
exemptions or grounds for refusal
([ICO](https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/individual-rights/individual-rights/right-to-object/)).
Every unsubscribe click therefore needs to stop marketing immediately, whether it is treated as an
objection or as a withdrawal of consent.

**Erasure and suppression are not the same thing.** If you delete every trace of someone who has objected,
their address could simply come back in the next import. Regulators expect senders to keep the minimum
information needed to honour that choice. The ICO recommends putting the address on a suppression or "do
not contact" list instead of deleting that last piece of information
([ICO](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/direct-marketing-guidance/respect-peoples-preferences/)).
In other words, remove the person's other data, but keep the bare address on the suppression list so the
address cannot be re-added to marketing.

### In AcelleMail

**See and correct what you hold.** Open **Lists**, open the list, go to **Subscribers → View all**, and
click the contact's email address. The contact page shows how they joined (1) and the fields you hold (2).
Change a field and click **Save changes**.

![A contact's page showing when and how the contact joined, plus the fields stored for that person.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/05-contact-record.png)

**Erase a contact and keep the address suppressed.** Do it in this order:

1. In the list's **Subscribers → View all**, search for the address and tick the contact.
2. Open **Actions** and click **Blacklist**. The address is added to the blacklist, the contact is marked
   **Blacklisted** on all your lists, and signup forms and imports can no longer bring it back as an active
   contact. The change happens in the background, so reload the page until the status shows **Blacklisted**
   before continuing.
3. Tick the contact again, open **Actions** and click **Delete**. The contact and its fields are removed,
   including the sending history, opens and clicks. The address itself remains on the blacklist under
   **Sending → Blacklist**.

![The subscriber list with one address selected, showing the Blacklist action before Delete.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/06-erase-and-suppress.png)

Repeat the search and **Delete** step for every other list that contains the address. When the account uses
the Ads integration, open the contact page first and click **Purge from ad audiences** so the address is
removed from every ad audience it was sent to.

**Hand over a copy.** Open the list's **Subscribers → Export**. To export a single person, first create a
segment with one condition, Email equal to their address ([how to build a
segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment)). Then choose **Export a specific segment** (1),
click **Start export** (2), and download the CSV file.

![The Export subscribers card with Export a specific segment and Start export.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/07-export-contacts.png)

The export contains the contact's field values, status and dates. It does not contain tags, the signup
source, the IP address, or opens and clicks. When someone asks for everything you hold, those additional
items need to come from the contact page, where the **Engagement** card shows the campaigns and automations
they have been part of.

## 8. Tracking opens and clicks

An event record showing that a named person opened an email at a particular time or clicked a particular
link is personal data. The same GDPR requirements therefore apply: there needs to be a lawful basis, the
privacy notice should explain the processing, and the data should not be kept longer than necessary.

Email tracking also raises an ePrivacy issue. Article 5(3) restricts storing information on a person's
device, or reading information from it, without consent, and the EDPB has said that this applies to email
tracking. Its [Guidelines
2/2023](https://www.edpb.europa.eu/our-work-tools/our-documents/guidelines/guidelines-22023-technical-scope-art-53-eprivacy-directive_en)
describe the tracking pixel a sender uses "to detect when the receiver reads the email" and conclude that
Article 5(3) ePD applies to both tracking pixels and tracking links. Enforcement varies between countries,
so the relevant national regulator is worth checking for the emails you send.

A practical approach is to:

- Explain in the signup form or privacy notice that opens and clicks are measured, and why.
- Ask for consent to that measurement alongside the subscription where the regulator expects it.
- Leave tracking off when you do not need it.

In AcelleMail, tracking is controlled per campaign. In the campaign's Setup step, open **Advanced Settings**
and untick **Track opens**, **Track clicks**, or both:

![AcelleMail campaign Advanced Settings with the Track opens and Track clicks options.](https://acellesend.com/plugins/acelle/landing/images/help/kb/gdpr-for-email-marketing-explained/08-tracking-switches.png)

A campaign sent without tracking will not show opens or clicks in its report. The limits of those metrics
are covered in [Email Marketing Metrics Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained).

## 9. Processors, transfers and security

**A contract with every processor.** Article 28(3) requires processing by a processor to be governed by a
contract. The processor must follow your instructions, keep the data secure, and delete or return it at
the end. Most services publish a data processing agreement (DPA) that you can accept online. Keep a list
of your processors, accept the relevant agreements, and retain a copy. In a typical AcelleMail setup, that
list includes the hosting provider, sending service, email verification service, and any other tool that
receives copies of your contacts.

**Transfers outside the EU.** Personal data can leave the EU and wider European Economic Area only through
one of the mechanisms in Chapter V. Two are especially common for senders:

- **An adequacy decision.** The European Commission has decided that the destination "ensures an adequate
  level of protection" (Article 45). For the United States, that currently covers organisations certified
  under the EU-US Data Privacy Framework, adopted in July 2023. The framework has been challenged in court,
  so check that it remains in force and that your provider appears on the [Data Privacy Framework
  list](https://www.dataprivacyframework.gov/list).
- **Standard contractual clauses.** These are contractual terms issued by the European Commission under
  Article 46 and are often included in a provider's data processing agreement.

The GDPR does not require all data to stay inside the EU. Keeping transfers to a minimum can still make the
setup easier to manage. With an AcelleMail server in an EU data centre and a sending service that offers an
EU region, much of your subscribers' data may stay there.

**Security.** Article 32 requires "appropriate technical and organisational measures to ensure a level of
security appropriate to the risk". For a mailing list, the practical measures are fairly straightforward:

- Serve AcelleMail over HTTPS and keep both the application and server up to date.
- Give accounts only to people who need access, and remove access when they leave.
- Back up the database and protect the backups just as carefully as the live data.
- Delete exported files when they are no longer needed. An old CSV sitting in a downloads folder is an
  easy way for a list to leak.

The server-side steps are in [Securing Your AcelleMail
Install](https://acellesend.com/kb/articles/post-install-hardening-checklist).

## 10. Records, breaches and fines

**Keep a record of what you do.** Article 30 requires controllers to "maintain a record of processing
activities". Organisations with fewer than 250 employees are exempt only if, among other conditions, the
processing is occasional. A regular newsletter normally does not meet that condition. A simple one-page
record can cover the purpose of the processing, the people and fields involved, recipients, transfers,
retention, and security measures.

**Report a breach within 72 hours.** When subscriber data is lost, stolen or disclosed to the wrong people,
the controller must notify the relevant data protection authority "without undue delay and, where feasible,
not later than 72 hours after having become aware of it", unless the breach is unlikely to result in a risk
to people's rights and freedoms (Article 33). Where the risk is high, the affected people must be told as
well (Article 34). A laptop containing an exported list can be enough to create a breach. So can an ordinary
email sent with the whole mailing list visible in the To line.

**The fines.** Breaches involving the core principles, consent rules, individual rights or certain transfer
requirements can lead to fines of "up to 20 000 000 EUR, or in the case of an undertaking, up to 4 % of the
total worldwide annual turnover of the preceding financial year, whichever is higher" (Article 83(5)).
Breaches involving processor contracts, records, security and breach reporting fall within a lower band of
10 million euros or 2% (Article 83(4)). Regulators can also order an organisation to stop processing. A
person who receives your email can complain to their data protection authority free of charge (Articles 77
and 57(3)). The relevant authority can be found in the [EDPB's list of members](https://www.edpb.europa.eu/about-edpb/about-edpb/members_en).

## 11. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| "We rely on legitimate interests" for email to people who never signed up | The ePrivacy rules generally require consent first, and legitimate interests cannot replace that requirement | Send only with consent, or use the soft opt-in for your own customers where its conditions are met |
| Contacts deleted the moment they unsubscribe | The address can come back in the next import | Keep the address suppressed: use the Unsubscribed status or the blacklist |
| A signup form that asks for a phone number and a birthday "just in case" | You should not collect fields you do not need | Ask for the email address and add other fields only when there is a real use for them |
| No agreement with the sending service | Article 28 requires a contract with each processor | Accept the provider's data processing agreement and keep a copy |
| A privacy notice that names no recipients and no retention period | Article 13 requires that information | Name the processors, explain relevant transfers, and state how long contacts are kept |
| A deletion request ignored because it came as a reply to a newsletter | No special form is required, and the one-month response period has already started | Treat the message as a rights request and record the date it arrived |
| "It is B2B, so the GDPR does not apply" | A named work address is personal data | Apply the GDPR and check the national email rule for company addresses |
| Old exports kept in inboxes and downloads folders | Every extra copy creates another place the list can leak | Delete exports when they are no longer needed |

## Summary

1. The GDPR applies if you are established in the EU, or if you are targeting people there in a way that brings you within Article 3.
2. In a typical mailing setup, you are the controller and your hosting, sending and verification services are processors.
3. The ePrivacy rules govern whether marketing email may be sent; the GDPR governs how the personal data is handled. Both can apply to the same activity.
4. The seven GDPR principles shape how the list is collected, used, retained and protected.
5. Choose a lawful basis before collecting the data and explain it in the privacy notice.
6. Tell people who you are, what you do with their data, who receives it and how long you keep it.
7. Respond to rights requests within one month. An objection to direct marketing takes effect immediately.
8. When you erase someone who objected, keep the bare address suppressed so it cannot return to the marketing list. In AcelleMail: **Blacklist**, then **Delete**.
9. Opens and clicks are personal data, and email tracking also raises ePrivacy considerations.
10. Have the right contracts with processors, understand where the data travels, and protect every copy of the list.
11. Keep a record of the processing and deal with reportable breaches within 72 hours.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email List Quality Explained: Sources, Spam Traps and List Hygiene

> Why bought lists fail, how good addresses go bad, the three kinds of spam traps, and the routine that keeps a list clean in AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/email-list-quality-explained
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, List Cleaning, Email Verification, Spam Traps
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 13 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

A list is only as good as the people on it who asked for your mail. Every other address costs you twice. It
brings nothing back, and it tells mailbox providers such as Gmail and Outlook.com that nobody is looking
after your list, which can affect where they put your mail for everyone else.

This guide covers:

1. Where good lists come from
2. Why you should never buy, rent or scrape a list
3. How a good address goes bad
4. Spam traps
5. Addresses to handle with care
6. The routine that keeps a list clean
7. Keeping a list clean in AcelleMail

It ends with the most common mistakes and a one-screen summary.

## 1. Where good lists come from

Good lists are built one signup at a time, and every good source passes the same basic test: **the person
typed their own address because they wanted to hear from you.**

![Where a list comes from. A signup form, an empty opt-in box at checkout, a download or event whose form mentions email, and a confirmation click build a good list. A bought or rented list, scraped addresses, a partner's list and an old list re-imported without checks build a bad one](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-list-quality-explained/01-good-and-bad-sources.png)

Some sources look fine on paper but carry more risk. M3AAWG, the industry's anti-abuse working group, names
incentivised signups, social-media signups, refer-a-friend forms and sweepstakes as higher-risk: they
"prioritize any email address over the right email address". If you collect addresses this way, confirm
each one with double opt-in (a confirmation email the person must click) before sending anything else.

Whatever the source, keep a record of how each person joined. In many countries the law asks you to
prove it. [Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained) covers what counts
as consent and where AcelleMail records it.

## 2. Why you should never buy, rent or scrape a list

A list you buy or rent, or scrape together from websites, fails in four ways at once.

**Nobody on it asked for your mail**, so people press "Report spam". Google's sender guidelines are
blunt: "Don't purchase email addresses from other companies." People who did not sign up "might mark
your messages as spam, and future messages to these recipients will be marked as spam." Yahoo's
guidelines say the same: "Don't purchase mailing lists".

**It carries spam traps.** Address harvesters, programs that copy addresses from web pages, pick up the
trap addresses planted there for exactly that purpose (section 4). M3AAWG: "Pristine trap hits may be a
strong indicator of the presence of purchased lists."

**The consent is not yours.** The UK regulator, the ICO, says consent on a bought list counts only if it
"named your organisation (not just 'trusted partners' or similar)". A published address is not an
invitation either: "Just because someone's contact details are publicly available, it doesn't mean
they've consented to your direct marketing." The rules in the US, the EU and Canada are compared in
[Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained).

**"Verified" does not mean "wanted".** A seller may promise the list has been checked. Verification only
tells you an address exists; Spamhaus, which runs some of the most widely used blocklists, points out
that it "does nothing to verify the permission of the recipient".

The same goes for a list from a trade show or a partner: those people agreed to hear from someone else.
To reach them, ask the organiser or partner to point their own audience to your signup form, and add
only the people who sign up.

## 3. How a good address goes bad

Even a well-built list gets worse over time. People change jobs, switch providers and stop
checking old inboxes; providers close accounts nobody uses.

![How a good address goes bad, in five stages: it signs up, the person moves on, the address goes silent, it hard-bounces, and it may be reused as a spam trap. You can stop it at stage 3 with a re-engagement email, or at stage 4 at the latest by removing hard bounces](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-list-quality-explained/02-how-an-address-decays.png)

The last stage is the dangerous one. Spamhaus explains how providers turn abandoned addresses into
traps: mail to them is first "rejected with a hard bounce for a period of time, often 12 months or
more", then "the addresses are silently turned back on in the form of spamtraps". M3AAWG suggests at
least 12 months of inactivity before an address is reused this way.

Two things follow:

- **Remove hard bounces straight away, and recycled traps never see you.** An address bounces for months
  before it becomes a trap.
- **A list you have not mailed for a long time is the riskiest list you have.** Its addresses may have
  bounced during that time and come back as traps, and you never saw the bounces. M3AAWG calls for special
  care whenever people get mail "who have not been sent to for an extended time". Spamhaus's remedy for
  a neglected list is "list validation or a permission pass campaign to reconfirm opt-in status": verify
  the list first (section 7), then ask the people on it once whether they still want your mail, and keep
  only those who say yes.

## 4. Spam traps

A spam trap is an email address that no person reads, run by a mailbox provider or a blocklist operator to
catch senders with poor list habits. Nobody ever asked for mail at a trap address, so every message that
reaches one tells its operator something about how you collect and maintain addresses. The operators never
reveal which addresses are traps.

![Three kinds of spam traps: typo traps (a misspelled provider domain), recycled traps (abandoned addresses that are turned back on, the most common kind) and pristine traps (addresses that never belonged to anyone). For each one, the image shows what hitting it says about a list and what prevents it](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-list-quality-explained/03-spam-trap-types.png)

What happens when you hit traps depends on who runs them and how often you hit them. Trap data is "often
used and redistributed via DNS blacklists (DNSBLs) and other reputation systems" (M3AAWG), so the result
can be anything from more of your mail landing in spam to your sending IP address or domain being
listed. Once listed, your mail is refused by every provider that checks that list.

Don't go looking for the traps themselves. Spamhaus asks senders to "view spamtraps as proof of a data
collection or hygiene issue", because "attempting to locate and remove traps only treats the symptom". A
verification service cannot find them for you either: a recycled trap accepts mail like any working
address, and Spamhaus notes that verification "won't catch many typo traps". The fix is always at the
source: consent, double opt-in and the routine in section 6.

## 5. Addresses to handle with care

Some addresses are real but still do not belong on a marketing list, or belong there only when someone
clearly asked:

| Kind | Example | Why it needs care | What to do |
|---|---|---|---|
| Role address | `info@`, `sales@`, `support@` | Reaches a team or a help desk, so no one person asked for your mail, and whoever reads it next may press "Report spam" | Keep only if someone signed up with it and confirmed |
| Technical role address | `postmaster@`, `abuse@`, `hostmaster@` | Mailboxes every domain is expected to have ([RFC 2142](https://www.rfc-editor.org/rfc/rfc2142)), often published in domain registration records, so harvesters collect them. Spamhaus: they "should almost NEVER be on a marketing mailing list" | Remove |
| Disposable address | An address from a temporary-inbox service | Made to be thrown away after one signup | Remove. Many verification services mark these Risky |
| Typo | `anna@gmial.com` | Never reaches the person, and some typo domains are traps | Double opt-in stops them: a typo never confirms |
| Catch-all domain | Any address at a company whose mail server accepts every name | The server accepts every address, so a verification service cannot tell a real mailbox from an invented one | Keep only if the person confirmed or clicked recently |

## 6. The routine that keeps a list clean

![The list hygiene routine in six habits: confirm the address at signup, verify old or imported lists before a first send, remove hard bounces after every send, exclude contacts who unsubscribe or complain, sunset inactive contacts every few months, and send what you promised](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-list-quality-explained/04-hygiene-routine.png)

Mailbox providers ask for these habits in writing. Google's sender guidelines: "Confirm each recipient's
email address before subscribing them", "Periodically send messages to confirm that recipients want to
stay subscribed" and "Consider unsubscribing recipients who don't open or read your messages." Yahoo's:
"Monitor hard and soft bounces as well as inactive recipients."

**How long is "silent"?** There is no official number; it depends on how often you send. Six months
without a click is a long silence for a weekly newsletter, but only two emails for a quarterly one.
Judge by clicks as well as opens: Apple's Mail Privacy Protection "prevents senders from seeing if
you've opened the email message they sent you", so opens say little about people who read in Apple Mail.
Clicks still count: the protection "does not extend to links".

When you remove silent contacts, unsubscribe them rather than blacklist them: someone who went quiet may
want to sign up again one day.

## 7. Keeping a list clean in AcelleMail

AcelleMail handles part of the routine for you. Campaigns and automations go only to contacts whose status
is **Subscribed**. Once a list has been verified, they also skip every contact whose result is not
**Deliverable**; contacts that have not been checked yet still get mail.

### Verify a list

Verification asks a service whether each address has a working mailbox, without sending anything. Run it
before the first send to a list you did not build yourself, and on any list you have not mailed for a
while.

1. Open **Lists**, open the list, and go to its **Email verification** tab (1).
2. If your account uses its own verification service, choose it under **Select verification server**
   (2). You connect that service under **Sending → Email verification**. If your plan provides the
   service, this choice is not shown.
3. Click **Start verification** (3). Checks use your plan's verification credits; the **Credits
   available** card shows how many are left.

![A list's Email verification tab: 1 the tab, 2 Select verification server, 3 Start verification, 4 the Undeliverable, Risky and Unknown results, which campaigns and automations skip](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-list-quality-explained/05-list-email-verification.png)

**Start verification** checks only the addresses that have not been checked yet, such as new signups and
new imports. To check the whole list again, for example before mailing it after a long break, click
**Reset verification data** first. That deletes the earlier results, so every address uses a credit
again.

When the check finishes, **Verification results** (4) sorts the list into four groups. The verification
service decides what goes where, so the same address can be Risky with one service and Deliverable with
another:

| Result | What it means | What AcelleMail does |
|---|---|---|
| **Deliverable** | The service found a working mailbox | Sends to it |
| **Undeliverable** | No working mailbox: the address or its domain does not exist, or the account is closed | Skips it. Blacklist it to keep it out for good |
| **Risky** | The address may accept mail, but the service flags a problem: a disposable or role address, a catch-all domain, a suspected trap | Skips it |
| **Unknown** | The service got no clear answer, for example because the receiving server did not respond | Skips it |

A Deliverable result proves that a mailbox works, not that its owner asked for your mail. Verification
cleans a list; it cannot turn a bought list into a good one.

### Blacklist the undeliverable

Campaigns skip Undeliverable contacts only while they stay on the list with that result. Delete one, and
the next import of an old file brings it back as a new, unchecked contact, and AcelleMail mails
unchecked contacts. Blacklist them instead:

1. Open the list's **Subscribers → View all**.
2. Set the filters to **Subscribed** and **Undeliverable** (1).
3. Choose **Select → All subscribers** (2).
4. Open **Actions**, click **Blacklist** (3) and confirm.

![The list's subscribers filtered to Subscribed and Undeliverable: 1 the two filters, 2 Select, used to pick all subscribers, 3 Blacklist in the Actions menu](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-list-quality-explained/06-remove-undeliverable.png)

The message that follows says how many contacts are being moved. If that is fewer than you selected, run
the action again: with the **Subscribed** filter on, only the contacts still left are shown.

A blacklisted address is marked **Blacklisted** on all your lists, marked again whenever it arrives in
an import, and refused by your signup forms. Your blacklist is under **Sending → Blacklist**.

### Bounces, complaints and unsubscribes

These are handled as they arrive, as long as bounces and complaints can reach AcelleMail. Section 6 of
[Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained) shows how that is set up.

- **Hard bounces** mark the contact **Blacklisted** on all your lists, automatically.
- **Soft bounces** are left alone, because the next attempt may work. If the same address soft-bounces
  send after send, blacklist it yourself: Google advises senders to "automatically unsubscribe
  recipients who have multiple bounced messages". Each campaign lists its bounces under **Sending logs →
  Bounce log**; [Hard Bounce vs Soft Bounce](https://acellesend.com/kb/articles/understanding-email-bounce-types) explains the
  codes.
- **Complaints** reported by a sending service such as Amazon SES mark the contact **Spam reported**,
  and campaigns skip it. Complaints read by a feedback loop handler (a mailbox your administrator sets
  up to receive complaint reports) are only logged, under **Sending logs → Feedback log**. Find each
  address in the list's subscribers and blacklist it yourself.
- **Unsubscribes** take effect at once: the contact is marked **Unsubscribed** and gets no more
  campaigns.

## 8. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| A bought, rented or scraped list | Nobody asked for your mail, and it carries pristine traps | Build your own list; ask partners to promote your signup form |
| A long-neglected list mailed in one go | Some addresses may have become recycled traps meanwhile | Verify it, then send a permission pass and keep those who confirm |
| Undeliverable contacts deleted | An import of an old file brings them back unchecked, and AcelleMail mails unchecked contacts | Blacklist them instead |
| "Deliverable" treated as permission | Verification proves a mailbox works, not that its owner asked | Consent and double opt-in; verification only cleans |
| Hunting for spam traps | Owners never reveal them, and removing one treats the symptom | Fix how addresses are collected and kept |
| Role addresses imported from a directory | Nobody in particular asked, and the next reader may report you | Keep only role addresses someone signed up with and confirmed |
| Soft bounces ignored for months | An address that soft-bounces every time is usually abandoned, like a full mailbox nobody empties | Blacklist addresses that soft-bounce send after send |

## Summary

1. A good list is built one signup at a time: the person typed their own address to hear from you.
2. Never buy, rent or scrape a list. Nobody on it asked, it carries spam traps, and its consent is not
   yours.
3. Lists decay on their own. Remove hard bounces at once, and never mail a long-neglected list without
   cleaning it first.
4. Spam traps show how you collect and keep addresses. Don't hunt them; fix the source.
5. Handle role, disposable, typo and catch-all addresses with care.
6. Confirm at signup, verify old and imported lists, remove bounces, keep unsubscribes and complaints
   out, and sunset silent contacts.
7. In AcelleMail, verify from the list's **Email verification** tab. Campaigns and automations then skip
   Undeliverable, Risky and Unknown contacts. Blacklist the undeliverable ones instead of deleting them.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# How Email Marketing Works, From Signup to Sale

> The email marketing cycle in one read: permission, list, message, sending, delivery and measurement, and why each step depends on the one before.

- Canonical URL: https://acellesend.com/kb/articles/how-email-marketing-works
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Automation, Segmentation
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 13 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Email marketing works as a cycle. People give you permission to email them, you send them something
worth reading, their mailbox provider decides where it goes, and the results help you decide what to send
next. Each step relies on the one before it, so a problem early in the cycle often shows up later,
sometimes as something that seems completely unrelated.

This guide takes you through the cycle once, from signup to sale. It is the starting point for the other
guides in [Domain Knowledge](https://acellesend.com/kb/category/domain-knowledge), and each step links to the guide that
explains it in more detail.

![The email marketing cycle in six steps: permission, the list, the message, sending, delivery and measurement. What you learn in the last step shapes the next round, and the cycle starts again.](https://acellesend.com/plugins/acelle/landing/images/help/kb/how-email-marketing-works/01-the-cycle.png)

This guide covers:

1. Permission: someone asks to hear from you
2. The list: the people you can write to
3. The message: campaigns and automations
4. Sending: handing the message over
5. Delivery: the mailbox provider decides
6. Measurement: what happened, and what to change
7. How the steps depend on each other
8. The cycle in AcelleMail

It ends with the mistakes that are most common at the start and a one-screen summary.

## 1. Permission: someone asks to hear from you

The cycle starts when a person agrees to receive your email. They might fill in a signup form on your
website, tick an empty box at checkout, or download something from a page that says you will follow up
by email. That agreement is called consent.

Consent matters for two reasons. The first is the law. In the EU, the UK and Canada, marketing email
requires consent, with a few narrow exceptions. In the United States, the law does not require it, but it
does require you to stop when someone asks. The second is the mailbox providers, which pay attention to how
people respond to your mail. People who never asked for an email are more likely to report it as spam, and
those reports can affect where your later emails end up.

Two things are worth getting right from the first signup:

- **Say what people will receive.** Consent covers what you described, so the line next to the signup
  button should say what you will send, how often, and who it comes from.
- **Keep a record.** You should be able to show when and how each person joined.

Many senders also ask each new contact to confirm their address by clicking a link in a confirmation
email. This is called double opt-in. It helps keep mistyped and fake addresses off the list, and it gives
you stronger evidence that the owner of the address actually asked to join.

The full picture is in [Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained). If
you send to people in the EU, [GDPR for Email Marketing
Explained](https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained) covers what else the law asks of you.

## 2. The list: the people you can write to

Each signup becomes a contact on a list. A contact is an email address together with whatever else you
asked for, such as a name or a country.

A list does not stay the same. People join, some unsubscribe, and some addresses stop working because
their owners change jobs or stop using the inbox. So the number that matters is not the size of the
list, but the number of contacts you can still write to. In AcelleMail, **Audience → Overview** shows the
difference for your whole account:

![The Audience health card in AcelleMail, with two rows marked: 1 Subscribed and 2 Total. Between them are the Unsubscribed, Unconfirmed and Blacklisted counts.](https://acellesend.com/plugins/acelle/landing/images/help/kb/how-email-marketing-works/02-audience-health.png)

1. **Subscribed** is the number of contacts who receive your campaigns.
2. **Total** counts every contact on your lists. The others stay on the list with a status that explains
   why they receive nothing: they unsubscribed, have not confirmed their address yet, or are
   blacklisted.

You rarely need to send the same email to everyone. A segment is a part of a list chosen by rules, for
example "joined in the last 30 days", "lives in France" or "clicked the last newsletter". Sending to a
segment helps each email reach the people it is actually relevant to.

A list also needs looking after. Addresses that bounce should be removed, people who unsubscribe should
stay unsubscribed, and an old list should be checked before you send to it again. A list that was bought
or copied from somewhere else skips step 1 entirely, which is why it can cause problems at every later
step. See [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained).

## 3. The message: campaigns and automations

There are two main ways to send marketing email.

A **campaign** is one email sent to a list or a segment at a time you choose: a newsletter, an
announcement or a promotion.

An **automation** sends emails automatically when a contact does something or reaches a certain date. Typical
examples are a welcome email after signup, a reminder after an abandoned cart and a birthday offer. You
build it once, and it then runs for every contact who qualifies.

![A campaign and an automation side by side. Anna, Ben and Chloe join a list on different days of the same week. The automation sends each of them a welcome email on the day they join and a tips email three days later. The campaign, a newsletter, reaches all three on the same day.](https://acellesend.com/plugins/acelle/landing/images/help/kb/how-email-marketing-works/03-campaign-and-automation.png)

There is another kind of email that is not marketing at all. Receipts, password resets and similar
messages are called transactional email. They follow different rules and are best sent separately. See
[Transactional vs Marketing Email](https://acellesend.com/kb/articles/transactional-vs-marketing-email).

Whatever the type, a marketing email needs a few things to do its job:

- The From name should be one the reader recognises.
- The subject line should honestly describe what is inside.
- The content should match what you promised at signup.
- There should be one clear call to action.
- Every email needs a working unsubscribe link.

Several laws also require your postal address to appear, including the CAN-SPAM Act in the United States and
CASL in Canada.

In AcelleMail, a campaign is built in five steps, and you can go back to any of them before you send:

![The five steps of a campaign in AcelleMail: Recipients, Setup, Template, Schedule and Confirm.](https://acellesend.com/plugins/acelle/landing/images/help/kb/how-email-marketing-works/04-campaign-steps.png)

An automation is set up as a flow. This example welcomes new subscribers:

![An automation flow in AcelleMail with four nodes marked: 1 TRIGGER, 2 SEND EMAIL, 3 WAIT, 4 CONDITION, which splits into a YES and a NO branch.](https://acellesend.com/plugins/acelle/landing/images/help/kb/how-email-marketing-works/05-automation-flow.png)

1. A **TRIGGER** starts the flow for each contact, here when they join the list.
2. **SEND EMAIL** sends the welcome email.
3. **WAIT** pauses the flow, here for three days.
4. A **CONDITION** then checks whether the contact opened the email, and sends them down the **YES** or
   the **NO** branch.

The step-by-step tutorials are [Your First Email Campaign in
AcelleMail](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail) and [How to set up a welcome
email automation](https://acellesend.com/kb/articles/set-up-a-welcome-email-automation).

## 4. Sending: handing the message over

An email marketing application does not deliver mail by itself. It hands each message to a sending
server, which can be your own mail server or a service such as Amazon SES. The sending server then
passes the message to the mailbox provider of each recipient, such as Gmail, Outlook.com or Yahoo.

Before this works well, your domain has to show that the mail really comes from you. Three DNS records
do that: SPF, DKIM and DMARC. Gmail and Yahoo have required authentication from every sender since 2024,
and without it your email may be refused or sent to spam.

Volume matters too. A new domain or a new sending server has no history with the mailbox providers,
so it is safer to start with small sends to your most engaged contacts and increase the volume
gradually. This is called warming up.

The three records are explained in [Email Deliverability
Explained](https://acellesend.com/kb/articles/email-deliverability-explained), section 3, and the setup is in [How to Set Up
SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).

## 5. Delivery: the mailbox provider decides

Two decisions stand between your sending server and the reader, and the mailbox provider makes both.

First, its server accepts or refuses the message. A refusal is called a bounce. It is permanent when the
address does not exist, and temporary when, for example, the mailbox is full.

Second, the provider decides where to put the accepted message: in the inbox, in a tab such as Gmail's
Promotions, or in the spam folder. Your campaign report sees the first decision. It cannot see the second.

Providers do not publish exactly how they make these decisions, but what they do publish points in the same
direction. They look at whether your mail is authenticated, at the reputation your domain and IP addresses
have built, at how people reacted to your earlier emails, and at the quality of your list. One of the
clearest signals is the spam button. Gmail asks senders to keep spam complaints below 0.1% and never to
reach 0.3%.

All of this is covered in [Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained).

## 6. Measurement: what happened, and what to change

After a campaign, the report shows what happened: how many emails were delivered and how many bounced,
and how many people opened, clicked, unsubscribed or complained. With tracking set up on your website,
you can also see how many of those clicks led to a purchase or a signup.

Not every number deserves the same trust. Opens are counted with a small tracking image, and some mail
apps now load that image whether or not the person actually reads the email, so the open rate is only a
rough guide. Clicks, and what people do after clicking, are a better measure of success. Complaints, hard
bounces and unsubscribes tell you whether your list and your reputation are in good shape.

Then the cycle closes. What you learn changes who you write to next, what you send them and when you
send it. You might send the next email to a segment, rewrite the content, or test two subject lines
against each other. The next campaign then starts with better information than the one before it.

Each number is explained in [Email Marketing Metrics
Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained).

## 7. How the steps depend on each other

Because the steps build on one another, a problem often appears one or two steps after its cause. This
table can help you trace it back:

| A weakness in this step | Tends to show up as | Read more |
|---|---|---|
| 1. Permission: people did not clearly ask for your email | Spam complaints in step 5 and few clicks in step 6 | [Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained) |
| 2. The list: old, bought or unchecked addresses | Hard bounces and spam traps in step 5 | [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained) |
| 3. The message: content that differs from what was promised | Unsubscribes and complaints | [Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained), section 3 |
| 4. Sending: missing authentication, or a sudden jump in volume | Email refused or filed as spam in step 5 | [Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained) |
| 6. Measurement: success judged by opens alone | The wrong changes in the next round | [Email Marketing Metrics Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained) |

## 8. The cycle in AcelleMail

Each step has its place in the AcelleMail menu:

![The AcelleMail menu with three groups marked. Campaigns, with Email, Automation and Templates, is step 3, the message. Audience, with Overview, Lists, Subscribers, Segments and Forms, is steps 1 and 2, permission and the list. Sending, with Sending servers, Sending domains, Email verification and Blacklist, is steps 4 and 5, sending and delivery.](https://acellesend.com/plugins/acelle/landing/images/help/kb/how-email-marketing-works/06-menu.png)

| Step | Where it lives in AcelleMail | Tutorial |
|---|---|---|
| 1. Permission | **Audience → Forms**, and the **Double opt-in** setting of each list | [Build a Signup Form and Embed It on Your Site](https://acellesend.com/kb/articles/creating-effective-signup-forms-and-landing-pages) |
| 2. The list | **Audience → Lists**, **Subscribers** and **Segments** | [Importing Contacts CSV](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping) and [How to build a subscriber segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment) |
| 3. The message | **Campaigns → Email**, **Automation** and **Templates** | [Your First Email Campaign in AcelleMail](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail) |
| 4. Sending | **Sending → Sending servers** and **Sending domains** | [How to Set Up Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) |
| 5. Delivery | **Sending → Email verification** and **Blacklist**, and the **Sending logs** of each campaign | [Sender Reputation Monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring) |
| 6. Measurement | The report of each campaign, which you open from **Campaigns → Email** | [How to read your email campaign report](https://acellesend.com/kb/articles/how-to-read-your-email-campaign-report) |

Which items you see under **Sending** depends on your plan. If your administrator manages the sending
servers for you, **Sending servers** is not shown.

## 9. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| A bought list, or an old customer file, imported and mailed straight away | The people on it did not ask for your email, and some of the addresses no longer work | Send only to people who signed up, and verify an old list before the first send |
| One email to the whole list, every time | Much of it is not relevant to the person reading it, and unsubscribes rise | Use segments, so that each email goes to the people it is meant for |
| A signup form that only says "Subscribe" | People do not know what they agreed to | Add one line that says what you send and how often |
| No SPF, DKIM or DMARC records | Mailbox providers cannot confirm that the mail is yours, and may refuse it | Publish the records before your first campaign |
| A new domain sending to the full list on its first day | It has no reputation yet | Start small and raise the volume gradually |
| A campaign judged by its open rate | Opens are not counted reliably | Look at clicks and at what people did after clicking |
| People who unsubscribe are deleted | Their address can come back with the next import | Leave them on the list as **Unsubscribed** |

## Summary

1. Email marketing is a cycle of six steps: permission, the list, the message, sending, delivery and
   measurement.
2. It starts with permission. Say what people will receive, and keep a record of how each person joined.
3. A list changes all the time. What counts is the number of contacts you may still write to, and
   segments let you write to the right ones.
4. A campaign goes out when you choose. An automation follows each contact's own timing.
5. A sending server transmits your email, and SPF, DKIM and DMARC show that it comes from you.
6. The mailbox provider decides whether to accept each email and where to put it. Your report sees only
   the first of those decisions.
7. Judge a campaign by clicks and by what people did afterwards, and keep an eye on complaints and hard
   bounces.
8. Each step depends on the one before it, so trace a problem back to where it began.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Transactional vs Marketing Email: The Difference and Why It Matters

> How to tell a transactional email from a marketing one, what the US, EU, UK and Canadian rules ask of each, and why to send them separately.

- Canonical URL: https://acellesend.com/kb/articles/transactional-vs-marketing-email
- Author: Lena Hart, Email Deliverability Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Transactional Email, Sender Reputation, Can Spam
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 12 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

A business sends two kinds of email. A transactional email goes out because one person did something,
such as placing an order or asking for a password reset. A marketing email goes out because the business
decided to send it, usually to many people at once. The law, mailbox providers and readers themselves
treat the two differently, so it helps to know which kind you are sending.

This guide covers:

1. The two kinds of email
2. The grey zone between them
3. Emails that mix both
4. What the rules ask of each kind
5. Why the two should be sent separately
6. Where AcelleMail fits

It ends with the mistakes senders make most often and a one-screen summary.

> **Not legal advice.** This guide describes the rules in general terms. The official texts are linked in
> section 4.

## 1. The two kinds of email

![Two kinds of email, two sets of rules. A transactional email is sent because one person did something. It goes to that person, they expect it, and it contains only what is needed for that action. A marketing email is sent because you chose to send it. It goes to a list or segment, and it gives the reader a reason to buy, read or come back.](https://acellesend.com/plugins/acelle/landing/images/help/kb/transactional-vs-marketing-email/01-two-kinds.png)

Different rulebooks use different names for the first kind. The US CAN-SPAM Act calls it a
"transactional or relationship message". The UK regulator, the ICO, speaks of "service messages". Gmail
simply says "transactional messages" and gives as examples "password reset messages, reservation
confirmations, and form submission confirmations".

The US Federal Trade Commission (FTC) gives the most detailed definition. A message is transactional or
relationship when it consists only of content that does one of five things:

1. It facilitates, completes or confirms a transaction the recipient has already agreed to.
2. It gives warranty, recall, safety or security information about something the recipient bought.
3. It tells the recipient about a change to an account, membership, subscription or loan, or gives a
   regular account statement.
4. It gives information about an employment relationship or employee benefits.
5. It delivers goods or services that are part of a transaction the recipient has already agreed to.

The FTC adds that "the law views these categories narrowly". A message does not become transactional
simply because it goes to an existing customer.

## 2. The grey zone between them

Many emails are sent automatically and still sell something. What matters is what the email says, not
what triggered it:

![The grey zone. Password resets, order confirmations, receipts, shipping notices and failed-payment notices are clearly transactional. Newsletters, sale announcements, product launches, win-back campaigns and event invitations are clearly marketing. In between, a welcome email or a trial notice is a service message when it only informs and becomes marketing when it promotes, while abandoned-cart reminders and review requests are safest treated as marketing.](https://acellesend.com/plugins/acelle/landing/images/help/kb/transactional-vs-marketing-email/02-the-grey-zone.png)

Two points help with the cases in the middle.

First, the sender's own label carries little weight. In Google's words, "Message recipients, not Google,
determine the nature of the messages they receive." If people mark a so-called service email as spam, it
is treated like any other unwanted mail.

Second, when you are unsure, it is safer to treat the email as marketing. That means sending it only to
people who agreed to receive marketing, and including an unsubscribe link. Treating a marketing email as
transactional is the mistake that causes problems. The opposite mistake costs very little.

## 3. Emails that mix both

A receipt with a discount code and a shipping notice with product suggestions are both common. Whether
such an email still counts as transactional depends on where you send it.

**In the United States**, the FTC looks at the primary purpose of the message. Two things decide it: the
subject line and the order of the content. The message is commercial if a recipient "would likely
conclude that the message contains an advertisement or promotion", or if the transactional content "does
not appear mainly at the beginning of the message".

![One order, two emails. The first has the subject "Your order #1042 has shipped", shows the order details first and ends with one short suggestion, so it is still transactional. The second has the subject "Your order shipped, and here is 20% off" and shows the promotion before the order details, so it is treated as marketing.](https://acellesend.com/plugins/acelle/landing/images/help/kb/transactional-vs-marketing-email/03-primary-purpose.png)

**In the United Kingdom**, the test is stricter. The ICO says: "If your service message has elements
that are direct marketing, even if that is not the main purpose of your message, then it will count as
direct marketing." General branding and logos do not count as marketing.

**Gmail** advises against mixing at all: "Don't mix different types of content in the same message. For
example, don't include promotions in sales receipt messages."

The simplest policy is to keep promotions out of service emails. If you do add one for readers in a
country that allows it, keep the subject line and the top of the email about the transaction, place the
promotion last, and send it only to people you could lawfully market to.

## 4. What the rules ask of each kind

| Rule | Transactional email | Marketing email |
|---|---|---|
| **United States**, [CAN-SPAM](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business) | It "may not contain false or misleading routing information, but is otherwise exempt from most provisions" | An honest subject line, a clear notice that the message is an advertisement, your postal address, and an opt-out honoured within 10 business days |
| **EU and UK**, ePrivacy rules and the GDPR | A [service message](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/direct-marketing-guidance/identify-direct-marketing/) is not direct marketing, so the marketing consent rule does not apply. The GDPR still covers the personal data, usually on the basis of the contract | Prior consent, or the soft opt-in for your own customers, and a way to opt out in every message |
| **Canada**, [CASL](https://laws-lois.justice.gc.ca/eng/acts/E-1.6/page-1.html) | Consent is not required for a message that solely "facilitates, completes or confirms a commercial transaction" (section 6(6)). The message must still identify the sender and include an unsubscribe mechanism | Express or implied consent, sender identification, and an unsubscribe mechanism |
| **Gmail and Yahoo** | Authentication and the other rules for all senders apply. One-click unsubscribe is not required: "Transactional messages are excluded from this requirement" ([Google](https://support.google.com/a/answer/14229414)) | Bulk senders must support one-click unsubscribe and show a visible unsubscribe link |

Two details are easy to miss. In Canada, a transactional message is excused from the consent rule but
not from the rest, so a receipt sent to a Canadian customer should still say who you are and offer a way
to unsubscribe. And Gmail counts all your mail when it decides whether you are a bulk sender: "we count
all messages sent from the same primary domain", transactional mail included.

The consent rules for marketing are explained in [Email Consent
Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained), and the wider duties under the GDPR in
[GDPR for Email Marketing Explained](https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained).

## 5. Why the two should be sent separately

Mailbox providers keep a reputation for each sender, and they keep it per IP address and per domain. If
receipts and promotions share both, a single campaign that draws complaints can lower the reputation
that your receipts and password resets depend on. Those are the emails your customers can least afford
to miss.

![Two streams, two reputations. Transactional email starts in your shop or app, is sent by a transactional service from an address such as orders@mail.example.com, and has its own reputation. Marketing email starts in AcelleMail, is sent by your sending server from an address such as hello@news.example.com, and has its own reputation. A rise in complaints on the marketing stream does not carry over to the receipts.](https://acellesend.com/plugins/acelle/landing/images/help/kb/transactional-vs-marketing-email/04-separate-streams.png)

The providers recommend the separation themselves:

- **Yahoo**: "Don't send bulk/marketing email from the same IPs you use to send user mail, transactional
  mail, alerts, etc." Its reason is that "Each IP and DKIM domain has a reputation" ([Sender best
  practices](https://senders.yahooinc.com/best-practices/)).
- **Google**: "If you must send from multiple IP addresses, use a different IP address for each message
  type", and "Messages of the same category should have the same From: email address". Its examples are
  `sales@` for receipts, `deals@` for promotions and `alert@` for account notifications ([Email sender
  guidelines](https://support.google.com/a/answer/81126)).

In practice, the separation has three levels. Most senders need the first two:

1. **A From address for each kind of email**, used consistently.
2. **A subdomain for each stream**, for example `mail.example.com` for transactional email and
   `news.example.com` for marketing. Each subdomain gets its own SPF and DKIM records, as described in
   [How to Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).
3. **A separate IP address or sending account for each stream**, once your volume is high enough to keep
   both warm. See [Dedicated vs Shared Sending IP](https://acellesend.com/kb/articles/dedicated-vs-shared-ip-address).

The two streams also behave differently, which is another reason to keep them apart. Transactional email
leaves one message at a time, all day. Marketing email leaves in large batches. When they are mixed, a
problem in one is harder to see in the reports of the other.

## 6. Where AcelleMail fits

AcelleMail is built for the marketing side: campaigns and automations sent to lists. It is not a
transactional email service, because it has no way to send a single message to one person at the moment
your shop or application asks for it. Receipts and password resets should come from the system that
creates them, through a transactional email service. You can use the same provider for both, such as
Amazon SES, as long as each stream has its own subdomain.

Two parts of AcelleMail are still relevant here.

### The service emails AcelleMail sends for a list

Every list has a small set of emails that AcelleMail sends on its own. You can see and edit them under
the list's **Forms / Pages → All templates**:

![A list's Forms / Pages templates with the four emails marked: 1 Confirmation email, 2 Welcome email, 3 Goodbye email, 4 Update email](https://acellesend.com/plugins/acelle/landing/images/help/kb/transactional-vs-marketing-email/05-list-service-emails.png)

1. **Confirmation email**: asks a new contact to confirm their address when double opt-in is on.
2. **Welcome email**: sent after the subscription is confirmed.
3. **Goodbye email**: confirms that someone has been unsubscribed.
4. **Update email**: carries the link a contact uses to update their details.

These are service messages, so it is best to keep promotions out of them. The confirmation email matters
most: the person has not finished subscribing yet.

If your plan lets you use your own sending servers, you can choose which server sends these emails. Open
the list's **Settings** and, under **Sending server**, pick a **Default sending server**. The note on
that card mentions campaigns, but the setting applies to the list's own emails. A campaign's servers are
chosen in the campaign, as shown next.

### Keeping a campaign on its own servers

By default, a campaign uses all the sending servers available to you. To keep marketing on the servers
you have set aside for it:

1. In the campaign's Setup step, open **Sending servers** and switch off **Use all servers randomly**
   (1).
2. Switch on only the servers this campaign may use (2).

![A campaign's Sending servers card: 1 the Use all servers randomly switch, turned off, 2 the list of servers, with one switched on for this campaign](https://acellesend.com/plugins/acelle/landing/images/help/kb/transactional-vs-marketing-email/06-campaign-sending-servers.png)

This card is available when your plan lets you add your own sending servers. Otherwise your
administrator manages the servers for you. Setting up more than one server is covered in [Multiple
Sending Servers in AcelleMail](https://acellesend.com/kb/articles/configuring-multiple-sending-servers).

Automation emails, such as a welcome series or an abandoned-cart reminder, are triggered by what a
contact does. They are still judged by what they say (section 2), and most of them are marketing.

## 7. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| A discount banner at the top of every receipt | The receipt can become a marketing email, and in the UK any promotion makes it one | Keep receipts about the order |
| A promotion labelled "service update" and sent to everyone | The label does not decide the kind of email; the content does | Send promotions only to people who agreed to marketing |
| An abandoned-cart reminder with no unsubscribe link | It is triggered by the shopper, but its purpose is to sell | Treat it as marketing: consent and an unsubscribe link |
| Password resets and newsletters sent from the same address and IP | A campaign that draws complaints can affect the delivery of the resets | Use a separate From address and subdomain for each stream |
| Receipts to Canadian customers with no sender details | CASL excuses them from consent, not from identification and unsubscribe | Add your contact details and an unsubscribe mechanism |
| No SPF or DKIM on the transactional subdomain | Authentication rules apply to all mail, not only to marketing | Publish SPF and DKIM for every subdomain you send from |

## Summary

1. A transactional email is sent because one person did something. A marketing email is sent because you
   decided to send it.
2. An email that is triggered automatically can still be marketing. What it says decides.
3. If you are unsure, treat the email as marketing: consent and an unsubscribe link.
4. Keep promotions out of service emails. The US looks at the primary purpose, while the UK treats any
   promotion as marketing.
5. Transactional email is excused from some rules, but not from all of them. Authentication applies to
   everything, and Canada still asks for sender details and an unsubscribe mechanism.
6. Send the two kinds from different addresses and subdomains, and from different IP addresses at higher
   volumes.
7. AcelleMail handles the marketing side. Use a transactional service for receipts and password resets,
   and keep each campaign on the sending servers you have set aside for marketing.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email Consent Explained: Opt-In, GDPR, CAN-SPAM and CASL

> What makes email consent valid, single vs double opt-in, the proof to keep, and how GDPR, CAN-SPAM and CASL differ, with the AcelleMail settings.

- Canonical URL: https://acellesend.com/kb/articles/email-consent-and-opt-in-explained
- Author: Daniel Reyes, Technical Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Double Opt In, GDPR, Can Spam, CASL
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 17 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Consent is the permission someone gives you to send them email. It is what separates email marketing
from spam: in most countries, the law sets the rules around it, while the largest mailbox providers also watch
how people respond to the emails you send.

This guide covers:

1. What valid consent looks like
2. Single opt-in vs double opt-in
3. The signup form: say what they will get
4. Keeping proof of consent
5. The rules in the United States, the EU and UK, and Canada
6. Making unsubscribing easy
7. How long consent lasts

Each part shows where the same idea lives in AcelleMail. The guide ends with the most common mistakes
and a one-screen summary.

> **Not legal advice.** This guide explains the general models. Which rules apply to you depends on where
> you and your recipients are; the official texts are linked in section 5.

## 1. What valid consent looks like

The EU's General Data Protection Regulation (GDPR) gives the definition that many other rules follow. Consent
is a "freely given, specific, informed and unambiguous indication" of what a person wants, given "by a
statement or by a clear affirmative action" ([GDPR, Article
4(11)](https://eur-lex.europa.eu/eli/reg/2016/679/oj)). On a signup form, those four words become four
simple tests:

| Test | What it means on a signup form | It fails when… |
|---|---|---|
| **Freely given** | Saying no should not cost them anything. | Agreeing to marketing is required to buy, download or register. |
| **Specific** | It covers email marketing from you, about what you described. | The wording is "I agree to receive communications". From whom, and about what? |
| **Informed** | They know who you are, what you will send, and that they can leave at any time. | The form does not name the sender or explain the content or how to leave. |
| **Unambiguous** | They take a clear action: type their address and press the button, or tick an empty box. | The box was already ticked, or the page says "by continuing, you agree". |

The European Data Protection Board (EDPB), the EU's joint body of data protection regulators, explains
what someone needs to be told for their consent to count: who you are, what you want their data for, what
data you collect, and that they can withdraw at any time ([Guidelines 05/2020 on
consent](https://www.edpb.europa.eu/our-work-tools/our-documents/guidelines/guidelines-052020-consent-under-regulation-2016679_en),
§64; its two other items, automated decisions and transfers abroad, rarely apply to a newsletter). A
good signup form can cover all of this in a few lines:

![Anatomy of a consent-ready signup form: the sender's name, what subscribers will get and how often, the Subscribe button as the clear action, how to unsubscribe, and a link to the privacy notice](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/01-signup-form-anatomy.png)

### What does not count

Some things may look like permission, but they are not:

![What counts as permission and what does not. Submitting a signup form, ticking an empty box and clicking a confirmation link count as consent. A box ticked in advance, "by continuing, you agree", a bought or scraped list and a line buried in your terms do not](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/02-what-counts-as-consent.png)

- **A box ticked in advance.** "The use of pre-ticked opt-in boxes is invalid under the GDPR," the EDPB
  says, and "silence or inactivity" does not count either (§79).
- **A list you bought, rented or scraped.** The people on it agreed, if at all, to hear from someone
  else. The UK's Information Commissioner's Office (ICO) accepts a bought-in list only if each person's
  consent named your organisation: "If the consent doesn't name you … then it is not valid"
  ([ICO](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-direct-marketing-using-electronic-mail/how-do-we-comply-with-the-pecr-electronic-mail-marketing-rules/)).
  People who do not know you are more likely to report the mail as spam, and most sending services do not allow such lists
  under their terms. More in [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained).
- **A line hidden in your terms.** The ICO asks you to "keep the consent separate from other things,
  such as terms and conditions". Agreeing to your terms does not mean agreeing to marketing.

### Implied consent and the "soft opt-in"

A few rules allow marketing email without explicit consent, but only in limited situations:

- **EU and UK: the soft opt-in.** You may email your own customers about similar products or services
  when all of these are true (the ICO's list for the UK's Privacy and Electronic Communications
  Regulations, PECR; the EU's ePrivacy Directive has a similar exception):
  1. you got their contact details yourself,
  2. while selling them something, or negotiating to,
  3. you only market your own similar products or services,
  4. you gave them a chance to opt out when you collected the details,
  5. and you give them a chance to opt out in every message.
- **Canada: implied consent.** Canada's Anti-Spam Legislation (CASL) treats an existing business
  relationship as implied consent: a purchase in the past two years, or an inquiry in the past six
  months, and it ends when that period does. CASL also accepts a business address that someone published
  or gave you, without saying they do not want messages, for mail about their work ([CASL, section
  10](https://laws-lois.justice.gc.ca/eng/acts/E-1.6/page-2.html)).
- **United States.** The CAN-SPAM Act does not require consent at all (section 5). But people who never
  asked for your mail can report it as spam, and Gmail and Yahoo set limits on spam complaints (section 6).

## 2. Single opt-in vs double opt-in

With **single opt-in**, an address joins your list as soon as the form is submitted. With **double
opt-in** (also called confirmed opt-in), you send a confirmation email first, and the address joins only
after its owner clicks the link inside.

![Single opt-in: form submitted, contact subscribed. Double opt-in: form submitted, contact unconfirmed, confirmation email sent, they click the link, contact subscribed](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/03-single-vs-double-opt-in.png)

| | Single opt-in | Double opt-in |
|---|---|---|
| The address joins | as soon as the form is submitted | after the confirmation link is clicked |
| Shows the address works | No | Yes |
| Shows its owner asked | No | Strong evidence, because the click comes from their own inbox |
| Typos, fake and malicious sign-ups | Join the list, then bounce or complain | Never confirm, so never receive a campaign |
| The cost | None | Some people never click, so the list grows a little slower |

Few laws specifically require double opt-in, but it gives you the strongest evidence of consent (in Germany it is
the generally expected way to show it), and it keeps mistyped addresses, including typo domains used
as spam traps, off your list. Use it on every form the public can reach.

### Turn on double opt-in for a list

1. Open **Lists**, choose the list, and open its **Settings** tab.
2. Under **Subscription settings**, switch on **Double opt-in** ("Require subscribers to confirm their
   email before being added").
3. Click **Save settings**.

![A list's Settings tab: under Subscription settings, Double opt-in is switched on, then Save settings](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/04-list-double-opt-in-setting.png)

### What a new subscriber sees

The list's **Forms / Pages → All templates** page shows every page and email in the sign-up journey, in
the order a new subscriber sees them:

![Forms / Pages, Subscribe section, numbered in the order a subscriber meets them: Public signup page, Confirmation page, Confirmation email, Thank-you page, Welcome email](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/05-subscribe-pages.png)

1. **Public signup page**: a hosted page with your signup form.
2. **Confirmation page**: shown after they submit the form. Tell them to check their inbox, and their
   spam folder, for the confirmation email.
3. **Confirmation email**: the email with the confirmation link. Keep it short: who you are, what they
   signed up for, one button. No offers yet.
4. **Thank-you page**: shown after they click the link.
5. **Welcome email**: sent once the subscription is confirmed, if **Send welcome email** is on in the
   list's settings.

Click **Settings** on a card to open that page or email and edit it.

### Contacts who have not confirmed

Until they click the link, new contacts stay with the status **Unconfirmed**, and campaigns skip them:
only **Subscribed** contacts receive campaigns. If some never confirm, you can send the link once
more:

1. Open **Subscribers → View all** and set the status filter to **Unconfirmed**.
2. Tick the contacts.
3. Choose **Actions → Send confirmation email**.

![Subscribers filtered to Unconfirmed; two contacts ticked; the Actions menu is open with Send confirmation email highlighted](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/06-unconfirmed-subscribers.png)

Send it once. Someone who has ignored two confirmation emails has given you their answer. Leave the
**Subscribe** item in the same menu alone for unconfirmed contacts. It marks them **Subscribed** without
their confirmation.

## 3. The signup form: say what they will get

An AcelleMail signup form adds people to a list; that is what it is for. Submitting it is the clear,
affirmative action the law asks for, so you do not need an extra "I agree" box. What matters is the
promise next to the button, which makes the consent specific and informed:

![The form builder with example copy: the line under the heading says who sends what and how often, and pressing Subscribe is the consent](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/07-signup-form-promise.png)

Write that line as a promise you intend to keep:

- **What** you will send: "new features and how-to tips", not "updates".
- **How often**: "one email a month".
- **Who** it comes from: your business or team name.

Three rules for checkboxes:

- **Never tick a box in advance.**
- **Keep agreements apart from marketing.** A required box for your terms or privacy policy (the **Terms
  of Service** element in the form builder's **Compliance** group) asks people to accept your terms.
  That is not the same as consenting to marketing.
- **One list per kind of email.** If you also want to send something different, partner offers for
  example, give it its own list and signup form, so each list contains only the people who asked for it.

**Mailing people in Canada?** Under CASL, a request for consent must also give your mailing address plus
a phone number, email address or website, and say that consent can be withdrawn. Put it in the form's
small print.

## 4. Keeping proof of consent

Under the GDPR you must be able to show that a person consented. The burden of proof is yours (Article
7(1)). The EDPB suggests keeping a record of how and when consent was obtained and of the information
shown at the time, and warns that "it would not be sufficient to merely refer to a correct configuration
of the respective website" (§108). Under CASL, too, whoever claims consent has to prove it (section 13).

For each contact, keep:

| Record | Example | What it shows |
|---|---|---|
| When they joined | 2026-09-30 18:26 | When consent was given |
| How they joined | a signup form, the API, an import, added by hand | The route, and which contacts rely on your own records |
| Where from | the IP address of the signup | For a form signup, that it came from a real visit |
| That they confirmed | the status **Subscribed** on a double opt-in list | That someone with access to the inbox said yes |
| What they were shown | the form's wording at the time | What exactly they agreed to |

AcelleMail shows the first three on every contact, under the name at the top of the contact's page:
**Created**, **Source** and **IP address**.

![A contact who joined through the public signup page: Created, Source: web and IP address show when, how and from where they joined](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/08-subscriber-consent-record.png)

**Source** shows how the contact arrived: `web` for the hosted signup page, `embedded-form` for a form on
your website, `api` for the API, `added` for someone added by hand, `imported` for a file import. For a
form signup, **IP address** is the visitor's; for contacts added by hand or through the API it is yours
or your server's. For imported contacts AcelleMail cannot know how you collected them, so the proof has
to come from your own records.

What AcelleMail cannot keep for you is the form's wording: when you change a signup form, save a dated
copy or screenshot of the old version. Keep the proof for as long as you email the person, and after
that only as long as you might need it for a legal claim (EDPB §107).

## 5. The rules in the United States, the EU and UK, and Canada

The three best-known models side by side:

| | United States (CAN-SPAM) | EU and UK (GDPR with ePrivacy / PECR) | Canada (CASL) |
|---|---|---|---|
| **Model** | Opt-out: no consent needed, but stop when asked | Opt-in: consent needed to email individuals, apart from the soft opt-in | Opt-in: express or implied consent needed |
| **Business addresses** | Covered: the FTC says the law "makes no exception for business-to-business email" | Varies by country. The UK lets you email company addresses without consent (not sole traders or ordinary partnerships), if you identify yourself and offer an opt-out | Covered, apart from narrow exemptions in the regulations |
| **In every message** | A clear note that it is an ad, your valid postal address, a working opt-out; no misleading From line or subject | Who you are and a working way to opt out | Your name, mailing address and one other way to reach you; an unsubscribe mechanism |
| **Unsubscribe deadline** | 10 business days; the opt-out must keep working for 30 days after you send | No set number of days: once someone objects to direct marketing, you must stop (GDPR Article 21(3)) | 10 business days; the mechanism must stay valid for 60 days after you send |
| **Maximum penalty** | Up to $53,088 per email (the FTC's figure at the time of writing, adjusted for inflation each year) | GDPR: up to €20 million or 4% of worldwide annual turnover, whichever is higher; UK GDPR: £17.5 million or 4%. The email rules in ePrivacy and PECR carry their own penalties | Up to CAD 10 million per violation for a business, CAD 1 million for an individual |
| **Official source** | [FTC compliance guide](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business) | [GDPR](https://eur-lex.europa.eu/eli/reg/2016/679/oj) · [ICO guidance](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guidance-on-direct-marketing-using-electronic-mail/) | [CASL](https://laws-lois.justice.gc.ca/eng/acts/E-1.6/) |

What decides which rules apply is where your recipients are, not only where you are: the GDPR covers you
when you offer goods or services to people in the EU (Article 3(2)), and CASL covers messages sent from
or opened on a computer in Canada.

If you mail more than one country, the practical approach is to follow the strictest model everywhere:
express consent (the person actively agreed), collected with double opt-in, and every unsubscribe
honoured at once. That meets the consent and unsubscribe rules of all three; each law's own list of what
every message must contain still applies. For the law-by-law details in AcelleMail, see [GDPR Compliance
for Email Marketing with AcelleMail](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing), [CAN-SPAM
Compliance in AcelleMail](https://acellesend.com/kb/articles/can-spam-compliance-checklist) and [CASL Compliance in
AcelleMail](https://acellesend.com/kb/articles/casl-canadian-anti-spam-requirements).

Consent is only one part of the GDPR. What else it asks of a sender, from privacy notices to people's
rights and data processing agreements, is in [GDPR for Email Marketing
Explained](https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained).

## 6. Making unsubscribing easy

Consent can be withdrawn at any time, and the GDPR requires withdrawing to be "as easy" as giving it
(Article 7(3)). The EDPB's example: a festival that took consent with one click online but accepted
withdrawals only by phone, on weekdays during office hours, did not comply (§115).

The deadlines, and the tighter rule the largest mailbox providers apply:

![How fast an unsubscribe must take effect: Gmail and Yahoo 2 days for bulk senders, CAN-SPAM 10 business days, CASL 10 business days, GDPR stop when they object](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/09-unsubscribe-deadlines.png)

Since 2024, Gmail and Yahoo require **bulk senders** to support **one-click unsubscribe**, to show a
visible unsubscribe link, and to honour unsubscribes within two days. For Gmail, a bulk sender is anyone
sending about 5,000 or more messages a day to personal Gmail addresses. One-click unsubscribe works
through **headers**, hidden lines at the top of every email that mail apps read: they let Gmail or Apple
Mail show their own Unsubscribe button ([RFC 8058](https://www.rfc-editor.org/rfc/rfc8058)). The same
rules limit spam complaints: Gmail asks senders to stay below 0.1% and never to reach 0.3%, and Yahoo
sets 0.3% (Google's [Email sender guidelines](https://support.google.com/a/answer/81126), Yahoo's
[Sender best practices](https://senders.yahooinc.com/best-practices/)). More in [Email Deliverability
Explained](https://acellesend.com/kb/articles/email-deliverability-explained).

### In AcelleMail

**One-click unsubscribe.** AcelleMail adds the `List-Unsubscribe` headers, with one-click support, to
every campaign email while **One-Click Unsubscribe Headers** is on. It is an admin setting under
**Settings → General**, section **Email & Campaigns**. Keep it on.

![Admin Settings, General, Email & Campaigns: One-Click Unsubscribe Headers switched on](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/10-admin-one-click-unsubscribe.png)

**The unsubscribe link.** The link in the email opens a short confirmation page: the address being
removed and one button, **Yes, unsubscribe me**. No login, no password, no reason required.

![The Confirm unsubscribe page: the address being removed and the Yes, unsubscribe me button](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-consent-and-opt-in-explained/11-confirm-unsubscribe-page.png)

**Offer a middle way.** Some people want fewer emails, not none. A preference page lets them choose; see
[Subscriber Preferences & Frequency](https://acellesend.com/kb/articles/managing-subscriber-preferences-and-frequency).

**Keep the record.** Do not delete people who unsubscribe: their **Unsubscribed** status is your record
that they said no. A deleted contact can come back with the next import.

## 7. How long consent lasts

- **GDPR: no fixed expiry.** How long consent lasts "will depend on the context, the scope of the
  original consent and the expectations of the data subject", and if what you send changes considerably,
  you need to ask again. The EDPB recommends refreshing consent "at appropriate intervals" (§110–111).
- **CASL: implied consent from a purchase or an inquiry runs out**, two years after the purchase and six
  months after the inquiry. Ask for express consent before then: under CASL, an email that asks for
  consent is itself a commercial message, so it needs a valid consent to be sent.
- **In practice:** people who have not opened or clicked for a long time are the weakest part of your
  consent. Ask them once whether they still want your email, and remove those who do not answer. See
  [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained), section 6.

## 8. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| A "Keep me updated" box ticked in advance at checkout | Not a clear, affirmative action (EDPB §79) | Leave it empty; mail only the people who tick it |
| A form that only says "Subscribe" | The consent is neither specific nor informed | Add one line: what they will get, how often, from whom |
| A list you bought or rented, or got from a partner or an event | Consent does not pass to you, and the soft opt-in needs details you collected yourself | Import only people who asked to hear from you. Do not email the others to ask: that email is itself marketing. The ICO has fined companies for exactly that, Flybe and Honda among them, and CASL treats it the same way |
| "I agree to the Terms and Privacy Policy" as the only box | Accepting your terms is not consent to marketing | Keep marketing consent separate: the button and the promise next to it |
| An unsubscribe link that asks people to log in | Harder to leave than to join (GDPR Article 7(3)) | One page, one button, as AcelleMail does by default |
| No record of how older contacts joined | You cannot show that they consented (Article 7(1)) | Keep Created, Source and IP address, and a dated copy of each form version |

## Summary

1. Valid consent is freely given, specific, informed and unambiguous: a clear action by the person.
2. Never tick a box in advance, never rely on a bought list, never hide consent in your terms.
3. Put the promise (what, how often, from whom) next to the signup button.
4. Use double opt-in on every public form: **Lists → [your list] → Settings → Double opt-in**.
5. Keep proof: when, how and from where each contact joined, and what they were shown.
6. Know your model: CAN-SPAM is opt-out, while the EU, UK and Canada are opt-in. Or follow the strictest
   model everywhere.
7. Make leaving easy with one page and one button, honour it at once, and keep **One-Click Unsubscribe
   Headers** on.
8. Refresh consent over time; in Canada, implied consent from a purchase or an inquiry expires.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email Marketing Metrics Explained: Formulas, Limits and What to Trust

> What each campaign rate divides by, why opens and clicks can mislead, the complaint and bounce limits that matter, and how AcelleMail counts.

- Canonical URL: https://acellesend.com/kb/articles/email-marketing-metrics-explained
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: reference · Difficulty: beginner
- Tags: Email Marketing Basics, Open Rates, Click Rates, Analytics
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 12 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

A campaign report is a handful of counts and the rates built from them. To read one properly, you need
to know two things about every rate: what it divides by, and what can make the count on top misleading.
This guide goes through both, metric by metric.

This guide covers:

1. The five counts behind every report
2. Delivery: delivered and bounced
3. Engagement: opens, clicks and unsubscribes
4. Why opens and clicks can mislead
5. Complaints: the number providers watch
6. Outcomes: conversions, revenue and ROI
7. Reading a campaign report in AcelleMail
8. Which numbers to judge a campaign by

It ends with the most common mistakes and a one-screen summary.

## 1. The five counts behind every report

Every rate in a campaign report is one of five counts divided by another:

![From send to sale: 10,000 sent, 9,800 delivered, 3,920 opened, 490 clicked, 49 converted, and the five rates built from them: delivery rate, open rate, click rate, click-to-open rate and conversion rate](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-metrics-explained/01-from-send-to-sale.png)

Two conventions decide whether two numbers can be compared:

- **Unique or total.** A unique count counts each person once, however many times they opened or
  clicked. A total count includes every repeat. Rates use unique counts; a total is useful only to see
  how often people came back.
- **What the rate divides by.** Most tools divide opens and clicks by *delivered*, some by *sent*. The
  same campaign can show two different open rates in two tools. Check before you compare.

## 2. Delivery: delivered and bounced

| Metric | Formula | What it tells you |
|---|---|---|
| **Delivered** | sent − bounced | How many messages the receiving servers accepted. It says nothing about the inbox or spam folder. |
| **Delivery rate** | delivered ÷ sent | The share that was accepted. |
| **Bounce rate** | bounced ÷ sent | The share that was refused. Split it by type before you act on it. |
| **Hard bounce rate** | hard bounces ÷ sent | The quality of your list. On a list collected with consent and cleaned regularly, it stays close to zero. |

A **hard bounce** is permanent (the address does not exist); a **soft bounce** is temporary (a full
mailbox, a busy server). Sending services judge you on hard bounces. Amazon SES, for example, counts
"only hard bounces", asks senders to stay below 2%, puts an account under review at 5% and may pause
sending at 10% ([Amazon SES sending review
FAQ](https://docs.aws.amazon.com/ses/latest/dg/faqs-enforcement.html)). Other services publish their own
limits.

A high delivery rate does not mean your mail reached the inbox. That second decision is covered in
[Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained); what to do about hard
bounces is in [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained).

## 3. Engagement: opens, clicks and unsubscribes

| Metric | Formula | What it tells you |
|---|---|---|
| **Open rate** | unique opens ÷ delivered | Roughly how many people saw the email. Useful for trends in your own sends, weak as an absolute (section 4). |
| **Click rate** | unique clicks ÷ delivered | How many people acted on the email. The most dependable engagement signal you have. |
| **Click-to-open rate (CTOR)** | unique clicks ÷ unique opens | How well the content persuaded the people who opened. Automatic opens push it down. |
| **Unsubscribe rate** | unsubscribes ÷ delivered | How many people decided they no longer want your mail. A jump after one campaign points at its content or its timing. |

How the counts are collected matters for what follows. An **open** is recorded when a tiny image in the
email, a tracking pixel, loads from your server. A **click** is recorded when a link first passes
through a tracking address that notes the click and then forwards the reader. In AcelleMail both are
switched on per campaign, with **Track opens** and **Track clicks** under **Advanced Settings** in the
campaign's Setup step. With either switched off, its rate stays at zero.

## 4. Why opens and clicks can mislead

Both signals depend on software behaving like a reader, and three things can break that link:

![What skews opens and clicks: Apple Mail Privacy Protection makes opens too high, images turned off make opens too low, and link scanners make clicks too high](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-metrics-explained/02-what-skews-opens-and-clicks.png)

**Apple Mail Privacy Protection.** With it on, Apple Mail loads the images in an email when the message
arrives: remote content "is privately downloaded in the background when you receive a message (instead
of when you view it)" ([Apple, Mail on
Mac](https://support.apple.com/guide/mail/protect-email-privacy-mlhlp1205/mac)). In Apple's words the
feature "prevents senders from seeing if you've opened the email message they sent you" ([Apple, Mail on
iPhone](https://support.apple.com/guide/iphone/use-mail-privacy-protection-iphf084865c7/ios)). Every
Apple Mail reader who turned it on counts as an open, whether they read the message or not. The share
varies with your audience, so the same open rate can mean different things on two lists.

**Images turned off.** Some mail apps, and some readers, load images only when asked. Those people can
read the whole email and never count as an open. A click still counts, so a reader who clicked has read
the email whether or not an open was recorded.

**Link scanners.** Security filters in many company mail systems follow the links in incoming mail to
check them. Microsoft Defender for Office 365, for one, says "URLs are scanned prior to message
delivery" ([Microsoft](https://learn.microsoft.com/en-us/defender-office-365/safe-links-about)). A scan
can register as a click. The pattern gives it away: every link in the email clicked within seconds of
delivery, often from the same company domain.

What this means in practice:

- Judge a campaign's success by clicks and what came after them, not by opens.
- Use the open rate to compare your own sends with each other: the same list, a similar time, two
  subject lines in an A/B test.
- Look past opens when you decide who has gone quiet; clicks are the better sign of a reader who left.
  See [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained), section 6.

## 5. Complaints: the number providers watch

The **complaint rate** (also called spam rate) is complaints ÷ delivered: how many people pressed
"Report spam". It is the one engagement number mailbox providers publish limits for:

![The two rates with published limits: complaint rate limits from Gmail (below 0.1%, never 0.3%), Yahoo (below 0.3%) and Amazon SES (review from 0.1%, may pause at 0.5%), and the Amazon SES bounce rate limits (best below 2%, review from 5%, may pause at 10%)](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-metrics-explained/03-health-limits.png)

- **Gmail** asks senders to "keep spam rates reported in Postmaster Tools below 0.10% and avoid ever
  reaching a spam rate of 0.30% or higher" ([Email sender
  guidelines](https://support.google.com/a/answer/81126)). It counts only mail that reached the inbox:
  the spam rate "is the percent of your messages that are delivered to engaged recipient's Inbox and
  then marked as spam by the recipient".
- **Yahoo** asks bulk senders to stay below 0.3% ([Sender best
  practices](https://senders.yahooinc.com/best-practices/)).
- **Amazon SES** puts an account under review at 0.1% and may pause sending at 0.5%, counting only "mail
  sent to domains that send complaint feedback to SES".

**Why your report shows fewer complaints than there are.** A sending platform hears about a complaint
only when the mailbox provider reports it back through a feedback loop. Gmail does not report individual
complaints at all; its spam rate is visible only in [Gmail Postmaster
Tools](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough). A campaign report that shows zero complaints
means none came back, not that nobody complained.

## 6. Outcomes: conversions, revenue and ROI

| Metric | Formula | What it tells you |
|---|---|---|
| **Conversion rate** | conversions ÷ delivered, or ÷ unique clicks | How many recipients did what the email asked: bought, booked, signed up. Always say which denominator you used. |
| **Revenue per email** | revenue from the campaign ÷ delivered | What each delivered email earned. Lets you compare campaigns of different sizes. |
| **Return on investment (ROI)** | (revenue − cost) ÷ cost | Whether the campaign paid for itself. |
| **List growth rate** | (new contacts − contacts lost) ÷ contacts at the start of the period | Whether the list grows faster than it loses unsubscribes, bounces and complaints. |

None of these happen in the email, so your email platform cannot see them on its own. Tag your links so
your analytics tool or shop can tell which campaign sent the visitor. In AcelleMail, switch on **Enable
UTM tracking** under **UTM tracking** in the campaign's Setup step; every link then carries the
campaign's UTM parameters. Details: [UTM Parameters in
AcelleMail](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking) and [Email Campaign
ROI](https://acellesend.com/kb/articles/email-roi-calculation-measuring-campaign-profitability).

### A worked example

The counts from section 1, with the bounces and complaints added. Illustrative numbers, not a benchmark:

| Result | Count |
|---|---|
| Sent | 10,000 |
| Bounced | 200 (150 hard, 50 soft) |
| Delivered | 9,800 |
| Unique opens | 3,920 |
| Unique clicks | 490 |
| Unsubscribes | 29 |
| Complaints | 5 |
| Orders | 49 |

| Metric | Calculation | Result |
|---|---|---|
| Delivery rate | 9,800 ÷ 10,000 | 98% |
| Bounce rate | 200 ÷ 10,000 | 2% (hard: 1.5%) |
| Open rate | 3,920 ÷ 9,800 | 40% |
| Click rate | 490 ÷ 9,800 | 5% |
| Click-to-open rate | 490 ÷ 3,920 | 12.5% |
| Unsubscribe rate | 29 ÷ 9,800 | 0.30% |
| Complaint rate | 5 ÷ 9,800 | 0.05% |
| Conversion rate (of delivered) | 49 ÷ 9,800 | 0.5% |
| Conversion rate (of clicks) | 49 ÷ 490 | 10% |

The complaint rate looks safe at 0.05%. Remember that it counts only the complaints that came back to
you.

## 7. Reading a campaign report in AcelleMail

Open **Campaigns → Email** and click a sent campaign. Its report starts with four cards:

![A campaign report's four headline cards: 1 Recipients 1,938, 2 Delivered 1,187 (61.25%), 3 Opened 469 (39.51%), 4 Clicked 47 (3.96%)](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-metrics-explained/04-campaign-report.png)

1. **Recipients**: every contact on the lists or segments the campaign was sent to. That includes the
   contacts it skipped because they were not **Subscribed**, or because email verification found them
   Undeliverable, Risky or Unknown. It is not the number sent. In this example, 1,938 contacts were on
   the list and 1,207 of them were Subscribed.
2. **Delivered**: the messages your sending server accepted, 1,187 here, including any that bounce
   later. Its percentage is of **Recipients**, so it is not a delivery rate. Messages that could not be
   sent at all show as **Failed** under **Statistics breakdown**, 20 in this case. The delivery rate of
   what was actually sent is 1,187 ÷ (1,187 + 20) = 98.3%.
3. **Opened**: unique opens, as a percentage of Delivered.
4. **Clicked**: unique clicks, as a percentage of Delivered. The click-to-open rate is not shown as a
   figure of its own; here it is 47 ÷ 469 = 10%.

Further down, the **Click rate** card lists **Total clicks** next to **Unique clicks**. The rate uses
the unique count.

**The figures are saved, not live.** At the top of the report, next to "Last updated", the ↻ button
(**Refresh campaign data**) recalculates them in the background; come back a few minutes later. In the
campaign list, a campaign whose figures need updating says "No statistics or out-dated, click to
update".

**Who did what.** The **Sending logs** menu on the report (**Logs** on an A/B test) lists each contact
behind the numbers: the **Delivery log**, **Bounce log**, **Feedback log**, **Open log**, **Click log**
and **Unsubscribe log**.

**Comparing campaigns.** The campaign list shows Delivered, Open rate and Click rate for every campaign.
Type part of a name into the search box to line up similar sends:

![The campaign list searched for "Edition": four campaigns with Delivered out of every contact on their list, Open rate and Click rate side by side](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-metrics-explained/05-compare-campaigns.png)

The number after the slash in the **Delivered** column is the same Recipients count as on the report:
every contact on the list.

## 8. Which numbers to judge a campaign by

- **Success: clicks and conversions.** What readers did.
- **Health: complaints, hard bounces and unsubscribes.** Whether your list and your reputation are in
  good shape. These are the numbers with limits attached (section 5).
- **Opens: trends only.** Compare within your own sends, never as an absolute.

This guide gives no "industry average" on purpose. Published averages mix audiences, countries and
counting methods, and many cannot say how much of their open rate is automatic. Your own history is the
benchmark that matters: the same kind of campaign, to the same list, over time. When you want to know
whether a change helped, change one thing at a time; see [A/B Testing Email Subject
Lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates).

## 9. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| Your open rate compared with an "industry average" or another tool's | Counting methods and denominators differ | Compare with your own earlier campaigns |
| Recipients read as "how many we sent" | It includes every contact the campaign skipped | Use Delivered plus Failed for what was sent |
| A rising open rate celebrated | Automatic opens can rise on their own, with no new readers | Check that clicks rose too |
| Total clicks used as the click count | Repeat clicks and link scanners inflate it | Use unique clicks |
| "Zero complaints" taken at face value | Gmail does not report complaints back | Check Gmail Postmaster Tools |
| A report full of zeros right after sending | The figures are saved and not yet updated | Click ↻ next to "Last updated" |
| A conversion rate with no denominator | Of delivered and of clicks differ tenfold | Say which one you used |

## Summary

1. Every rate is one count divided by another. Know which, and use unique counts.
2. Delivered means accepted by the receiving server, not placed in the inbox.
3. Opens are skewed both ways: Apple Mail Privacy Protection adds opens, blocked images hide them. Use
   them for trends only.
4. Judge success by clicks and conversions, and watch for link scanners.
5. Keep complaints below 0.1% and hard bounces near zero. Your report sees only the complaints that come
   back to you.
6. Tag links with UTM parameters to measure what happens after the click.
7. In AcelleMail, Recipients counts every contact on the list; read rates against Delivered, and refresh
   saved figures with ↻.
8. Benchmark against your own campaigns, not against averages.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email Deliverability Explained: How Mail Reaches the Inbox

> Delivered is not the same as inboxed. What Gmail, Yahoo and Microsoft look at before they file your email, their sender rules, and the AcelleMail settings.

- Canonical URL: https://acellesend.com/kb/articles/email-deliverability-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: guide · Difficulty: beginner
- Tags: Email Marketing Basics, Inbox Placement, Sender Reputation, Bounce Handling
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 13 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Deliverability is your ability to get email into the inbox. Being accepted by the receiving server is
only half of it; the message also has to end up somewhere the reader will see it. Mailbox providers such
as Gmail, Outlook.com and Yahoo make that decision for every message, based on signals you have some
control over.

This guide covers:

1. Delivered vs inboxed
2. What mailbox providers weigh
3. Authentication: SPF, DKIM and DMARC
4. Reputation: earn it slowly
5. Engagement and spam complaints
6. List quality and bounces
7. The Gmail, Yahoo and Microsoft sender rules
8. How to see where you stand

Each part shows where the idea lives in AcelleMail. The guide ends with the most common mistakes and a
one-screen summary.

## 1. Delivered vs inboxed

Two separate decisions stand between your campaign and a reader:

1. **The receiving server accepts or refuses the message.** Accepted means *delivered*. Refused is a
   **bounce**, and the receiving server says why: the address does not exist, the mailbox is full, or the
   message was blocked.
2. **The mailbox provider decides where to put the accepted message**: in the inbox, in a tab such as
   Gmail's Promotions, or in the spam folder.

![Delivered is not the same as inboxed. The receiving server first accepts or refuses the message. An accepted message is then filed in the inbox, in a tab such as Promotions, or in the spam folder. The campaign report sees only the first decision](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/01-delivered-vs-inboxed.png)

Your campaign report sees the first decision, and not even all of it. In AcelleMail, **Delivered**
counts the messages your sending server accepted, including ones that bounce later. A refusal shows up
when the bounce comes back (section 6). The report cannot see the folder at all. A campaign can be 99%
delivered and still land mostly in spam; falling opens and clicks, along with the mailbox providers' own
tools (section 8), can only give you clues.

The Promotions tab is not the spam folder. Mail there is still in the inbox, sorted by type.

## 2. What mailbox providers weigh

No provider publishes a formula. What they do publish points in the same direction: prove who you are,
send mail people asked for, make leaving easy, and keep complaints low. In practice, that comes down to
five signals:

![What mailbox providers weigh: authentication, reputation, engagement, list quality and content, with what you can do about each one](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/02-five-signals.png)

The rest of this guide takes them one at a time.

## 3. Authentication: SPF, DKIM and DMARC

Anyone can type your address into the From line of an email. Three DNS records let a receiving server
check that a message claiming to come from your domain really does:

| Record | What it checks | Standard |
|---|---|---|
| **SPF** (Sender Policy Framework) | Is the server that sent this message on the list your domain publishes? It checks the domain of the **Return-Path**, the address bounces go back to. | [RFC 7208](https://www.rfc-editor.org/rfc/rfc7208) |
| **DKIM** (DomainKeys Identified Mail) | Is the message unchanged since your domain signed it? The receiver verifies the signature with a public key published in your DNS. | [RFC 6376](https://www.rfc-editor.org/rfc/rfc6376) |
| **DMARC** | Did SPF or DKIM pass **for the domain in the From line**? It also tells receivers what to do when neither does (`p=none` means take no action, `p=quarantine` means treat it as spam, and `p=reject` means refuse it) and where to send reports. | [RFC 7489](https://www.rfc-editor.org/rfc/rfc7489) |

The word that matters is **aligned**. SPF and DKIM each check a domain, but not necessarily the one the
reader sees. DMARC passes only when SPF or DKIM passes *and* its domain matches the From domain, or is
part of the same domain, such as `mail.brightside.coffee` for `brightside.coffee` ("relaxed" alignment,
the default):

![SPF, DKIM and DMARC check three different names in one message: the From domain the reader sees, the Return-Path domain that SPF checks and the DKIM signing domain. DMARC passes only when SPF or DKIM passes for a domain aligned with the From domain](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/03-authentication-alignment.png)

Two practical points from the providers themselves:

- **DKIM key length.** Gmail requires a DKIM key of at least 1024 bits and recommends 2048. The keys
  AcelleMail creates for your domains are 1024-bit, which meets that minimum.
- **Start DMARC at `p=none` with a report address** (`rua=`). The policy asks receivers to take no
  action yet, and the reports (from the providers that send them) show every service sending mail as
  your domain. Tighten the policy only when all of them pass; see [DMARC Enforcement
  Migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration).

### In AcelleMail

Open **Sending → Sending domains**, add the domain you send from, publish the records AcelleMail lists
for it at your DNS provider, then click **Verify DNS records** on the domain's page. The list shows each
domain's state:

![Sending Domains: awesomesolutions.demo is Verified; mail.awesomesolutions.demo is Unverified because its DNS records are not published yet](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/04-sending-domains.png)

1. **Verified**: the records AcelleMail lists for the domain were found.
2. **Unverified**: the records were not found yet. DNS changes can take time to spread; publish them,
   wait, and verify again.

**Verified** does not cover DMARC: it is one record for your whole domain, and you publish it yourself.
Whether SPF is checked depends on how the domain was set up. The step-by-step setup is in [How to Set Up
SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) and [Complete DNS
Setup for Email Sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending).

## 4. Reputation: earn it slowly

Mailbox providers keep a reputation for your sending **IP addresses** and your **domain**, built from
how your mail has been received over time. Two things follow:

- **Anything new starts with no history.** A new domain or a new dedicated IP address has no reputation,
  good or bad. Start with small volumes to your most engaged contacts and raise them step by step. This
  is called **warming up**. Google's own advice is to "increase sending volume slowly to avoid delivery
  problems", and it tracks volume, feedback and limits per domain and IP address.
- **Consistency counts.** A steady rhythm from the same domain builds trust faster than bursts.

On a **shared IP**, other senders' behaviour affects the reputation you share; on a **dedicated IP**, it
is all yours, which also means you have to earn it. See [Dedicated vs Shared Sending
IP](https://acellesend.com/kb/articles/dedicated-vs-shared-ip-address).

### In AcelleMail

AcelleMail can ramp the volume up for you with a warmup strategy. The schedules and the setup are in
[Domain Warmup](https://acellesend.com/kb/articles/domain-warmup) and [IP Warmup
Schedule](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers). Separately, every sending server has
a **Sending limit** that caps how fast it sends. You find it under **Sending → Sending servers**: open
the server, then the **Configuration** card. That menu appears if your plan lets you add your own
sending servers; otherwise your administrator sets both.

![A sending server's Configuration: 1 Bounce handler, 2 Feedback loop handler, 3 Sending limit](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/05-sending-server-configuration.png)

The other two settings on that card, **Bounce handler** and **Feedback loop handler**, come up in the
next two sections.

## 5. Engagement and spam complaints

Providers learn from how people treat your mail. The clearest signal is the **spam button**, and it is
the one signal with a published limit:

- **Gmail** asks senders to keep the spam rate shown in its Postmaster Tools below **0.1%**, and never
  to reach **0.3%**. A bulk sender above 0.3% cannot ask Google to look into its delivery problems
  (Google calls this mitigation) until the rate has stayed below 0.3% for seven days in a row.
- **Yahoo** sets **0.3%**, counted against mail delivered to the inbox.

Complaints come from mail people did not ask for, no longer want, or cannot find a way out of. Consent
and an easy unsubscribe are the fixes; see [Email Consent
Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained).

### In AcelleMail

Complaints reach AcelleMail through **feedback loops**, services some mailbox providers run to report
complaints back to the sender. Each campaign lists them under **Sending logs → Feedback log**. What
happens next depends on how the complaint arrives:

- **Reported by a sending service** such as Amazon SES: AcelleMail marks the contact **Spam reported**,
  and campaigns skip it.
- **Read by a Feedback loop handler** (number 2 in the screenshot above, a mailbox your administrator
  sets up under **FBL Handlers**): the complaint is logged, but the contact is not changed. Blacklist
  those contacts yourself, as shown in [Email List Quality
  Explained](https://acellesend.com/kb/articles/email-list-quality-explained).

Details: [Feedback Loops (FBL)](https://acellesend.com/kb/articles/feedback-loops-fbl-setup).

Gmail does not report individual complaints. Its spam rate is only in Postmaster Tools; see [Gmail
Postmaster Tools Walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough).

## 6. List quality and bounces

A **hard bounce** is permanent: the address does not exist, or the server will never accept it. A
**soft bounce** is temporary: the mailbox is full, or the server is briefly unavailable. A high hard-bounce
rate tells providers your list was not collected or maintained carefully.

Worse than a bounce is a **spam trap**: an address no person uses, kept by mailbox providers and
blocklist operators to catch senders with poor list habits. You cannot spot one by looking at it; you
avoid them with consent, double opt-in and regular cleaning. See [Email List Quality
Explained](https://acellesend.com/kb/articles/email-list-quality-explained).

### In AcelleMail

Bounces reach AcelleMail through the sending server's **Bounce handler** (number 1 in the screenshot
above): a mailbox AcelleMail reads, which your administrator sets up under **Bounce Handlers**. Services
such as Amazon SES report bounces through their own notifications instead; see [How to Set Up Amazon SES
with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail).

AcelleMail treats every permanent refusal (a 5xx reply from the receiving server) as a hard bounce, and
a hard bounce marks the contact **Blacklisted** for all your lists. That includes refusals that are
really about you, such as a failed authentication check: if a whole domain starts hard-bouncing at once,
check section 3 before you blame the list. Once the cause is fixed, ask your administrator to take those
addresses off the blacklist.

Each campaign lists its bounces under **Sending logs → Bounce log**:

![A campaign's Bounce log under Sending logs: each bounced recipient with its bounce type, hard or soft](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/06-bounce-log.png)

Before you send to an old or imported list, verify it. See [How to verify your email list before you
send](https://acellesend.com/kb/articles/how-to-verify-your-email-list-before-you-send).

## 7. The Gmail, Yahoo and Microsoft sender rules

In 2024 Gmail and Yahoo turned long-standing advice into requirements, and Microsoft followed in 2025:

![What Gmail, Yahoo and Microsoft require. Every sender needs SPF or DKIM, a spam rate below 0.3% and valid forward and reverse DNS. Bulk senders also need SPF and DKIM, a DMARC record, alignment, one-click unsubscribe and unsubscribes honoured within 2 days. Microsoft requires SPF, DKIM, DMARC and alignment from senders of more than 5,000 messages a day](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-deliverability-explained/07-bulk-sender-rules.png)

The details that decide whether they apply to you:

- **Who is a bulk sender, for Gmail:** anyone sending close to 5,000 or more messages a day to personal
  Gmail addresses. Mail from your primary domain and all its subdomains counts together, and once you
  qualify, the status is permanent. The rules cover personal Gmail accounts, not Google Workspace ones.
- **Gmail is enforcing.** In November 2025 Gmail began ramping up enforcement: messages that fail the
  requirements can be refused, for a while or for good.
- **Yahoo's bulk rules** match Gmail's, with two differences: Yahoo publishes no volume number for
  "bulk", and it accepts a `mailto:` unsubscribe header, though it "highly recommends" the one-click
  method.
- **Microsoft** has required SPF, DKIM and DMARC (at least `p=none`, aligned) since May 2025 from
  domains sending more than 5,000 messages a day to Outlook.com, Hotmail and Live addresses. Mail that
  fails is refused with the error `550 5.7.515`.

The official pages: Google's [Email sender guidelines](https://support.google.com/a/answer/81126) and
[FAQ](https://support.google.com/a/answer/14229414), Yahoo's [Sender best
practices](https://senders.yahooinc.com/best-practices/) and Microsoft's [requirements for high-volume
senders](https://techcommunity.microsoft.com/blog/microsoftdefenderforoffice365blog/strengthening-email-ecosystem-outlook%E2%80%99s-new-requirements-for-high%E2%80%90volume-senders/4399730).

In AcelleMail, the one-click unsubscribe header is the **One-Click Unsubscribe Headers** admin setting.
See [Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained), section 6.

## 8. How to see where you stand

Your campaign report shows deliveries, bounces and spam reports. For what it cannot see, use the
providers' own tools:

| Tool | What it shows | Guide |
|---|---|---|
| Gmail Postmaster Tools | Your spam rate, authentication results and compliance with Google's sender rules | [Gmail Postmaster Tools Walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough) |
| Microsoft SNDS | How Outlook.com sees your sending IP addresses | [Microsoft SNDS Walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough) |
| DMARC reports | Every server sending mail as your domain, and whether it passes | [DMARC Enforcement Migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration) |

What to watch week by week is in [Sender Reputation
Monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring).

## 9. Common mistakes

| What you see | Why it is a problem | What to do |
|---|---|---|
| Two SPF records on one domain | SPF fails with a `permerror`, because the standard allows only one | Merge them into a single record |
| DKIM signed with the sending service's domain | DKIM passes but is not aligned with your From domain, so it does not count for DMARC | Set up DKIM for your own domain |
| DMARC jumped straight to `p=reject` | Mail from a service you forgot, such as invoices or a help desk, is refused | Start at `p=none`, read the reports, then tighten |
| A new domain or server sending full volume on day one | No reputation yet; Google asks senders to increase volume slowly | Warm up: a warmup strategy, or a low **Sending limit** |
| An old, bought or imported list mailed without checking | Hard bounces and spam traps | Verify the list first; mail only people who asked |
| An SMTP sending server with no bounce handler | Dead addresses keep getting mail | Pick a **Bounce handler** on every SMTP server |
| An unsubscribe link that is hard to find | People press "Report spam" instead | A visible link plus the one-click header |

## Summary

1. Delivered means accepted; deliverability is about reaching the inbox.
2. Providers weigh authentication, reputation, engagement, list quality and content.
3. Publish SPF, DKIM and DMARC, and make sure they are **aligned** with your From domain.
4. Warm up anything new, and keep your volume steady.
5. Keep spam complaints below 0.1%, and never let them reach 0.3%.
6. Make sure bounces and complaints reach AcelleMail, through a **Bounce handler** on every SMTP server
   or the sending service's own notifications. Check the **Feedback log** for complaints.
7. Gmail's and Yahoo's every-sender rules apply to everyone. Gmail's bulk rules apply once you send
   close to 5,000 messages a day to Gmail addresses; Yahoo publishes no number; Microsoft's rules apply
   above 5,000 a day to Outlook.com.
8. Watch Postmaster Tools, SNDS and your DMARC reports.

Terms used here are defined in the [Email Marketing Glossary](https://acellesend.com/kb/articles/email-marketing-glossary).

---

# Email Marketing Glossary: Terms Every Sender Should Know

> Plain-English definitions of email marketing terms you meet in guides, dashboards and reports, the pairs people confuse, and what AcelleMail calls them.

- Canonical URL: https://acellesend.com/kb/articles/email-marketing-glossary
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Domain Knowledge — Email marketing fundamentals — consent, deliverability, metrics, list quality, key terms
- Content type: reference · Difficulty: beginner
- Tags: Email Marketing Basics
- Published: 2026-09-30 · Last updated: 2026-10-21
- Reading time: 27 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

This glossary explains the terms you are likely to meet when you send email campaigns: in this knowledge
base, in the dashboard of your sending service and in your campaign reports. The definitions are kept
short. Where a term has a full article, the definition links to it.

The terms are grouped by topic and follow the path of an email, from the moment someone signs up to the
report you read afterwards. Related terms therefore sit next to each other. If you are looking for one
word, the Find in page command of your browser is the quickest way to reach it (Ctrl+F on a computer, or
Cmd+F on a Mac).

This glossary covers:

1. People and permission
2. Lists and list quality
3. Messages
4. Sending
5. Authentication
6. Delivery
7. Tracking and metrics
8. Law and privacy
9. Terms that are easy to confuse
10. These terms in AcelleMail

## 1. People and permission

These terms describe the people you send to and the permission they gave you. [Email Consent
Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained) covers the subject in full.

| Term | What it means |
|---|---|
| **Contact** (subscriber, recipient) | A person on one of your lists, identified by an email address. Many tools say subscriber. A report may say recipient for the people a particular email was sent to. |
| **List** (mailing list, audience) | A set of contacts that you send email to. Each contact has an email address, and most lists also store a name and a few other details. |
| **Signup form** | The form a person fills in to join your list. Its wording tells them what they will receive and who will send it. |
| **Opt-in** | A person's agreement to receive your email, usually given by submitting a signup form. |
| **Single opt-in** | A way of running a list in which an address joins as soon as the signup form is submitted. |
| **Double opt-in** (confirmed opt-in) | A way of running a list in which the person first receives a confirmation email and joins only when they click the link in it. The click shows that the address works and is good evidence that someone with access to the inbox asked to join. It is not proof, because a security filter that checks links can follow the link on its own. |
| **Consent** | The permission a person gives you to send them email. Under the GDPR it has to be freely given, specific, informed and unambiguous, and you must be able to show that you have it. |
| **Express and implied consent** | Express consent means that the person actively agreed, for example by submitting a signup form. Implied consent is assumed from an existing relationship, such as a recent purchase. Only some laws accept it. In Canada, for example, it expires two years after the purchase. |
| **Soft opt-in** | An exception in the EU and UK rules. A business may email its own customers about similar products without asking for consent first, provided that they could opt out when their details were collected and can opt out in every message. |
| **Proof of consent** | The record that shows when and how a person joined your list, such as the date, the form they used and the IP address of the signup. |
| **Unsubscribe** (opt-out) | A person's request to stop receiving your email. Every marketing email needs a working way to make that request. |
| **Preference centre** | A page where a contact can choose what they receive or how often, as an alternative to leaving the list. See [Subscriber Preferences & Frequency](https://acellesend.com/kb/articles/managing-subscriber-preferences-and-frequency). |
| **Suppression list** (do-not-contact list) | The addresses you must not send to again, for example because the person unsubscribed or complained, or because the address hard-bounced (section 6). Because the list is kept, an address stays excluded even if it is imported again later. |

## 2. Lists and list quality

These terms describe how a list is organised and what keeps it in good condition. The reasons behind
them are in [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained).

| Term | What it means |
|---|---|
| **Segment** | A part of a list selected by rules, such as "clicked in the last 90 days" or "country is Germany". An email sent to a segment reaches only the contacts who match. See [How to build a subscriber segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment). |
| **Custom field** | An extra piece of information stored for each contact, such as a company, a city or a birthday. You can build segments from it and insert it into an email with a merge tag (section 3). See [How to add custom fields](https://acellesend.com/kb/articles/how-to-add-custom-fields-to-your-list). |
| **Tag** | A label that you attach to contacts to group them, for example "webinar" or "customer". It is not the same thing as a merge tag. |
| **Import** | Adding contacts to a list from a file, usually a CSV file. See [Importing Contacts CSV](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping). |
| **Bought list** (purchased or rented list) | Addresses obtained from a third party. The people on such a list did not ask to hear from you, and both Google and Yahoo tell senders not to use one. |
| **List hygiene** (list cleaning) | The routine that keeps a list accurate: confirming new addresses, removing hard bounces, keeping unsubscribes and complaints out, and retiring contacts who have gone silent. |
| **Email verification** (list validation) | A check, carried out by a verification service, of whether each address on a list has a working mailbox. Nothing is sent to the address. A good result shows that the mailbox exists, not that its owner asked for your mail. See [How to verify your email list before you send](https://acellesend.com/kb/articles/how-to-verify-your-email-list-before-you-send). |
| **Role address** | An address that belongs to a function and not to one person, such as `info@` or `sales@`. Several people may read it, and none of them necessarily signed up. |
| **Disposable address** | An address from a temporary-inbox service, made to be used once and then abandoned. |
| **Catch-all domain** | A domain whose mail server accepts mail for every address, whether or not a mailbox exists behind it. A verification service cannot tell a real address from an invented one at such a domain. |
| **Spam trap** | An address that no person reads, run by a mailbox provider or a blocklist operator to identify senders with poor list habits. The three kinds are typo traps, recycled traps and pristine traps. The operators do not reveal which addresses are traps. |
| **Inactive contact** | A contact who has not opened or clicked for a long time. How long counts as inactive depends on how often you send. |
| **Re-engagement email** (win-back) | An email that asks inactive contacts whether they still want to hear from you. See [Re-engagement Automation in AcelleMail](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers). |
| **Permission pass** (reconfirmation) | One email that asks everyone on an old or neglected list to confirm that they still want your mail. Only the people who confirm stay on the list. |
| **Sunset policy** | Your rule for when an inactive contact stops receiving mail, for example after six months without a click. |

## 3. Messages

These terms describe the emails themselves. The figure shows the parts of one marketing email, and the
table defines them together with the kinds of email you can send.

![The parts of a marketing email. In the inbox the reader sees the From name, the subject line and the preheader. After opening, they see the From address, a greeting filled in by a merge tag, the call to action and the footer with the postal address and the unsubscribe link. The List-Unsubscribe header, the tracking pixel and the envelope sender are not visible to the reader.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-glossary/01-parts-of-an-email.png)

| Term | What it means |
|---|---|
| **Campaign** | One email sent to a list or a segment at a time you choose, such as a newsletter or a promotion. |
| **Automation** (workflow, autoresponder) | One or more emails sent automatically when a contact does something or reaches a date, such as signing up or having a birthday. You build it once, and it then runs for every contact who qualifies. |
| **Welcome email** | The first email a new contact receives after joining a list. See [How to set up a welcome email automation](https://acellesend.com/kb/articles/set-up-a-welcome-email-automation). |
| **Marketing email** | An email that promotes a product, a service or the business itself. It is usually sent to many people at once. |
| **Transactional email** (service message) | An email sent because one person did something, such as a receipt or a password reset. The law and the mailbox providers treat it differently from marketing email. See [Transactional vs Marketing Email](https://acellesend.com/kb/articles/transactional-vs-marketing-email). |
| **Template** | A reusable design for an email. You choose one, replace its content and send. |
| **Header** | The lines at the top of every email that mail servers and mail apps read, such as From, To, Subject and Date. The reader sees only a few of them. |
| **From name and From address** | The sender name and the email address shown to the reader. The domain of the From address is the one that DMARC protects (section 5). |
| **Reply-To address** | The address that receives replies when it differs from the From address. |
| **Subject line** | The line of text shown in the inbox before an email is opened. |
| **Preheader** (preview text) | The short text that most inboxes show after the subject line. It is taken from the beginning of the email unless you set it yourself. See [Preheader Text in AcelleMail](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster). |
| **Merge tag** (personalisation tag) | A placeholder that is replaced with each contact's own data when the email is sent. A first-name tag, for example, becomes "Maria" in the copy sent to Maria. See [Using Merge Tags in AcelleMail](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags). |
| **Personalisation** | Adapting an email to each reader, from a first name in the greeting to content chosen from what you know about them. |
| **Call to action** (CTA) | The link or button that asks the reader to do the one thing the email is for. |
| **Plain-text version** | A text-only copy of an email that travels together with the designed version. Mail apps that cannot display the design show it instead. |
| **Footer** | The bottom part of a marketing email, which normally holds the unsubscribe link and the sender's postal address. |
| **List-Unsubscribe header** (one-click unsubscribe) | A hidden header that lets a mail app show its own Unsubscribe button. With the one-click version, the mail app sends the request itself and the reader does not have to open a web page ([RFC 8058](https://www.rfc-editor.org/rfc/rfc8058)). |
| **A/B test** (split test) | Sending two or more versions of an email to parts of a list to see which one performs better. The better version then goes to the rest of the list. See [A/B Testing Email Subject Lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates). |

## 4. Sending

An email passes through several systems between your screen and the reader's inbox. The figure names
them, and the table defines the terms that belong to this part of the journey.

![The systems an email passes through. Your email platform builds the message, the sending server transmits it over SMTP, the mailbox provider checks who it is from and accepts or refuses it, and an accepted message is filed in the reader's mailbox. Along the way the sending server looks up the MX record and the mailbox provider looks up SPF, DKIM and DMARC. A refused message returns as a bounce, and a spam report can return as a complaint.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-glossary/02-systems-an-email-passes-through.png)

| Term | What it means |
|---|---|
| **Email service provider** (ESP) | A company or a platform that sends email for you and gives you tools to manage lists and campaigns. Some people use the term for the sending service alone. |
| **Sending server** (sending service) | The system that transmits your messages. It can be your own mail server or a service such as Amazon SES, SendGrid or Mailgun. |
| **Mailbox provider** | The company that hosts the reader's inbox, such as Gmail, Outlook.com or Yahoo. It decides whether to accept your email and where to file it. |
| **SMTP** | Simple Mail Transfer Protocol, the standard that mail servers use to pass messages to one another ([RFC 5321](https://www.rfc-editor.org/rfc/rfc5321)). |
| **MTA** (mail transfer agent) | Mail server software that sends and receives email over SMTP, such as Postfix. |
| **API** | A way for one program to give instructions to another. Many sending services accept email through an API as well as over SMTP. |
| **IP address** | The numeric address of a server on the internet. Mailbox providers keep a reputation for each IP address that sends them mail. |
| **Dedicated IP** | A sending IP address that only you use, so its reputation reflects only your mail. |
| **Shared IP** | An IP address that a sending service uses for many senders at once. They share its reputation. See [Dedicated vs Shared Sending IP](https://acellesend.com/kb/articles/dedicated-vs-shared-ip-address). |
| **Sending domain** | The domain you send from, such as `news.example.com` in the address `hello@news.example.com`. |
| **Subdomain** | A domain that sits under your main domain, as `news.example.com` sits under `example.com`. Many senders use one subdomain for marketing email and another for transactional email. |
| **DNS** | The Domain Name System, the internet's directory of domains. The DNS records of your domain tell other servers where to deliver its mail and which servers may send on its behalf. |
| **MX record** | A DNS record that names the servers which accept mail for a domain. |
| **PTR record** (reverse DNS) | A DNS record that links an IP address back to a hostname. Gmail requires every sending IP address to have one. |
| **TLS** | Encryption for the connection between two mail servers, which keeps a message from being read on the way. Gmail requires senders to use it. |
| **Envelope sender** (Return-Path, bounce address) | The address that bounces are sent back to. The sending server gives it to the receiving server when it hands over the message, and it can differ from the From address the reader sees. See [Return-Path vs Envelope-Sender](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained). |
| **Queue** | The line of messages waiting to be sent. A large campaign leaves the queue gradually. |
| **Throttling** | Limiting how fast mail is sent, for example to a set number of messages per hour. Mailbox providers throttle as well: they temporarily refuse mail from a sender whose volume rises too fast. See [Email Throttling & Rate Limits](https://acellesend.com/kb/articles/email-throttling-and-rate-limits-explained). |
| **Sending limit** (quota) | The largest number of emails you may send in a given period, set by your sending service or your plan. |
| **Warmup** (warming up) | Raising the volume step by step on a new IP address or domain, so that mailbox providers can build a reputation for it. See [Domain Warmup](https://acellesend.com/kb/articles/domain-warmup). |
| **Bulk sender** | A sender of large volumes. Gmail applies extra rules to anyone who sends close to 5,000 messages or more a day to personal Gmail addresses. |

## 5. Authentication

Anyone can type your address into the From line of an email. Authentication is how a receiving server
checks that a message claiming to come from your domain really does. The setup is described in [How to
Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).

| Term | What it means |
|---|---|
| **Spoofing** | Sending email with a forged From address, so that it appears to come from someone else. |
| **Email authentication** | The checks a receiving server runs to confirm that a message comes from the domain it claims. The three standards are SPF, DKIM and DMARC. |
| **SPF** | Sender Policy Framework. A DNS record that lists the servers allowed to send mail for a domain. The receiving server checks the sending server against the record of the envelope sender's domain ([RFC 7208](https://www.rfc-editor.org/rfc/rfc7208)). |
| **DKIM** | DomainKeys Identified Mail. The sending server adds a digital signature to each message, and the receiving server checks it with a public key published in the DNS of the signing domain. A valid signature shows that the domain took responsibility for the message and that the signed parts were not changed on the way ([RFC 6376](https://www.rfc-editor.org/rfc/rfc6376)). |
| **DKIM selector** | A short name inside the DKIM signature that tells the receiving server which of the domain's keys to look up. |
| **DMARC** | A DNS record that tells receiving servers what to do with mail that fails authentication for your domain: nothing (`p=none`), treat it as suspicious, which usually means the spam folder (`p=quarantine`), or refuse it (`p=reject`). It also says where reports should be sent ([RFC 7489](https://www.rfc-editor.org/rfc/rfc7489)). |
| **Alignment** | The DMARC requirement that the domain which passed SPF or DKIM matches the domain in the From address the reader sees. |
| **DMARC report** | A summary that participating mailbox providers send, usually once a day, to the address named in your DMARC record. It lists the servers that sent mail as your domain and whether each one passed. |
| **BIMI** | A DNS record that lets supporting mailbox providers show your logo next to your mail. It needs a DMARC policy of `quarantine` or `reject`, and Gmail also requires a mark certificate. See [BIMI Setup](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail). |

## 6. Delivery

These terms describe what happens after a message leaves your sending server. [Email Deliverability
Explained](https://acellesend.com/kb/articles/email-deliverability-explained) covers them in depth.

| Term | What it means |
|---|---|
| **Delivered** | The receiving server accepted the message. This says nothing about the folder it was placed in. |
| **Bounce** | A message that the receiving server refused, together with the reason it gave. |
| **Hard bounce** | A permanent failure, for example because the address does not exist. You should stop sending to that address. |
| **Soft bounce** | A temporary failure, such as a full mailbox or a server that is briefly unavailable. A later attempt may succeed. See [Hard Bounce vs Soft Bounce](https://acellesend.com/kb/articles/understanding-email-bounce-types). |
| **Deferral** | A temporary refusal in which the receiving server asks the sender to try again later. The sending server retries by itself, and the message bounces only if the retries keep failing. |
| **Bounce code** | The number a receiving server gives when it refuses a message. Codes that begin with 5 mean a permanent failure, and codes that begin with 4 a temporary one. See [Decoding Bounce Messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages). |
| **Inbox placement** | Where a delivered message was filed: in the inbox, in a tab such as Promotions, or in the spam folder. |
| **Promotions tab** | A tab in Gmail's inbox for marketing mail. A message there is in the inbox, not in the spam folder. |
| **Spam filter** | The software a mailbox provider uses to decide whether a message goes to the inbox or to the spam folder. |
| **Deliverability** | Your ability to get mail into the inbox, as opposed to merely having it accepted. |
| **Sender reputation** | The trust that mailbox providers place in your sending IP addresses and domains, built from how your mail has been received over time. See [Sender Reputation Monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring). |
| **Engagement** | How recipients react to your mail: whether they open it, click, delete it unread or report it as spam. |
| **Complaint** (spam report) | A recipient pressed "Report spam" or "Junk" in their mail app. |
| **Feedback loop** (FBL) | A service that some mailbox providers run to report complaints back to the sender, so that the address can be suppressed. See [Feedback Loops (FBL)](https://acellesend.com/kb/articles/feedback-loops-fbl-setup). |
| **Postmaster Tools** | Google's free dashboard for senders. It shows your domain's spam rate, its authentication results and whether it meets Gmail's sender rules. See [Gmail Postmaster Tools Walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough). |
| **SNDS** | Smart Network Data Services, Microsoft's free dashboard for senders. It shows how Outlook.com sees your sending IP addresses. See [Microsoft SNDS Walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough). |
| **Blocklist** (DNSBL, blacklist) | A published list of IP addresses or domains known for sending spam. Many receiving servers consult such lists before they accept mail. |

## 7. Tracking and metrics

Every rate in a campaign report is one count divided by another. The formulas below are the common ones.
[Email Marketing Metrics Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained) covers their limits
and shows how AcelleMail counts.

| Term | What it means |
|---|---|
| **Tracking pixel** | A tiny image in an email. When it loads from the sender's server, an open is recorded. |
| **Click tracking** | Rewriting the links in an email so that each click first passes through a tracking address, which records the click and then forwards the reader. |
| **Unique and total** | A unique count includes each person once, however often they opened or clicked. A total count includes every repeat. Rates are normally based on unique counts. |
| **Sent** | The number of messages your sending server tried to deliver. |
| **Delivery rate** | Delivered ÷ sent. |
| **Bounce rate** | Bounced ÷ sent. |
| **Open rate** | Unique opens ÷ delivered. Some mail apps load images automatically and others block them, so the figure is an estimate. See [Open Rates + Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp). |
| **Click rate** (click-through rate, CTR) | Unique clicks ÷ delivered. |
| **Click-to-open rate** (CTOR) | Unique clicks ÷ unique opens. |
| **Unsubscribe rate** | Unsubscribes ÷ delivered. |
| **Complaint rate** (spam rate) | Complaints ÷ delivered. Gmail asks senders to stay below 0.1% and never to reach 0.3%. |
| **Conversion** | A recipient doing what the email asked, such as buying, booking or signing up. |
| **Conversion rate** | Conversions ÷ delivered, or conversions ÷ unique clicks. A conversion rate should always say which of the two it uses. |
| **Revenue per email** | Revenue from the campaign ÷ delivered. |
| **ROI** (return on investment) | (Revenue − cost) ÷ cost. See [Email Campaign ROI](https://acellesend.com/kb/articles/email-roi-calculation-measuring-campaign-profitability). |
| **List growth rate** | (New contacts − contacts lost) ÷ contacts at the start of the period. |
| **UTM parameters** | Short labels added to the end of a link, such as `utm_source` and `utm_campaign`, which tell your analytics tool which campaign a visitor came from. See [UTM Parameters in AcelleMail](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking). |
| **Apple Mail Privacy Protection** (MPP) | A feature of Apple Mail that downloads the images in an email when the message arrives, whether or not the person reads it. Each download is recorded as an open. |
| **Automatic opens and clicks** | Opens and clicks produced by software and not by a reader. Apple Mail Privacy Protection causes automatic opens. Security filters that follow the links in an email in order to check them, often called link scanners, cause automatic clicks. |
| **Benchmark** | A figure you compare your own results with. Your earlier campaigns are the most dependable benchmark, because other senders may count differently. |

## 8. Law and privacy

> **Not legal advice.** These are short descriptions of legal terms, not statements of what the law
> requires in your case. The official texts are linked from the two guides named below.

The rules themselves are explained in [Email Consent
Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained) and [GDPR for Email Marketing
Explained](https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained).

| Term | What it means |
|---|---|
| **CAN-SPAM Act** | The United States law on commercial email. It does not require consent, but every commercial message needs an honest subject line, a clear notice that it is an advertisement, the sender's postal address and a working opt-out, which must be honoured within 10 business days. See [CAN-SPAM Compliance in AcelleMail](https://acellesend.com/kb/articles/can-spam-compliance-checklist). |
| **GDPR** | The General Data Protection Regulation, the European Union's data protection law. It governs how you collect, use and keep the personal data on your list. |
| **ePrivacy Directive** | The EU rules that decide whether marketing email may be sent and when information may be stored on or read from a person's device. The device rule applies to tracking pixels and tracked links in email. Each country applies the directive through its national law, and the United Kingdom's version is called PECR. |
| **CASL** | Canada's Anti-Spam Legislation. A commercial message needs the recipient's express or implied consent, the sender's contact details and a way to unsubscribe. See [CASL Compliance in AcelleMail](https://acellesend.com/kb/articles/casl-canadian-anti-spam-requirements). |
| **Direct marketing** | The term used in EU and UK law for promotional messages directed at particular people. Marketing email is one form of it. |
| **Personal data** | Any information relating to an identified or identifiable person. On a mailing list, that includes the email address, the name and the record of opens and clicks. |
| **Data subject** | The person the data is about. On your list, that is each contact. |
| **Controller** | Under the GDPR, the organisation that decides why and how personal data is used. For your own list, that is you. |
| **Processor** | Under the GDPR, a service that handles personal data on behalf of the controller, such as a hosting provider or a sending service. |
| **Lawful basis** | The legal ground that the GDPR requires for each use of personal data. For marketing email it is usually consent or legitimate interests. |
| **Legitimate interests** | A lawful basis under the GDPR that depends on weighing your interest against the person's. It cannot replace consent where the ePrivacy rules require consent. |
| **Privacy notice** | The text that tells people who you are, what you do with their data, who receives it and how long you keep it. |
| **Rights request** (data subject request) | A request from a person to see their data (access), correct it (rectification), delete it (erasure) or receive a copy of it (portability), or to stop its use (objection). Under the GDPR you normally have one month to respond. |
| **Data processing agreement** (DPA) | The contract that the GDPR requires between a controller and each of its processors. |
| **Data breach** | Personal data being lost, stolen or disclosed to the wrong people. Under the GDPR, a breach must normally be reported to the data protection authority within 72 hours. |
| **Data protection authority** | The public body that enforces data protection law in a country, such as the ICO in the United Kingdom. |

## 9. Terms that are easy to confuse

Some of these terms sound alike, and some are used loosely. The table shows where each pair differs.

| The two terms | How they differ |
|---|---|
| **Delivered** and **inbox placement** | Delivered means that the receiving server accepted the message. Inbox placement is the folder it was put in afterwards. A campaign can be almost fully delivered and still land mostly in the spam folder. |
| **Hard bounce** and **soft bounce** | A hard bounce is permanent, so you stop sending to the address. A soft bounce is temporary, so a later email may get through. |
| **Unsubscribe** and **complaint** | Both mean that the person wants no more of your mail. An unsubscribe is a request made to you. A complaint is made to the mailbox provider, and it counts against your reputation. |
| **Suppression list** and **blocklist** | A suppression list is yours: the addresses you will not send to. A blocklist belongs to someone else: the senders whose mail receiving servers refuse. Both are sometimes called a blacklist, which is how they come to be confused. |
| **From address** and **envelope sender** | The From address is what the reader sees. The envelope sender is what the mail servers use for bounces. SPF checks the envelope sender, while DMARC protects the From address. |
| **Click rate** and **click-to-open rate** | Both count unique clicks. The click rate divides them by delivered emails, and the click-to-open rate by unique opens, so the second is normally the higher figure. |
| **Email verification** and **double opt-in** | Verification checks that a mailbox exists. Double opt-in is good evidence that the owner of the mailbox asked to join. A verified address can still belong to someone who never signed up. |
| **List** and **segment** | A list is a separate set of contacts. A segment is a filtered view of one list, and its membership changes as the contacts change. |
| **Campaign** and **automation** | You decide when a campaign goes out, and everyone receives it at about the same time. An automation sends to each contact at their own moment, when they do something or reach a date. |
| **Sending service** and **mailbox provider** | The sending service transmits your mail. The mailbox provider receives it on behalf of the reader. |
| **Tag** and **merge tag** | A tag is a label on a contact. A merge tag is a placeholder in an email. |

## 10. These terms in AcelleMail

AcelleMail uses most of these terms as they are defined here. A few have a different name in the app:

| In this glossary | In AcelleMail |
|---|---|
| Contact | A subscriber. You find them under **Audience → Subscribers**, or under **Subscribers → View all** in a list. |
| Campaign | An email campaign, under **Campaigns → Email**. |
| Custom field | A list field, added under **Manage list fields** in a list. |
| Suppression list | The blacklist, under **Sending → Blacklist**. |
| Complaint | The contact status **Spam reported**. A campaign lists its complaints under **Sending logs → Feedback log**. |
| Throttling | The **Sending limit** of a sending server, in its **Configuration** card. |
| Feedback loop | A **Feedback loop handler**, which is a mailbox that AcelleMail reads for complaint reports. Bounces have a **Bounce handler** of the same kind. Both are chosen in a sending server's **Configuration** card. |
| Open and click tracking | **Track opens** and **Track clicks**, under **Advanced Settings** in a campaign's Setup step. |
| One-click unsubscribe | **One-Click Unsubscribe Headers**, an admin setting under **Settings → General**. |

### On a list

Open **Audience → Lists**, open a list and choose **Subscribers → View all**. Four of the terms from
sections 1 and 2 are on this screen:

![A list's Subscribers tab in AcelleMail, with four areas marked: 1 the Segments tab, 2 the Manage list fields tab, 3 the Status column with the values Unconfirmed, Subscribed and Blacklisted, 4 the Verification column with the values Deliverable, Unknown, Undeliverable and Risky.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-glossary/03-list-subscribers.png)

1. **Segments** holds the segments of this list.
2. **Manage list fields** is where you add custom fields.
3. **Status** shows where each contact stands. **Unconfirmed** means that a double opt-in confirmation
   is still pending, **Subscribed** that the contact receives campaigns, **Unsubscribed** that they
   opted out, and **Blacklisted** that the address has been blocked, either by you or after a hard
   bounce. A contact whose complaint was reported by your sending service is marked **Spam reported**.
4. **Verification** shows the result of email verification: **Deliverable**, **Undeliverable**,
   **Risky** or **Unknown**. A dash means that the address has not been checked yet.

Campaigns and automations are sent only to contacts whose status is **Subscribed**.

### In a campaign

The last step of a campaign, **Confirm**, shows how the email will appear in an inbox. Four of the terms
from section 3 are in that preview:

![The "How it lands in the inbox" preview on a campaign's Confirm step, with four lines marked: 1 the From name, 2 the From address, 3 the subject line, 4 the preheader.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-glossary/04-inbox-preview.png)

1. The From name comes from **From name** in the Setup step.
2. The From address comes from **From email** in the same step.
3. The subject line comes from **Email subject**, also in the Setup step.
4. The preheader comes from **Preheader text** in the Template step.

Merge tags are written between double curly brackets. In the Setup step, type `{{` in **Email subject**
(1) and AcelleMail lists the merge tags you can use (2). Keep typing to narrow the list, then choose
one:

![The Email subject field on a campaign's Setup step with "{{fir" typed at the end of the subject, marked 1, and the menu of matching merge tags that opens under it, marked 2.](https://acellesend.com/plugins/acelle/landing/images/help/kb/email-marketing-glossary/05-merge-tags.png)

Choosing **First name** adds `{{first_name}}` to the subject, and each contact then sees their own first
name in its place. The entries described as "Custom list field" are the custom fields of the list you
are sending to.

## Where to read more

The other guides in [Domain Knowledge](https://acellesend.com/kb/category/domain-knowledge) explain these terms in context:

- [How Email Marketing Works](https://acellesend.com/kb/articles/how-email-marketing-works) follows one email from signup to
  sale.
- [Email Consent Explained](https://acellesend.com/kb/articles/email-consent-and-opt-in-explained) covers permission and the
  rules in the United States, the EU, the UK and Canada.
- [Email List Quality Explained](https://acellesend.com/kb/articles/email-list-quality-explained) covers list sources, spam
  traps and list hygiene.
- [Email Deliverability Explained](https://acellesend.com/kb/articles/email-deliverability-explained) covers authentication,
  reputation and the sender rules of Gmail, Yahoo and Microsoft.
- [Email Marketing Metrics Explained](https://acellesend.com/kb/articles/email-marketing-metrics-explained) covers the
  formulas and what can distort them.
- [GDPR for Email Marketing Explained](https://acellesend.com/kb/articles/gdpr-for-email-marketing-explained) covers the EU's
  data protection rules.
- [Transactional vs Marketing Email](https://acellesend.com/kb/articles/transactional-vs-marketing-email) covers the two kinds
  of email and why they are sent separately.

---

# Verify your list with Clearout

> Connect your Clearout account once, then clean any list from its Email verification tab — and know what the four verdicts Clearout sends back actually mean.

- Canonical URL: https://acellesend.com/kb/articles/verify-your-list-with-clearout
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: intermediate
- Tags: Email Verification, List Cleaning, Bounce Handling
- Published: 2026-09-29 · Last updated: 2026-10-06
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Clearout checks whether an address can actually receive mail, and hands back a safe-to-send verdict for each one. You connect it once, then clean any list from that list's own **Email verification** tab. Budget about ten minutes for the setup.

## Before you start

- **A Clearout account.** Sign up at [clearout.io](https://clearout.io?ref=m2qzmgn) — a new account starts with 100 free credits and no card, which is enough to verify a small list end to end.
- **Access to the Verification Servers screen.** The screenshots below are the admin area (**Verification Servers**, under **Sending**). Customers whose plan lets them bring their own service add it at **Sending → Verification servers** instead, where the same three fields are labelled *Verification service*, *Max verifications* and *Per time base*.
- **A queue worker covering the `high` queue.** Clearout verifies a whole list in a single upload, and that run is queued as high priority. A worker started with `--queue=high,default` covers it.

## Step 1 — Add Clearout as a verification server

Open **Verification Servers** and click **Add server** (top right). The **Add verification server** popup lists every service that ships with a driver — type `clear` in the search box to narrow it to one.

![The Add verification server popup filtered to the Clearout card, with Free to start and Bulk lists badges](https://acellesend.com/plugins/acelle/landing/images/help/kb/verify-your-list-with-clearout/01-add-verification-server.png)

Two badges on the card matter. **Free to start** is the free credits. **Bulk lists** means Clearout takes your whole list in one upload rather than one address per API call — that is why a large list finishes in one run instead of trickling through. **Try for free**, **Buy now** and **API docs** open Clearout's own pages in a new tab.

Click **Select**.

## Step 2 — Create an API token in Clearout

Leave the popup open and sign in to Clearout in another tab. Go to **Developer → API**, the page headed **Manage your API Tokens**, and click **+ Create API Token**.

![Clearout's Developer → API page listing an API token with its Status toggle on](https://acellesend.com/plugins/acelle/landing/images/help/kb/verify-your-list-with-clearout/02-clearout-api-token.png)

Name the token something you will recognise in six months (the one above is *Test AI*), then copy it. Two things to watch:

- **Copy the whole string.** A Clearout token is one long value with a colon in the middle. Both halves are part of it.
- **Leave the Status toggle on.** A disabled or expired token is not rejected when you save it in AcelleMail — it fails later, when a run calls Clearout.

## Step 3 — Paste the token and set a credit limit

Back in AcelleMail, the form now shows the fields Clearout needs.

![The Clearout server form: Server name, API Token, and Credit limit of 100 per 1 hour](https://acellesend.com/plugins/acelle/landing/images/help/kb/verify-your-list-with-clearout/03-configure-clearout-server.png)

- **Server name** — a descriptive name to identify this server. It is what you pick from the dropdown in Step 5.
- **API Token** — paste the token you just copied. It is a password field, and nothing calls Clearout when you save, so a wrong token stays invisible until a run uses it.
- **Credit limit**, **Per**, **Time unit** — a rate limit on the API calls this server makes, for example `100` per `1` `hour`. It exists to protect your Clearout credits from a runaway run on a big list.

Clearout throttles on its own side too — the free plan allows three instant checks a minute. A run that hits either limit waits and retries rather than failing.

Click **Save server**.

## Step 4 — Open the list you want to clean

Go to **Lists**, open the list, and click the **Email verification** tab.

![A list's Overview screen with the Email verification tab in the tab bar](https://acellesend.com/plugins/acelle/landing/images/help/kb/verify-your-list-with-clearout/04-open-email-verification-tab.png)

## Step 5 — Pick the Clearout server and start the run

The four cards at the top tell you where you stand before you spend a credit: **Total subscribers**, **Verified**, **Unverified**, and **Credits available**.

![Verification controls with the Clearout server selected and Start verification ready](https://acellesend.com/plugins/acelle/landing/images/help/kb/verify-your-list-with-clearout/05-choose-server-and-start.png)

**Credits available** is your AcelleMail plan's verification allowance, not your Clearout balance — it reads *Not included* when your plan grants none, which is normal when you connected your own Clearout account. The balance that a run actually draws on is the one in your Clearout dashboard.

Under **Verification controls**, choose your Clearout server in **Select verification server**, then click **Start verification**. The run covers every contact that has never been checked; contacts with a verdict already are skipped. To check those again, go to the **Subscribers** tab, filter or tick the ones you want, and start the run from the **Actions** menu there.

Progress appears in the **Verification runs** table below the controls. Because Clearout takes the list as one upload, a run sits at *Queued* / *Running* while Clearout works through the file, then fills in the verdicts at the end — a quiet progress bar is not a stuck one.

## Step 6 — Read the verdicts

Open the **Subscribers** tab. Every row now carries a **Verification** badge, and the toolbar filter narrows the table to one verdict at a time.

![The subscribers table filtered to Risky, with a Verification badge on each row](https://acellesend.com/plugins/acelle/landing/images/help/kb/verify-your-list-with-clearout/06-verification-results.png)

| Badge | What Clearout found | What to do |
|---|---|---|
| **Deliverable** | Safe to send. | Mail them. |
| **Risky** | A role address (`info@`, `support@`), a catch-all domain that accepts everything, a gibberish local part, or a known spam trap. | Hold them back from your main sends. |
| **Undeliverable** | The mailbox or the domain does not exist — or it is a disposable address. | Remove them. |
| **Unknown** | No definite answer, or the address sits on your Clearout allow/block list, which Clearout skips instead of checking. | Re-check later. |

The one that surprises people: a disposable address (`mailinator.com` and its cousins) comes back **Undeliverable** even though the mailbox exists right now. That is Clearout's send verdict rather than a syntax check, and for a marketing list it is the answer you want.

## Common issues

| What you see | What to do |
|---|---|
| *Clearout: missing api_key in server options* | The server was saved without a token. Open **Verification Servers → Edit**, paste it again, and check for a trailing space. It appears when a run starts, never when you save. |
| The run fails with a message quoting Clearout (*List is not available*, a daily limit) | Clearout refused the job — usually credits exhausted or a plan limit. Check the balance in the Clearout dashboard, then start the run again. |
| *No verification server available. Please contact your administrator.* | The account has no verification server of its own and the plan grants none. Add one as admin, or enable email verification on the customer's plan. |
| The run stays at 0% and nothing moves | No worker is consuming the `high` queue. Start one with `--queue=high,default`. |
| **Credits available** reads *Not included* | That card is your plan's allowance, not Clearout's. A run on your own Clearout server is paid for with Clearout credits. |

## What to do after

- Work through the buckets — filter to **Undeliverable** on the Subscribers tab, select all, and delete or blacklist them. The step-by-step is in [How to verify your email list before you send](https://acellesend.com/kb/articles/how-to-verify-your-email-list-before-you-send).
- Re-verify after every import and every few months for quiet lists: [Email list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability).
- If bounces are what brought you here, [understanding bounce types](https://acellesend.com/kb/articles/understanding-email-bounce-types) tells you which ones verification can prevent and which it cannot.

---

# Acelle AI credits explained: what they are and how a prompt gets spent

> AI credits are the fuel for AcelleMail's AI tasks — generating an email template, building a campaign from a prompt, rewriting copy. Here is which support plans include them, how a single task turns into spend, and what the Acelle Hub is doing on your behalf to keep that spend low.

- Canonical URL: https://acellesend.com/kb/articles/acelle-ai-credits-explained
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: reference · Difficulty: beginner
- Tags: Email Templates, Automation, Acellemail
- Published: 2026-07-18 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What AI credits are

**AI credits are the fuel for AcelleMail's AI tasks.** Any time you ask the AI to *do* something — generate an email template, spin up a campaign from a short prompt, rewrite a subject line, or turn a plain-English description into a segment — that work runs on credits.

A couple of concrete examples of what "an AI task" means in practice:

- **Generate an email template.** Describe the email you want and get a ready-to-edit template back, instead of starting from a blank canvas.
- **Build a campaign from a prompt.** Hand the AI a one-line brief ("a re-engagement email to subscribers who haven't opened in 90 days, friendly tone") and it drafts the campaign for you to review.
- **Rewrite or improve copy.** The ✨ sparkle button next to any rich-text field improves, shortens, expands, or fixes the tone of what you've written.

One thing worth knowing up front: **AI in AcelleMail is a plugin, not part of core.** AcelleMail core ships with no built-in AI. You add the optional Acelle AI plugin, and *that* is what surfaces the sparkle button, the agent chatbox, and the coach panels throughout the app. AI credits are what those surfaces draw down when they run a task.

## Which plans include AI credits

AI credits come bundled with the **support plans**. There are four tiers, and the amount of AI credit steps up as you go:

| Support plan | AI credits |
|---|---|
| **Essential** | None — Essential is support, updates, and install help only |
| **Plus** | ✅ AI credits included |
| **Pro** | ✅ Extended AI credits |
| **Max** | ✅ Maximum AI credits included |

So the rule of thumb is simple: **AI credits are available from Plus and above.** If you're on Essential and want to use the AI tasks, moving up to Plus is what switches them on. Each higher tier raises the per-cycle allotment — Plus is the entry point, Pro gives you an extended bucket for teams running campaigns weekly, and Max carries the largest bucket for long-term, high-volume use.

Your AI credit is an **entitlement on the plan itself** — it refreshes each billing cycle. You don't buy credits as a separate SKU to make the AI work; picking a plan from Plus upward is what grants them.

## How a task turns into spend

Every time you run one of those AI tasks, it consumes a **certain amount of credit — and the amount scales with the size of the task.** A small job costs a little; a big one costs more:

- A single sparkle rewrite or a short chatbox question is a **small** amount of credit.
- A long, multi-step job — say, an agent that reads your campaign, fans out to several tools, and assembles a full flow — costs **more**, because there's simply more work happening under the hood.

Here's what happens when you press the button. The AI surface **takes your input** — your prompt, plus the relevant context from the screen you're on — and **pushes it up to the Acelle Hub**, a service endpoint whose whole job is to process AI tasks. The Hub does the heavy lifting and sends the result back, and the credit is drawn from your current cycle's balance.

Two details that matter in day-to-day use:

- **You only pay for work that lands.** If a task fails partway — a timeout, an engine error — it doesn't burn your balance. The credit is spent on a successful result, not on the attempt.
- **You can watch it in real time.** The admin AI usage dashboard shows per-task spend as it happens, so the balance is never a black box.

## What the Acelle Hub is actually doing

This is the part that makes the credits go further than they look.

**Acelle isn't running its own AI model.** Under the hood, the Acelle Hub is a **proxy** that connects to the strongest AI engines available — the same Claude and GPT-class models you'd reach for directly. You're getting frontier-model quality; Acelle isn't asking you to settle for something weaker.

But the Hub is more than a pass-through. Sitting in front of those engines, it does three things on every request:

1. **It holds AcelleMail's knowledge base.** The Hub is where the system's own knowledge about AcelleMail lives — how campaigns, lists, templates, and deliverability actually work in *this* product. That context is added for you, so the engine answers as an AcelleMail expert rather than a general-purpose chatbot.
2. **It optimizes the input size and context of each request.** Instead of forwarding a bloated prompt, the Hub trims and shapes what gets sent — enough context to get a great answer, not a byte more. Smaller, sharper requests cost less to run.
3. **It queries Acelle's built-in libraries to improve the outcome.** Where a ready-made building block already exists in Acelle, the Hub reaches for it instead of asking the engine to reinvent it — better results, less model work.

The combined effect: in our own testing, routing a task through the Acelle Hub comes out **roughly 3× to 8× cheaper** than sending the same job straight to Claude or GPT yourself. That figure is a **first-party, relative measurement** — it varies by task and is a ballpark, not a guarantee — but the direction is consistent, and it's the reason a plan's credit bucket stretches across far more real work than the raw model price would suggest.

## Why this is a good deal

Put the three pieces together and here's what you're actually getting when you spend an Acelle AI credit:

- You're running **on top of the strongest AI engines** — not a weaker in-house substitute.
- Your request arrives **pre-loaded with AcelleMail context and trimmed to size**, so the answer is sharper and the run is leaner.
- And you're doing it at a **fraction of the cost** of wiring up a raw model API and paying per token yourself.

Strongest engine, optimized context, minimal cost — that's the whole point of the credit model.

## Common questions

**Do I need my own OpenAI or Anthropic key?**
No. Using Acelle AI credits means the Acelle Hub handles the engine connection for you — no keys, no separate model bill, no balance to top up somewhere else. (If you *prefer* to run your own key, the AI plugin supports that too, but then you're managing that engine and its cost yourself, not spending credits.)

**What happens when my AI credits run out for the cycle?**
The AI tasks stop until your balance refreshes at the next billing cycle, or until you move to a higher tier with a larger allotment. Your other AcelleMail features are unaffected — only the AI surfaces draw on this balance.

**Does Essential include any AI credits?**
No. Essential covers priority support, install/upgrade help, and product updates. AI credits start at Plus.

**Is my data used to train the AI?**
No. Prompts and responses are used to generate your result and are logged in your own AcelleMail database for the usage dashboard — they aren't used to train models. See the AI plugin page and your privacy policy for the specifics.

## Related articles

- [AI-assisted email copy without sounding "AI"](https://acellesend.com/kb/articles/ai-assisted-email-copy-without-sounding-ai)
- [Regular vs Extended License: which AcelleMail license do you need?](https://acellesend.com/kb/articles/regular-vs-extended-license-for-acellemail)

---

# Regular vs Extended License: which AcelleMail license do you need?

> Envato issues two Standard licenses for AcelleMail: Regular and Extended. One question decides which you need — do your end users pay? Here is the rule, what "one end product" actually means, and where your purchase code goes in the app.

- Canonical URL: https://acellesend.com/kb/articles/regular-vs-extended-license-for-acellemail
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: reference · Difficulty: beginner
- Tags: Acellemail, Self Hosted, Codecanyon
- Published: 2026-07-15 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

You bought AcelleMail on CodeCanyon and you want to know which licence covers what you're building — and what that purchase code actually does once it's in the app. This page answers both.

One thing to be clear about up front: **the licence is Envato's, not ours.** AcelleMail is sold through CodeCanyon, so the licence you hold is an Envato licence and Envato's terms govern it. Everything below quotes Envato's own pages and links to them. Where Envato routes a question to its Help Team rather than publishing an answer, this page says so instead of guessing.

## The two Standard licenses

Envato issues two *Standard* licences for code items like AcelleMail. Both grant you "an ongoing, non-exclusive, worldwide license to make use of the digital work (Item)". The features are identical — **you are not buying fewer features with Regular.**

| | Regular License | Extended License |
|---|---|---|
| Features | All | All |
| Scope | One single End Product | One single End Product |
| Your end users | Are **not** charged | **May** be charged |

Envato's own one-line summary:

> The main difference is that under the Regular License, your end product is distributed for free, whereas under the Extended License your end product may be sold.

Worth knowing: Regular and Extended are the two licences in Envato's **Standard** category — the pair that applies to code items. Envato lists other categories (Music, Sound Effects, Video, Photo, Specialty) that don't apply here.

## Which one do you need?

One question decides it: **do your end users pay?**

> If the end users need to pay to see the end product, you need an Extended License.

The catch is that "Free" has a precise meaning in the Regular License, and it explicitly covers subscriptions:

> No fee is paid by the end user to access the End Product. The End Product is not sold.

> No fee is paid to subscribe to a service that includes the End Product (eg a website subscription fee).

That second clause is the one people miss. A subscription that includes access to your AcelleMail install **is** a fee — so it isn't Free, and Regular doesn't cover it. It doesn't matter that they're paying for "the subscription" rather than "the software".

| What you're doing | Licence |
|---|---|
| Sending campaigns for your own company | Regular |
| Running an install for one client; nobody is charged to use it | Regular |
| Charging customers for accounts on your AcelleMail (SaaS mode) | Extended |

### Extended is not a SaaS free pass

Extended lets you charge for **one** end product. It does not let your users spin up their own copies:

> No. The Extended License only allows for a single end product. By allowing your users to download their own customized versions, you're allowing the item to be used in multiple end products.

Envato rules out made-to-order and on-demand copies too — even a multi-use licence doesn't cover letting each customer generate their own customised product. That would need a separate single-use licence per product created.

So: Extended covers *your* SaaS. It doesn't cover a business whose product is handing out AcelleMail instances.

## What "one end product" actually means

Both licences are limited to one End Product, so the term carries a lot of weight:

> The item is what you purchase from Envato Market. The end product is what you build with that item.

The unit is the **End Product** — not the domain, and not the installation. Envato is blunt that Extended isn't a multi-domain or developer licence:

> If I purchase an Extended License, do I get a multi-use, multi-domain, multi-client or developer license? No. The Extended License is still limited to a single end product, but you can re-sell that product.

And an End Product can legitimately be copied:

> A single application means one unique end product (which can be copied and made available to end users of that product).

This is why "one licence = one server" is a rule of thumb people repeat, but **it isn't what the terms say** — the limit is conceptual, not an install counter. Whether two domains are one End Product or two depends on whether they're genuinely the same product, and that judgement is one Envato routes to its Help Team rather than publishing a formula for.

## Where your purchase code goes in AcelleMail

**During install, it's optional.** The site-config step has a *License Key (Optional)* field, and says so directly: "You can update this later in the settings page." Leave it blank and the install still completes.

**Afterwards it lives in Settings → License:**

- **Purchase Code** — paste the code and save.
- **Refresh** — re-checks your code. Use it when the page shows stale details, typically after renewing support.
- **Supported until \<date\>** — your item support window (see below), not a licence expiry.

**Verification is a network call.** The app checks your code against a licence-verification endpoint over HTTPS. This trips up more fresh installs than a bad code does: a server with no outbound HTTPS will fail verification even with a perfectly valid purchase code. If your egress is firewalled, or you're on Rocky/RHEL with SELinux blocking outbound connections, fix that first before assuming the code is wrong.

## Item support is not the license

These are two different things, governed by two different Envato documents. Conflating them causes a lot of needless worry.

**Item support is time-boxed.** Envato's Item Support Policy: "A supported item includes item support for 6 months from the purchase date," and at purchase you can extend it "up to a maximum of 12 months from the date of purchase." It covers questions about the item's features and functionality — not modifying it beyond its original features, and not installing it on your server.

**The licence is not.** Both Regular and Extended grant an **ongoing** licence, in the licence terms' own word. The Item Support Policy says nothing about the licence ending when support does, and support is nowhere made a condition of the licence grant — they're separate documents doing separate jobs.

So when Settings → License shows *Supported until* a date that has passed, that's your **support window** closing, not your licence. Envato also describes items as royalty free:

> You just need to pay for rights to use the item once per end product. You don't need to pay additional or ongoing fees for each person who sees or uses it.

## Common issues

| Issue | Cause / fix |
|---|---|
| Verification fails on a fresh install | The server can't reach the verification endpoint. Check egress firewall rules; on Rocky/RHEL, SELinux blocks outbound HTTP until `httpd_can_network_connect` is enabled. The code is usually fine — the network isn't. |
| Left License Key blank during install | Expected. The field is optional by design; add the code later in Settings → License. |
| Licence details look stale after renewing support | Hit **Refresh** on the License page to re-check. |
| You need to exceed your licence's limits | E.g. you hold Regular and are about to start charging. Contact the Envato Help Team with your Envato username/email and the item name. Envato handles this case by case and publishes no self-serve upgrade path — so don't assume you can simply pay a difference. |

## What to do next

- **On Extended and running SaaS mode?** You're licensed for one product you charge for — not for letting customers spin up their own instances. Re-read the Q21 quote above if that's your model.
- **On Regular and about to start charging?** Talk to the Envato Help Team *before* you flip the switch, not after.
- **Not sure your code is registered?** Settings → License shows the licence type, the buyer, and the support window at a glance.

## Sources

All quotes on this page come from Envato's own pages:

- [Regular License terms](https://codecanyon.net/licenses/terms/regular)
- [Extended License terms](https://codecanyon.net/licenses/terms/extended)
- [Envato Market License FAQ](https://codecanyon.net/licenses/faq)
- [Item Support Policy](https://codecanyon.net/page/item_support_policy)

Envato's terms are the authority on licensing questions and can change; if this page and Envato disagree, Envato wins.

## Related articles

- [Install AcelleMail on Ubuntu 24.04](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404)
- [REST API authentication and endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints)

---

# Sending Servers for a Plan: Shared Pool or Bring-Your-Own

> Every plan in AcelleMail answers one question before a customer on it can send a single email: whose sending server moves the message? You — the admin — decide that on the plan's Sending Servers tab. There are exactly two answers, and a customer on a given plan only ever sees one of them in their UI. This guide walks both options end-to-end with screenshots from the admin and the customer side, so you can pick the right mode for each plan you sell and know exactly what your customer will see after they subscribe.

- Canonical URL: https://acellesend.com/kb/articles/sending-server-for-plan
- Author: Martin Keller, SaaS & Business Editor
- Category: SaaS & Multi-tenant — White-label, subscription plans, billing, tenant management, branding
- Content type: guide · Difficulty: beginner
- Tags: Amazon SES, Sendgrid, Mailgun, Smtp, White Label, Multi Tenant, Subscription Billing, Pricing Plans, Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Every plan in AcelleMail has to answer one question before a customer on it can send a single email: **whose sending server moves the message?** You — the admin — decide that on the plan's **Sending Servers** tab. There are exactly two answers, and a customer on a given plan only ever sees one of them in their UI.

This guide walks both options end-to-end, so you can pick the right one for each plan you sell and know what your customer will see after they subscribe.

## The two delivery modes

| | **System's Sending Servers** | **Own Sending Servers** |
|---|---|---|
| Who provides the SMTP / API credentials? | You (the admin) | The customer |
| Where customers add servers? | They don't — invisible to them | Their own **Sending → Sending servers** menu |
| Best for | Beginner / managed plans, free tier, no-tech-setup buyers | Power users, agencies, bring-your-own-IP plans |
| Server pool | One shared pool across every customer on the plan | Each customer has their own pool, isolated from other customers |
| Quota of servers per customer | N/A (always the system pool) | Capped by the **Maximum Sending Servers** quota you set on the plan |

Both modes coexist on the same install — you can have a Free plan in System mode and a Pro plan in Own mode, side by side. The mode is a per-plan setting, not a global setting.

## Where to configure this

1. Sign in to the admin area at `/admin/login`.
2. Open **Plans** in the left nav.
3. Pick the plan you want to configure, then open the **Sending Servers** sub-tab in the plan's sidebar.

![Admin Plans index — the list of subscription plans you can edit, each with a status, price, sending-credit cap, and Edit action](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/01-admin-plans-index.png)

The **Sending Servers** tab is the only place where the delivery mode toggle lives. The mode you pick here writes the `HAS_OWN_SENDING_SERVER` entitlement under the hood — every customer subscription against this plan inherits it the moment you click **Save Changes**.

---

## Method 1 — System's Sending Servers (the "no setup" path for customers)

Pick this when you want your customers to just hit **Send** and have AcelleMail handle delivery. They never see an SMTP host, never paste an API key, never know what an MTA is.

### How it works

You attach one or more **admin-owned** sending servers to the plan. Every customer subscribed to the plan shares that pool. You control the credentials, the IP reputation, and the warm-up — they only see "campaign sent."

### Walkthrough

On the plan's **Sending Servers** tab, select the **System's Sending Servers** card (it's the default on every newly created plan) and click **Save Changes**.

![Sending Servers tab on the Starter plan — System's Sending Servers card selected, with a System Sending Servers panel showing one attached server (Local Sendmail) marked Primary at 100% traffic, plus a + Add Server button on the right](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/02-plan-sending-servers-system-mode.png)

Once saved, the **System Sending Servers** panel appears underneath. It lists every server already attached to the plan with its name, type, primary flag, and the share of traffic it gets.

To attach a new one, click **+ Add Server**:

![Add Sending Server popup over the Sending Servers tab — a Select Server dropdown lets the admin pick from the pool of system-owned, active sending servers that aren't already attached to this plan. The popup notes: "The first server added will be set as primary."](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/04-add-system-server-popup.png)

A few rules to know:

- **Only system (admin-owned) servers are pickable.** Customer-owned servers don't appear because a plan is shared — you can't bind one customer's private SMTP to other people on the same plan.
- **The first server you attach becomes Primary** automatically. Add a second to enable load-balancing across both.
- **Two or more servers → a "Load balance" button appears** next to **+ Add Server**. That's where you set the per-server **traffic %** so you can split sends 70/30, 50/50, or any ratio you like.
- **Customers see nothing.** Their **Sending → Sending servers** menu is hidden by the `has_own_sending_server` entitlement gate — they can't add a server, can't see your credentials, and don't need to.

### When to pick this mode

- Free tier — you want zero-friction signup, send-the-first-email-in-30-seconds.
- "Managed" tiers where deliverability is your value-add.
- Any plan where the customer would not realistically own SMTP credentials (e.g. small business / non-technical).

---

## Method 2 — Own Sending Servers (customer brings their own)

Pick this when your customer is technical, has their own IPs / domains / vendor accounts, and wants AcelleMail as a pure sending engine on top of *their* infrastructure.

### How it works

You enable Own mode on the plan and toggle which **providers** they're allowed to use (Amazon SES, SendGrid, Mailgun, raw SMTP, etc.). You also cap **how many** servers any one customer can add. The customer then sees a **Sending servers** menu in their own UI and adds the credentials themselves — you never see them.

### Walkthrough

On the plan's **Sending Servers** tab, select the **Own Sending Servers** card. The form expands to show two new sections:

![Sending Servers tab on the Growth plan — Own Sending Servers card selected. An Allowed Providers grid shows 9 toggles all enabled (Amazon SES, Elastic Email, Generic SMTP, SendGrid, SparkPost, Sendmail, Mailgun, Blastengine, Gmail OAuth). Below, a Maximum Sending Servers field shows 5 with an Unlimited toggle next to it.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/03-plan-sending-servers-own-mode.png)

**Allowed Providers.** Each toggle here corresponds to one server type the customer can add — Amazon SES, SendGrid, Mailgun, Elastic Email, SparkPost, Blastengine, Generic SMTP, Sendmail (local MTA), and Gmail (OAuth). Turn off the ones you don't want them touching. A common pattern: enable only **Amazon SES** and **SMTP** on a starter "Own" plan, then unlock the rest on a higher tier.

**Maximum Sending Servers.** A hard cap on how many servers each customer can add. Set an integer (e.g. `5`) or flip the **Unlimited** switch. This is the `MAX_SENDING_SERVERS` quota — leave it at a low number on entry-level Own plans, raise it for agency tiers.

Click **Save Changes** when both sections look right. The customer subscription doesn't change until they reload — but the next time they hit their dashboard, the new menu is there.

### What your customer sees after subscribing

They sign in to the customer side at `/login` and a new **Sending servers** entry shows up under the **Sending** menu in their sidebar. The page is headed **Sending Servers — Manage SMTP and API servers that power your email delivery**, with a sidebar that surfaces their vendor mix, combined throughput, warmup status, and a 3-step setup checklist:

![Customer-side Sending Servers index — one row in the table for the customer's own SMTP server (Awesome Solutions SMTP 1) with sending-limit, status Active, created date. Right sidebar shows a donut chart of vendor mix (1 SMTP server, 1 vendor), a combined-throughput card (5,000/hour), warmup status, available services, and a Setup checklist with Pick a service / Connect credentials / Send a test.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/05-customer-sending-servers-index.png)

They click **+ Add server** in the top-right. AcelleMail opens **Choose a server type** — a tiled grid of every provider you allowed on the plan, grouped under **Built-in providers**. If you only enabled SES + SMTP on this plan, only those tiles show up:

![Choose a server type page — Connect your sending pipeline hero with a 3-step ribbon (Pick a provider → Connect credentials → Send a test). Below, a Built-in providers grid with one tile per allowed type: Amazon Web Service API, Amazon Web Service SMTP, SMTP, Sendmail (UNIX), Mailgun API, Mailgun SMTP, SendGrid API, SendGrid SMTP, ElasticEmail API, ElasticEmail SMTP, SparkPost API, SparkPost SMTP, Blastengine API, Blastengine SMTP, Gmail (Google OAuth).](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/06-customer-server-type-picker.png)

They click a tile (say, **SMTP**) and land on the connection form. For SMTP they paste host, port, encryption, username and password — for API providers (SES, SendGrid, etc.) they paste an API key. Then **+ Add server**:

![Connect sending server form for SMTP — breadcrumb reads "Sending Servers / Select type / SMTP". A Connection settings panel collects SMTP host, SMTP port (default 587), Encryption (TLS), SMTP username and SMTP password, with a teal + Add server button at the bottom.](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-server-for-plan/07-customer-create-form.png)

That server is **theirs** — bound to their customer record. Other customers on the same plan don't see it and can't use it. The credentials are stored in AcelleMail's `sending_servers.config` JSON column scoped by `customer_id`.

### When to pick this mode

- Customers who already pay for their own SES / SendGrid / Mailgun account and don't want to re-pay for sending volume on your side.
- Agencies sending on behalf of clients — each client account gets its own vendor credentials.
- Compliance / data-residency situations where the customer must be the legal sender.
- Any plan where deliverability is *the customer's* responsibility, not yours.

---

## Common questions

**Can I mix both modes on the same plan?**
No. The two cards are mutually exclusive — saving one disables the other. If you want a plan where the customer can *fall back* to your servers when they have none, that's a feature request, not a setting today.

**What happens to customers already subscribed to a plan when I change its mode?**
The mode flips for every existing subscription on the next page load. Customers on a now-System plan will lose the **Sending servers** menu (their saved servers stay in the DB but become inaccessible). Customers on a now-Own plan get the menu and start at zero servers. Switch modes with care on plans that already have paying subscribers — usually safer to clone the plan and migrate customers across.

**Why is the "Add Server" dropdown empty for me?**
The popup only lists **system-owned, active** sending servers that aren't already attached to this plan. If you only have one system server and it's already attached, the dropdown is empty. Add a new system server first at **Admin → Sending → Sending servers → + New server**, then come back to the plan and attach it.

**Customer says they don't see the Sending servers menu — why?**
Three possibilities:

1. Their plan is in System mode. That's by design — switch the plan to Own mode if you want them to see it.
2. Their plan is in Own mode but **every provider toggle is off** — there's nothing they can add, so the menu hides itself.
3. Their `MAX_SENDING_SERVERS` quota is set to 0. Bump it (or flip Unlimited) and they'll see the menu again.

**Can I set "Use my system servers AND let them add their own"?**
Not as a single mode. The two cards are mutually exclusive. In practice almost nobody needs the hybrid — pick the mode that matches the buyer profile of the plan and stick with it.

---

# AcelleMail Webhook Signature Verification and Replay Protection

> When you receive a webhook from AcelleMail (or AcelleMail receives one from a third-party event source like Meta), the receiver has no built-in trust that the request came from where it claims. This guide walks the HMAC-SHA256 signature verification pattern AcelleMail uses internally for Ads webhook reception, the constant-time-compare hazard most operators get wrong on first attempt, the replay-protection cache pattern, and how to apply the same techniques to your own outbound integrations.

- Canonical URL: https://acellesend.com/kb/articles/acellemail-webhook-signature-verification-and-replay-protection
- Author: Tom Becker, Developer Editor
- Category: Developer Guide — REST API, webhooks, customization, extending source code, plugins
- Content type: reference · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 11 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

When you receive a webhook from AcelleMail — or when AcelleMail receives one from a third-party event source like Meta — the receiver has **no built-in trust** that the request came from where it claims. The Internet just hands HTTP requests to whatever URL is published. Without signature verification, anyone who finds your webhook URL can POST arbitrary payloads to it.

This guide walks the **HMAC-SHA256 signature verification pattern** AcelleMail uses internally (and that you should adopt for your own custom webhook receivers), the **replay-protection cache pattern** that prevents duplicate processing, and the **constant-time-compare hazard** that most operators get wrong on first attempt.

The canonical source is `app/Services/Ads/WebhookSignatureVerifier.php` in AcelleMail. This guide explains the design and shows equivalent implementations in PHP, Node.js, Python, and Go.

## What signature verification actually does

When AcelleMail (or any properly-designed event source) sends you a webhook, it includes a header like:

```
X-Webhook-Signature: sha256=a3b6f5e8c7d2…
```

The signature is computed as:

```
signature = HMAC-SHA256(secret_key, raw_request_body)
```

Both sides know the `secret_key` (you configured it when registering the webhook). To verify, you:

1. Read the raw request body **before parsing it as JSON** (crucial — JSON.stringify of the parsed object produces different bytes than the original).
2. Compute the expected HMAC using your stored secret + the raw body.
3. **Constant-time compare** the expected signature against the received signature.

If they match, the request was signed by someone who knows the secret — almost certainly the legitimate sender. If they don't match, reject with 401.

This protects against:
- **Spoofed requests** — attackers can't fake a valid signature without the secret.
- **Modified payloads** — if anything in the body changed in transit, the signature breaks.
- **Stale credentials** — rotating the secret immediately invalidates all previously-issued signatures (so leaked URLs aren't enough on their own).

It does **not** protect against:
- **Replay attacks** — a captured valid request can be re-sent verbatim. Replay protection (covered below) is a separate concern.
- **Secret-key exfiltration** — once an attacker has your secret, they can sign arbitrary requests. Treat the secret like a password.
- **Endpoint discovery** — signature verification doesn't hide that your endpoint exists.

## The canonical pattern (PHP, from AcelleMail source)

AcelleMail's [`app/Services/Ads/WebhookSignatureVerifier.php`](https://github.com/acelle/acelle) is the source-of-truth example. Two key methods:

### `verifyMeta($rawBody, $signatureHeader, $appSecret)`

```php
public function verifyMeta(string $rawBody, ?string $signatureHeader, ?string $appSecret): bool
{
    $enforce = (bool) config('ads.webhooks.enforce_signature', false);

    if ($appSecret === null || $appSecret === '') {
        // Misconfigured install — can't verify.  Observation mode still
        // lets events through for dev/staging where the secret isn't set.
        return !$enforce;
    }

    if ($signatureHeader === null || $signatureHeader === '') {
        return !$enforce;
    }

    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $appSecret);
    if (hash_equals($expected, $signatureHeader)) {
        return true;
    }

    return !$enforce;
}
```

Three things to notice:

1. **`hash_hmac('sha256', $rawBody, $appSecret)`** computes the HMAC. The order of arguments is `algorithm, data, key`. Passing them in the wrong order silently produces wrong output (no error, just wrong bytes).

2. **`hash_equals($expected, $signatureHeader)`** is a **constant-time comparison**. NEVER use `===` or `==` for signature comparison. See the next section.

3. **`$enforce` flag toggles between "reject on mismatch" (production) and "log + accept" (observation mode for staging).** This makes safe rollout possible — you can deploy verification in observation mode, watch logs for false-positive rejections, fix the issue, then enable enforcement. AcelleMail's `ads.webhooks.enforce_signature` config flag drives this.

### `recordAndCheckFresh($platform, $eventId)`

```php
public function recordAndCheckFresh(string $platform, string $eventId): bool
{
    $key = "ads_webhook_replay:{$platform}:{$eventId}";
    $ttl = (int) config('ads.webhooks.replay_protection_ttl_seconds', 300);

    if (Cache::has($key)) {
        return false; // already seen
    }

    Cache::put($key, 1, $ttl);
    return true;
}
```

Replay protection lives next to signature verification. The pattern:

1. Each webhook event has a unique `eventId` from the sender (Meta sends `id` in their payload; AcelleMail can use the campaign uid or webhook delivery uid).
2. First time we see that ID, record it in cache with a TTL.
3. Subsequent calls within the TTL return `false` → caller should ACK (return 200) but **not re-process**.

AcelleMail's default TTL is 300 seconds (5 minutes). This is a balance:
- Too short (60s) — slow-retrying senders may legitimately resend after that, and we'd double-process.
- Too long (24h) — cache memory grows; legitimate retries beyond TTL would fail (rare).

5 minutes works for ~99% of webhook senders (most retry within 30-60 seconds; few re-send the same event minutes later).

## The constant-time-compare hazard

This is the bug ~50% of first-time webhook-verifier implementations have.

**Bad:**

```php
if ($expected === $received) {  // ← TIMING ATTACK
    return true;
}
```

**Good:**

```php
if (hash_equals($expected, $received)) {  // ← CONSTANT-TIME
    return true;
}
```

The problem with `===`: PHP's string compare short-circuits on the first mismatched byte. By measuring how long the compare takes, an attacker can probe one byte at a time, eventually deriving the entire valid signature without ever knowing the secret. This is the classic **timing-attack** vulnerability.

`hash_equals()` compares all bytes regardless of where mismatches occur, taking constant time. Same applies to similar functions in other languages:

- **Node.js**: `crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))` (both must be same length; pre-pad if needed)
- **Python**: `hmac.compare_digest(expected, received)` (works on bytes or strings)
- **Go**: `subtle.ConstantTimeCompare([]byte(expected), []byte(received)) == 1`
- **Ruby**: `Rack::Utils.secure_compare(expected, received)` or `OpenSSL.fixed_length_secure_compare(...)`

If your language doesn't ship a primitive, implement it carefully:

```javascript
// Don't write your own unless you have to — and study existing implementations first.
function constantTimeEquals(a, b) {
  if (a.length !== b.length) return false;
  let result = 0;
  for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return result === 0;
}
```

## Receiving outbound webhooks from AcelleMail

AcelleMail emits outbound webhooks for these events (from `config/webhook_events.php`):

| Event | Trigger | Payload params |
|---|---|---|
| `new_subscription` | A customer activates a subscription plan | `customer_id`, `plan_id` |
| `cancel_subscription` | A customer cancels their subscription | `customer_id`, `plan_id` |
| `new_customer` | A new customer is created | `customer_id` |
| `change_plan` | A customer switches subscription plans | `customer_id`, `old_plan_id`, `new_plan_id` |
| `terminate_subscription` | Subscription ends (typically billing failure) | `customer_id`, `plan_id` |
| `automation_webhook` | An Automation webhook node fires | `automation_id` + any custom merge data from the automation |

These are SaaS-lifecycle events from AcelleMail's billing layer, NOT per-subscriber email events (those go through the bounce/click/open tracking system, see [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints)).

### Setting up a receiver

In your AcelleMail admin: **Settings → Webhooks → New webhook**. Fields:

- **Event** — pick from the list above.
- **HTTP endpoint** — your receiver URL.
- **Secret** — a random 32+ character string. SAVE THIS; you'll configure your receiver with it.
- **Active** — toggle on.

The default `Webhook::newDefault()` (Webhook.php:21) sets:

```php
$webhook->setting_retry_times = 2;          // retry twice on failure (3 attempts total)
$webhook->setting_retry_after_seconds = 900; // 15 minute delay between retries
```

If your endpoint is unreachable on first attempt, AcelleMail retries 15 minutes later, and again 15 minutes after that. After 3 total failures, the event is dropped.

This means your receiver must:
- ACK quickly (return 200 within ~5 seconds; longer and AcelleMail may time out).
- Be idempotent (the same event might be delivered multiple times if your earlier ACK was lost).
- Be reasonably available (down for >45 minutes = lost events).

For high-throughput receivers, the standard pattern is **ACK fast, process async**:

```php
// In your webhook receiver controller:
public function handle(Request $request)
{
    $raw = $request->getContent();
    $signature = $request->header('X-Acelle-Signature');

    // 1. Verify signature (your stored secret)
    if (!$this->verifySignature($raw, $signature)) {
        return response('Invalid signature', 401);
    }

    // 2. Queue for async processing
    ProcessAcelleWebhook::dispatch($raw);

    // 3. ACK immediately
    return response('OK', 200);
}
```

Then your queue worker does the actual business logic. This pattern keeps response times in the millisecond range and protects you from slow downstream services blocking the webhook ACK.

## Signature-verify implementations across languages

The same HMAC-SHA256 pattern, four languages:

### PHP (Laravel-flavored)

```php
function verifyAcelleSignature(string $rawBody, ?string $sigHeader, string $secret): bool
{
    if (empty($sigHeader)) return false;
    // Expect format: "sha256=<hex>"
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $sigHeader);
}
```

### Node.js

```javascript
const crypto = require('crypto');

function verifyAcelleSignature(rawBody, sigHeader, secret) {
  if (!sigHeader) return false;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const expBuf = Buffer.from(expected);
  const sigBuf = Buffer.from(sigHeader);
  if (expBuf.length !== sigBuf.length) return false;
  return crypto.timingSafeEqual(expBuf, sigBuf);
}
```

**Express body parsing gotcha:** `app.use(express.json())` consumes the body before your handler runs, so `req.body` is the parsed object — not raw bytes. Use `express.raw({ type: 'application/json' })` instead, then parse JSON yourself after verifying.

### Python (Flask/FastAPI)

```python
import hmac, hashlib

def verify_acelle_signature(raw_body: bytes, sig_header: str | None, secret: str) -> bool:
    if not sig_header:
        return False
    expected = "sha256=" + hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, sig_header)
```

**Flask gotcha:** use `request.get_data(as_text=False)` to get raw bytes — `request.json` already parses.

### Go

```go
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

func VerifyAcelleSignature(rawBody []byte, sigHeader, secret string) bool {
    if sigHeader == "" {
        return false
    }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(rawBody)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(sigHeader))
}
```

**net/http gotcha:** `r.Body` is a one-shot stream. Read it once into a buffer, then re-wrap for downstream handlers:

```go
body, _ := io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewBuffer(body))
```

## Replay protection in your receiver

Mirror AcelleMail's pattern in your own receiver:

```php
function isFreshAcelleEvent(string $eventId, int $ttlSec = 300): bool
{
    $key = "acelle_webhook_replay:{$eventId}";
    if (Cache::has($key)) {
        return false; // already seen
    }
    Cache::put($key, 1, $ttlSec);
    return true;
}

// In the handler:
if (!isFreshAcelleEvent($eventId)) {
    return response('OK (duplicate)', 200); // ACK but don't re-process
}
// ... process the event
```

If your stack doesn't have a Cache abstraction, Redis works:

```javascript
// Returns 1 if SET (fresh), 0 if NX failed (already seen).
const ok = await redis.set(`acelle_webhook_replay:${eventId}`, 1, 'EX', 300, 'NX');
if (ok !== 'OK') {
  return res.status(200).send('OK (duplicate)');
}
```

For low-volume webhooks, a database table with a unique constraint on `event_id` works too — insert, catch the unique-violation, ACK without processing.

## Common operator mistakes

**Reading parsed body for HMAC.** `JSON.stringify(req.body)` reorders keys or drops whitespace, producing different bytes than the original. The signature will always fail. **Always verify against the raw body.**

**Logging the secret.** Don't log webhook secrets in error messages. A signature-failure log entry should include the request signature header (received), the path, the timestamp, the IP — NEVER the expected signature or the secret. Log levels: warn for mismatches; info for successful verifications (sample 1% or skip).

**Trying to verify before middleware reads the body.** Most web frameworks consume the request body in middleware. By the time your verification handler runs, raw bytes are gone. Configure your framework to expose raw bytes specifically on the webhook endpoint (Express raw middleware, Flask `request.get_data(cache=True)`, etc.).

**Using `=` instead of `hash_equals()` in test suites.** Tempting because the test secret is short and "no one will time us." But the habit transfers to production code through copy-paste. Always use the constant-time primitive.

**Rotating the secret without coordination.** If you rotate AcelleMail's webhook secret, the AcelleMail side has the new value but your receiver still has the old one — every webhook fails until you update your receiver. Plan rotations with a brief overlap period if possible, OR rotate in one atomic operation if not.

**Ignoring the `enforce_signature` flag in dev.** Some operators run signature-verification in observation mode forever ("it's just for dev"). Then production silently lets in unverified requests. Always set `enforce=true` in production environment-specific config.

**Not handling the case where you have multiple webhooks per event.** AcelleMail supports multiple webhook configurations per event type. Each has its own secret. Your central receiver may need to look up the right secret based on path or webhook ID, not assume a single global secret.

## Operational best practices

- **Store the secret in environment variables**, not in code or config files. Use a secret manager (AWS Secrets Manager, HashiCorp Vault, doppler.com) for production rotation.
- **Allow IP allowlisting** as defense-in-depth (in addition to signature). Don't replace signature with IP-check — IPs change, and signature is stronger.
- **Monitor signature failures** via metrics. Sudden spike = either misconfiguration on the sender side or active attack. Investigate.
- **Test rotation drills periodically**. Annually if not more often. Practice the rotation, document the runbook, time it.
- **Have a panic-rotation runbook**. If a secret leaks, you should be able to rotate AcelleMail + receivers in under 10 minutes.

## Related reading

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — API auth (Bearer tokens — different from webhook secrets)
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — full outbound event catalog
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — when YOU send events INTO AcelleMail
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — adding custom outbound webhooks
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — security baseline that includes webhook setup

---

# Choosing the Queue Driver: Database vs Redis vs SQS for AcelleMail

> AcelleMail ships with `QUEUE_CONNECTION=database` as the default — and that's the right choice for installations up to ~500k emails per day. Past that threshold, database-queue contention starts to dominate worker time, and switching to Redis or SQS produces a step-change in throughput. This guide walks the trade-offs, the threshold where each driver wins, the migration steps, the AUTOMATION_QUEUE_CONNECTION separation pattern, and the operational gotchas you only learn after the first 3am queue incident.

- Canonical URL: https://acellesend.com/kb/articles/choosing-the-queue-driver-database-vs-redis-vs-sqs-for-acellemail
- Author: Lena Hart, Email Deliverability Editor
- Category: Email Infrastructure — Mail servers, MTA configuration, relay setup, IP management, monitoring
- Content type: guide · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 12 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail ships with `QUEUE_CONNECTION=database` as the default in `.env.example` line 40. The database driver puts all queue jobs in a MySQL/MariaDB table and uses row-level locks to coordinate between worker processes. It works out of the box, requires no extra services, and is **right for installations up to ~500k emails per day**.

Past that threshold, database-queue contention starts to dominate worker time. Workers spend more time waiting for locks than processing jobs. The symptoms are subtle at first — a small drop in throughput — then sudden when a campaign batch pushes contention past a critical point.

This guide walks the **three queue driver options** AcelleMail supports — database, Redis, AWS SQS — the **threshold where each wins**, the **migration path** between them, the **AUTOMATION_QUEUE_CONNECTION separation pattern** for high-volume installs, and the **operational gotchas** you only learn after the first 3am queue incident.

## How AcelleMail uses queues

Before picking a driver, understand what the queues actually do. From `resources/documents/supervisor_{master,worker}_config.tmpl`:

**Master pool** (2 processes by default):
```
queue:work --queue=import,default --tries=1 --max-time=180
```

The master pool handles:
- `import` — CSV imports of subscribers (chunky long-running work)
- `default` — miscellaneous Laravel default queue

**Worker pool** (15 processes by default):
```
queue:work --queue=high,batch,single,automation,automation-dispatch --tries=1 --max-time=180
```

The worker pool handles email sending:
- `high` — admin-priority sends (welcome to new admin user, password resets, etc.)
- `batch` — campaign sends (the high-volume firehose)
- `single` — individual transactional sends
- `automation` — automation-step emails (drip sequences, welcome series, etc.)
- `automation-dispatch` — the meta job that scans for due automations every 5 minutes

So a single AcelleMail install runs 17 worker processes (2 master + 15 worker), each polling the queue table or service for work.

**The implication for queue drivers:** with 17 workers all polling the same queue backend, the polling pattern itself matters a lot. A database queue with 17 SELECT FOR UPDATE statements firing every few seconds is a different load profile than 17 BLPOP'ing Redis clients.

## The three driver options

### Database (default)

**How it works.** A `jobs` table in your application database. Workers do `SELECT FROM jobs WHERE reserved_at IS NULL ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED`, pick up a row, set `reserved_at = NOW()`, process, then DELETE. Failed jobs go to a `failed_jobs` table.

**Pros.**
- Zero infrastructure overhead — already have MySQL.
- Easy to inspect (`SELECT * FROM jobs` shows current backlog).
- Easy to clear (TRUNCATE in a panic).
- Transaction-safe with the rest of your data (campaign create + queue dispatch in one transaction).
- Backups capture queue state alongside application state.

**Cons.**
- **Polling load on MySQL.** Each worker polls every few seconds (configurable). 17 workers × every 3 seconds = ~5-6 SELECT queries per second baseline. Manageable but not free.
- **Row-lock contention** at high concurrency. SKIP LOCKED helps but isn't free.
- **No native blocking semantics.** Workers must poll; they can't subscribe-and-block like Redis BLPOP.
- **Queue table grows.** Completed jobs DELETE but unprocessed/retried jobs accumulate. Backlog visibility costs grow with table size.

**Right when:** <500k emails/day, single-server install, no separate cache layer planned, ops team is database-only-comfortable.

### Redis

**How it works.** Redis lists per queue name. Workers BLPOP-block on the list (`BLPOP queue:batch 5` blocks up to 5 seconds for work). When a campaign dispatches, jobs RPUSH to the list. Failed jobs go to a separate `failed_jobs` list (Redis or DB).

**Pros.**
- **Blocking semantics.** Workers sleep until work arrives — no polling overhead.
- **Sub-millisecond enqueue/dequeue.** 100k jobs/sec on commodity Redis.
- **Lower MySQL load.** Frees your application DB from queue traffic — bigger wins than people expect.
- **Native pub/sub for monitoring.** Tools like Horizon (Laravel's official queue dashboard) plug in cleanly.
- **Memory-only is fast; persistence is configurable** (RDB snapshots or AOF for durability).

**Cons.**
- **Another moving part.** Redis is a service to run, monitor, back up, secure.
- **Memory cost.** Backlogged queues live in RAM. A 1M-job backlog at ~2KB each = ~2GB Redis memory.
- **Less inspection ergonomics.** Hard to "SELECT * FROM queue" — must use Redis CLI or a tool.
- **Failover complexity.** Single-Redis is a single point of failure; HA Redis (Sentinel, Cluster) is non-trivial.

**Right when:** 500k-5M emails/day, single Redis instance acceptable, ops team has Redis experience, throughput sensitivity matters.

### AWS SQS

**How it works.** Managed queue service from AWS. Workers long-poll SQS for messages. Each message has a visibility timeout — if worker doesn't ACK within the timeout, the message reappears.

**Pros.**
- **Fully managed.** AWS handles HA, durability, scaling.
- **Massive scale.** No practical upper limit on messages/sec.
- **Pay-per-use.** $0.40 per million messages. At 10M emails/month, that's $4/month — trivial.
- **Cross-region durability.** Critical for multi-region setups.
- **Native DLQ (dead-letter queue) support.**

**Cons.**
- **AWS lock-in.** Or at least AWS-tax — outbound traffic from non-AWS to SQS costs more.
- **Per-message ordering not guaranteed** on standard SQS. FIFO SQS exists but has lower throughput.
- **30s default visibility timeout** can cause duplicate processing of slow jobs. Tune carefully.
- **Network round-trip per poll.** Slightly higher per-job latency than Redis (5-10ms vs sub-1ms).
- **Per-message size limit 256KB.** Rarely hit but does affect large payload jobs.

**Right when:** Multi-region setup, AWS-native ops team, >5M emails/day, durability requirements demand managed service.

## The threshold question

Rough heuristics:

| Daily email volume | Recommended driver | Why |
|---|---|---|
| < 500k emails/day | **Database** | Default; no operational complexity benefit yet |
| 500k - 5M emails/day | **Redis** | Sweet spot for self-hosted scale |
| > 5M emails/day | **Redis (HA) or SQS** | Database is a bottleneck; managed service worth considering |
| Multi-region | **SQS** | Cross-region durability is hard with self-hosted Redis |

These are guidelines, not laws. Other factors:

- **Existing ops competency.** If your team already runs Redis for caching, the Redis option is cheap to add. If not, the learning curve matters.
- **Database server load.** If MySQL is already at 60%+ CPU during peak campaigns, moving queue off MySQL is a quick win even at lower email volumes.
- **Monitoring requirements.** Database queues are easy to inspect with familiar SQL tools. Redis/SQS need separate dashboards.
- **Failure-mode tolerance.** Database queue is "queue down = whole app down." Redis can be down for queue while app stays up (campaigns queue locally, drain when Redis recovers — though Laravel's behavior here depends on driver config).

## Switching from database to Redis

The migration is straightforward in concept; the operational details matter.

### 1. Install + secure Redis

```bash
# Ubuntu/Debian
sudo apt install -y redis-server
sudo systemctl enable --now redis-server

# Verify
redis-cli ping  # → PONG

# Secure: edit /etc/redis/redis.conf
sudo sed -i 's/^# requirepass .*/requirepass YOUR_LONG_RANDOM_PASSWORD/' /etc/redis/redis.conf
sudo systemctl restart redis-server
```

Set `bind 127.0.0.1` if Redis lives on the AcelleMail VPS. For separate Redis, bind to the internal-network IP and firewall port 6379 to AcelleMail's IP only.

### 2. Update `.env`

```bash
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis  # or 'predis'
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=YOUR_LONG_RANDOM_PASSWORD
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1       # if also using Redis as cache (recommended)
```

If running Redis on a separate server, set `REDIS_HOST` to its private IP and ensure connectivity (firewall, security groups).

### 3. Drain the database queue first

You don't want jobs split across two backends during the cutover. Stop new sends, wait for the database `jobs` table to empty:

```sql
SELECT COUNT(*) FROM jobs;        -- watch until 0
SELECT COUNT(*) FROM failed_jobs; -- check; handle separately
```

Or pause active campaigns and let the queue drain naturally. For high-volume installs where you can't pause, dual-run is possible (set `QUEUE_CONNECTION=redis` but keep one worker on `--connection=database` until it drains).

### 4. Restart workers

```bash
sudo supervisorctl restart acellemail:*
```

Workers pick up the new `QUEUE_CONNECTION` from `.env`.

### 5. Verify

```bash
# Inside Redis
redis-cli
> KEYS *
# Should see keys like "queues:batch", "queues:default", etc. after first send

# In application logs
tail -f storage/logs/laravel.log
# Watch for queue-related warnings
```

Send a test campaign to a small list. Verify the job processes successfully end-to-end.

## The AUTOMATION_QUEUE_CONNECTION separation pattern

For installs with heavy automation use, AcelleMail's `.env` supports an **optional** separate queue connection for automation jobs:

```bash
QUEUE_CONNECTION=redis              # main campaign sends
AUTOMATION_QUEUE_CONNECTION=database # automations + automation-dispatch
```

**Why separate?**

The `batch` queue (campaign sends) and the `automation` queue (drip + welcome + sequence sends) have very different load profiles:

- **Campaign sends** are bursty. A big newsletter dispatch creates 100k+ jobs in seconds, then quiets down.
- **Automation sends** are steady. A welcome-series email per new subscriber, a birthday email per matching profile per day. Continuous low-rate.

If both share the same Redis instance, a campaign burst can backlog the queue and delay automation sends. By splitting them onto separate connections, you isolate the load.

**When to use:**
- 5M+ emails/month
- Time-sensitive automations (welcome series, abandoned cart) where delay is unacceptable
- Heavy automation volume relative to campaign volume

Most installs don't need this. Keep both on the same driver unless you have specific reason to split.

## Operational gotchas

### 1. Backpressure on database driver

When the `jobs` table grows past ~100k rows, MySQL query plans on `reserved_at IS NULL ORDER BY id LIMIT 1` start to degrade. The index helps but doesn't eliminate the problem.

**Symptom.** Workers report "no jobs" repeatedly while there are clearly jobs in the table. Investigation shows the worker queried for `WHERE queue = 'batch' AND reserved_at IS NULL LIMIT 1` and the database optimizer scanned thousands of rows to find one matching.

**Fix.** Add a composite index if missing:

```sql
CREATE INDEX idx_jobs_queue_reserved ON jobs (queue, reserved_at, id);
```

Or migrate to Redis, where this whole class of problem doesn't exist.

### 2. Redis memory blowup during bounce-handler runs

The bounce-handler command (`handler:run` every 30 min per `routes/console.php`) processes bounces in chunks. If you have a sudden bounce-rate spike (a list import gone wrong), Redis can balloon to GBs in minutes.

**Symptom.** Redis memory usage spikes, then crashes with OOM.

**Fix.** Set `maxmemory` + `maxmemory-policy noeviction` in `/etc/redis/redis.conf`. With `noeviction`, Redis refuses writes when full instead of evicting queue jobs (which would lose work). Pair with monitoring alerts.

### 3. SQS visibility timeout vs slow campaign jobs

A campaign batch dispatch can take 30-60 seconds for very large lists. If SQS visibility timeout is 30s default, SQS re-queues the message at 30s, and you process it twice.

**Symptom.** Some recipients get duplicate emails after a campaign send.

**Fix.** Increase SQS visibility timeout on the queue: `aws sqs set-queue-attributes --queue-url ... --attributes VisibilityTimeout=300`. Or set per-message via `--message-attributes`.

### 4. Stale workers after deployment

After deploying new AcelleMail code, workers keep running with the old code in memory. They process new jobs with the stale logic.

**Symptom.** Jobs that should use the new feature use the old behavior; logs show old class definitions.

**Fix.** Restart workers after every deployment:

```bash
sudo supervisorctl restart acellemail:*
```

Or use Laravel's queue:restart command for graceful restart:

```bash
sudo -u www-data php artisan queue:restart
```

Workers will finish their current job, then restart with new code. Add this to your deploy script.

### 5. Failed jobs invisible without inspection habits

Failed jobs go to `failed_jobs` table (database) or a separate Redis list. Both are easy to forget.

**Symptom.** Days later, you notice 500 jobs in failed_jobs from a misconfigured template last Tuesday. Subscribers never got those emails.

**Fix.** Cron-scheduled check on `failed_jobs` count + alerting:

```bash
# In a monitoring script
COUNT=$(php artisan tinker --execute='echo \DB::table("failed_jobs")->count();')
if [ "$COUNT" -gt 0 ]; then
  # alert ops
fi
```

For Redis-backed queues, Laravel Horizon dashboard shows failed jobs visually. Worth the setup time for any install with Redis already running.

### 6. Long-running batch jobs hitting max-time

The supervisor config has `--max-time=180` (3 minutes). Workers self-terminate after 180s and supervisor restarts them. This is normal and intentional — workers can leak memory; periodic restarts protect against that.

**Symptom.** Long campaign batches that take >3 min seem to silently restart mid-stream.

**Fix.** Acelle's batch-dispatch design accounts for this — the campaign's `dispatchWithBatchMonitor` creates a Job Batch that survives worker restarts. Each batch sub-job is short (a few subscribers). If you see longer-running jobs, that's usually a sign of bad batching logic or a custom worker that's not following Acelle's patterns.

## Inspecting queue state

### Database queue

```sql
-- Current queue depth
SELECT queue, COUNT(*) FROM jobs GROUP BY queue;

-- Stuck/long-running jobs
SELECT id, queue, payload->>'$.displayName', attempts, reserved_at
FROM jobs
WHERE reserved_at IS NOT NULL
  AND reserved_at < NOW() - INTERVAL 5 MINUTE;

-- Failed jobs
SELECT id, queue, exception, failed_at
FROM failed_jobs
ORDER BY failed_at DESC LIMIT 20;
```

### Redis queue

```bash
redis-cli

> LRANGE queues:batch 0 10  # peek at first 10 jobs in batch queue
> LLEN queues:batch          # depth of batch queue
> KEYS queues:*              # all active queue keys
> SMEMBERS failed_jobs       # failed-job IDs
```

For pretty visualization, use **Laravel Horizon** (Composer install + small config). It's overkill for tiny installs but excellent for any operator running Redis-backed queues seriously.

### SQS queue

```bash
aws sqs get-queue-attributes \
  --queue-url https://sqs.region.amazonaws.com/account/your-queue \
  --attribute-names All

# ApproximateNumberOfMessages = current backlog
# ApproximateNumberOfMessagesNotVisible = currently being processed
```

## When to think about queue at all

You don't need to think about queue drivers until you hit one of:

- Your `jobs` table exceeds 100k rows during peak
- MySQL CPU hits >70% during campaign sends
- Sub-second delivery latency matters (transactional sends, etc.)
- You're planning >2M emails/day routinely
- An incident makes you realize you couldn't restore the queue from a backup easily

If none of those apply, the database default is doing its job. Spend the ops cycles elsewhere.

## Related reading

- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — foundation; covers supervisor config
- [Email Queue Architecture in AcelleMail](https://acellesend.com/kb/articles/email-queue-architecture-in-acellemail) — how dispatch + batch monitoring works internally
- [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing) — Redis-specific deep dive
- [Scaling for 100k Emails per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) — sharded-DB pattern for the next scale tier
- [Why Is My AcelleMail Campaign Stuck in "Sending" Status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status) — queue worker troubleshooting
- [Why Is My AcelleMail Automation Not Triggering](https://acellesend.com/kb/articles/why-is-my-acellemail-automation-not-triggering) — automation-queue specific
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — including Redis password + firewall
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — protect both database and Redis state

---

# Email Accessibility: WCAG Best Practices for Bulk Email

> Roughly 15% of your subscribers have a permanent or situational disability that affects how they read email — screen reader users, low-vision readers, color-blind users, motor-impaired clickers, cognitive-load-sensitive readers. The European Accessibility Act took effect 2025-06-28 with real fines; US ADA-equivalent enforcement is rising. Beyond compliance, accessible email gets ~10-25% better engagement across all readers. This guide walks the seven structural rules, the inline-style techniques email clients enforce, the screen-reader test pattern, and the WCAG 2.2 AA criteria that actually translate to bulk email.

- Canonical URL: https://acellesend.com/kb/articles/email-accessibility-wcag-best-practices-for-bulk-email
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Design & Content — HTML email, responsive design, dark mode, accessibility, content strategy
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 14 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Roughly **15% of your subscribers have a permanent or situational disability** that affects how they read email. Some are screen-reader users. Some are low-vision readers who zoom to 200%+. Some are color-blind (8% of men globally). Some have motor impairments that affect mouse precision. Some are dyslexic and depend on font + line-spacing choices for readability. Some are temporarily impaired — reading on a phone in bright sunlight, or in a moving car, or while exhausted.

The European Accessibility Act took effect **2025-06-28** and applies to "digital services for consumers" including marketing emails. The US ADA has been interpreted by courts to cover digital channels since 2017 — and the Department of Justice's 2024 NPRM strengthens that interpretation. Enforcement is ramping. Fines reach **€100,000+** in the EU, with private litigation in the US adding settlement risk.

Beyond compliance, accessible email **performs better for everyone** — ~10-25% better engagement across all readers, not just those with disabilities. Larger text is easier to scan on phones. Higher contrast is easier to read in any lighting. Semantic structure is easier to navigate even with full sight.

This guide is the operator-grade reference for the **seven structural rules** that make bulk email accessible — built around WCAG 2.2 AA criteria that actually translate to the constrained email medium.

## Why email accessibility is different from web accessibility

Web pages can ship full ARIA semantics, JavaScript focus management, CSS variables for theming, and modern CSS for layout. Email cannot. Major email clients strip:

- **JavaScript** entirely (and have for 20+ years)
- **Most modern CSS** (Outlook desktop in particular — uses Word's rendering engine, supports ~30% of modern CSS)
- **Web fonts** (Outlook ignores; iOS Mail loads from font CDN; Gmail strips on Android)
- **Custom HTML elements** (most clients strip non-standard tags)
- **<style>** blocks (Gmail strips when forwarding; many clients ignore media queries)
- **External resources** (Yahoo blocks image loads by default; most clients strip linked CSS)

The accessibility techniques that work on the web — `<button>` with `aria-label`, focus-trap modals, dynamically-updated `aria-live` regions — don't translate. Email accessibility is mostly **structural HTML + inline styles + careful color choice + alt text discipline**.

The good news: that's a much smaller surface area to master. Seven structural rules cover ~95% of WCAG AA compliance for email.

## The seven structural rules

### 1. Use semantic HTML — real headings, real lists, real paragraphs

Screen readers navigate email by structure. A user who hits the "next heading" key on their screen reader jumps to the next `<h1>`, `<h2>`, `<h3>` etc. If your email is all `<div style="font-size: 24px; font-weight: bold">` instead of `<h2>`, the screen reader can't navigate it — and visually-able users get no semantic benefit either.

**Use:**

```html
<h1>Welcome to AcelleMail</h1>
<p>Thanks for signing up. Here's what to do next.</p>

<h2>Step 1: Verify your email</h2>
<p>Click the button below to verify…</p>

<h2>Step 2: Choose your plan</h2>
<ul>
  <li>Starter — for solo creators</li>
  <li>Business — for growing teams</li>
  <li>Enterprise — for high-volume operators</li>
</ul>
```

**Avoid:**

```html
<!-- Don't fake headings with divs -->
<div style="font-size: 32px; font-weight: bold;">Welcome to AcelleMail</div>
<!-- Don't fake lists with line breaks -->
<div>• Starter — for solo creators</div>
<div>• Business — for growing teams</div>
```

Visually identical; structurally invisible to assistive tech.

Use `<h1>` once per email (the main heading). Then `<h2>` for major sections, `<h3>` for sub-sections, and so on. Skip levels only with intention.

### 2. Use real text, not images-of-text

Images-of-text fail multiple ways:
- **Screen readers ignore them** (unless `alt` is set, and even then, alt is summary-quality not full-text-quality).
- **Search-in-email fails** (users can't Ctrl-F to find the offer).
- **Zoom degrades quality** (low-vision users at 200%+ see pixelated text).
- **Email clients block images by default** (Yahoo, Outlook desktop). If the message is image-only, recipients see nothing.
- **Dark-mode inversion breaks images** (text-on-image-background can become unreadable when client auto-inverts).

The safest rule: **all primary content as HTML text**. Use images for product photos, illustrations, and decorative elements only. Brand fonts can be approximated with system fonts (Arial/Helvetica/Georgia) or fallbacks — see [Email Template Design Best Practices](https://acellesend.com/kb/articles/email-template-design-best-practices).

### 3. Alt text on every image — descriptive, not redundant

```html
<!-- Product image with descriptive alt -->
<img src="..." alt="Black leather backpack, 25L capacity, with laptop sleeve">

<!-- Logo with brand name (not "logo") -->
<img src="..." alt="AcelleMail">

<!-- Decorative image — empty alt is correct -->
<img src="..." alt="" role="presentation">
```

Three principles:

- **Functional images** (logos, CTA-button images) → describe the function/destination, not the visual ("AcelleMail" not "AcelleMail logo image").
- **Content images** (product photos, illustrations supporting the message) → describe the content concisely.
- **Decorative images** (dividers, background flourishes, spacers) → `alt=""` and `role="presentation"`. Empty alt is correct; missing alt is wrong.

Avoid alt text that says "image of" or "picture of" — screen readers already announce "image."

### 4. Color contrast at WCAG 2.2 AA — 4.5:1 for text, 3:1 for large

WCAG 2.2 AA requires:
- **4.5:1 contrast ratio** for normal text (under 18pt regular or 14pt bold)
- **3:1 contrast ratio** for large text (18pt+ regular or 14pt+ bold)
- **3:1 contrast ratio** for UI components and graphical objects (CTA buttons, icons)

Check your colors at [webaim.org/resources/contrastchecker](https://webaim.org/resources/contrastchecker). For brand colors that fail:
- Slightly darken or lighten the text or background to push to compliance.
- Use color as a secondary signal only — never the only way information is conveyed.

Common email-design fails:
- Light gray body text on white background. `#aaaaaa` on `#ffffff` is only **2.85:1** — fails AA.
- Brand-colored CTA button text that's hard to read at small sizes.
- Light-on-light gradients in headers.

Fix the text colors. The hierarchy by contrast usually looks like:
- Headings: `#000000` or `#111111` on white = ~20:1 (overkill but safe)
- Body text: `#333333` on white = 12.6:1 (very accessible)
- Secondary text: `#666666` on white = 5.7:1 (passes AA)
- Minimum: `#767676` on white = exactly 4.54:1 (just passes AA — risky)
- Don't use anything lighter than `#767676` for text on white background.

### 5. Body text 16px or larger

WCAG doesn't mandate a specific size, but reality does:
- Mobile screens are small; most readers can't easily zoom.
- Older adults (the most active email reader cohort) lose ~1pt of comfortable reading size per decade of age past 40.
- Outlook on Windows can render small text crisply but blurs at <16px on high-DPI displays.

Use **16px minimum** for body text, **18-20px** preferred. Headings scale up from there:
- `<h1>` — 28-32px
- `<h2>` — 22-26px
- `<h3>` — 18-20px
- Body — 16-20px
- Secondary text (footer, fine print) — 14px minimum

```html
<p style="font-size: 18px; line-height: 1.5; color: #333;">
  Body text at 18px with 1.5 line-height — comfortable on any device.
</p>
```

Line height matters as much as font size. **1.5× line height** (a.k.a. `line-height: 1.5`) is WCAG-recommended for body text.

### 6. Dark-mode-aware color choices

Gmail, Apple Mail, Outlook for iOS, and now Outlook on Windows 11 auto-invert email colors to fit a user's dark-mode setting. Sometimes the inversion is helpful; often it's broken.

**Common dark-mode failures:**

1. **Logo with transparent background on white** → inverted to white logo on dark, but if the logo has light gray accents, they vanish.
2. **CTA button: white text on brand color** → most clients keep button colors stable but the visual context around shifts dark, making the button look out of place.
3. **Dark text on white card on dark background** (deliberate light-card-in-dark-mode) → client inverts the card AGAIN, making text black-on-black.
4. **Images with hardcoded white backgrounds** → look like islands of light against dark backgrounds.

Defenses:

- **Use `prefers-color-scheme` media query** for clients that respect it (Apple Mail, Outlook macOS):
  ```html
  <style>
    @media (prefers-color-scheme: dark) {
      .light-only-bg { background-color: #1a1a1a !important; }
      .light-only-text { color: #e0e0e0 !important; }
    }
  </style>
  ```
- **Set images with transparent backgrounds when possible** (PNG with alpha). Adds a `meta` to disable Gmail's auto-darkening:
  ```html
  <meta name="color-scheme" content="light dark">
  <meta name="supported-color-schemes" content="light dark">
  ```
- **Test in dark mode** before shipping. iOS Mail dark mode is the easiest test surface (Settings → Display → Dark).

For more on dark-mode specifics, see [Dark Mode Email Design](https://acellesend.com/kb/articles/dark-mode-email-design).

### 7. Functional, accessible CTAs

The most common accessibility failure in marketing email is the CTA button. Three rules:

**Make the link/button focusable and announce intent.** Use real `<a>` elements, not `<div onclick>`. The link text should describe the destination — "Read the case study" not "Click here."

```html
<!-- Good — screen reader announces "Read the case study, link" -->
<a href="https://..." style="
  display: inline-block;
  padding: 16px 32px;
  font-size: 18px;
  background-color: #1e40af;
  color: #ffffff;
  text-decoration: none;
  border-radius: 6px;
">
  Read the case study
</a>

<!-- Bad — screen reader announces "Click here, link" with no context -->
<a href="https://...">Click here</a>
```

**Buttons need 44×44px minimum touch target.** WCAG 2.5.5 (Target Size). Mobile readers with motor impairments or in non-ideal conditions (gloves, jolting transit) need at least 44px square to tap reliably. Most email CTAs need padding to hit this — `padding: 16px 32px` gets you there with normal text.

**Color is a secondary signal.** Underline links by default (don't disable underlines on `<a>` in email body text — most clients show them by default). For buttons, the button shape + bold weight + label clarity all carry meaning beyond color, so a color-blind user still knows it's actionable.

For Outlook-specific button rendering (the hardest client), see [Bulletproof CTA Buttons for Outlook](https://acellesend.com/kb/articles/bulletproof-cta-buttons-for-outlook).

## Inline-style techniques that email clients allow

CSS-in-`<style>` is risky (Gmail strips on forward; older clients ignore). Inline CSS via `style=""` attribute is the safe bet. Three patterns that improve accessibility AND survive client stripping:

### A. Inline font-size + line-height on every text element

```html
<p style="margin: 0 0 16px 0; font-size: 18px; line-height: 1.5; color: #333333;">
  Lorem ipsum…
</p>
```

This guarantees the size renders identically across clients. Many clients (notably Outlook desktop) ignore `<style>` block declarations but respect inline.

### B. Inline padding for tap targets

```html
<table cellpadding="0" cellspacing="0" border="0">
  <tr>
    <td style="padding: 16px 0;">
      <a href="..." style="display: inline-block; padding: 14px 28px; font-size: 18px; …">
        Read more
      </a>
    </td>
  </tr>
</table>
```

The outer `<td>` padding adds breathing room around the button. The inner `<a>` padding makes the link itself a large tap target.

### C. Inline color with !important for dark-mode protection

For clients that auto-invert (Gmail), `!important` can keep your colors:

```html
<p style="color: #333333 !important; background-color: #ffffff !important;">
  Body text…
</p>
```

This is heavy-handed; use sparingly. Generally let dark-mode happen and design for it (rule 6), rather than blocking it everywhere.

## The 5-minute accessibility audit

Before sending any campaign, run this checklist:

| Check | How |
|---|---|
| **Headings have semantic levels** | Open the source. Search for `<h1>`, `<h2>`, etc. There should be at least one of each, in nested order |
| **Every image has alt text** | Search source for `<img`. Every match should be followed by `alt="..."` (descriptive) or `alt=""` (decorative) — never missing |
| **Body text >= 16px** | Search for `font-size: 14`, `font-size: 12`, `font-size: 10` — these should only appear in footer/legal text |
| **Color contrast passes 4.5:1** | Paste foreground + background hex into [webaim.org/resources/contrastchecker](https://webaim.org/resources/contrastchecker) for the body text and the CTA button |
| **CTAs use real `<a>` with descriptive text** | Search for "Click here" — none should exist |
| **CTAs have 44×44px tap target** | Open the email on a phone. Tap each link with your knuckle (less precise than fingertip). All should work |
| **Dark mode test** | iOS: Settings → Display → Dark. Open the email in iOS Mail. Look for white-card flashes and broken contrast |
| **Screen reader spot check** | macOS: Cmd+F5 to enable VoiceOver. Read the email aloud. Note where it sounds confusing — fix those parts |

The full audit takes 5 minutes per email template after the first time. After you've internalized the seven rules, you stop creating accessibility bugs in the first place.

## WCAG 2.2 AA criteria that map to email

Not all WCAG criteria translate to email (no focus management, no live regions, no skip-links). The ones that do:

| WCAG 2.2 criterion | Means in email |
|---|---|
| **1.1.1 Non-text content** (A) | Every image has alt text |
| **1.3.1 Info and relationships** (A) | Use real headings, lists, tables (not faked with `<div>`) |
| **1.3.5 Identify input purpose** (AA) | If using preference center, `autocomplete` attributes on inputs |
| **1.4.3 Contrast (minimum)** (AA) | 4.5:1 text contrast |
| **1.4.4 Resize text** (AA) | Text remains usable at 200% zoom; use relative sizes when possible |
| **1.4.10 Reflow** (AA) | Single-column on mobile (320px) without horizontal scroll |
| **1.4.11 Non-text contrast** (AA) | 3:1 for CTA buttons + icons |
| **1.4.12 Text spacing** (AA) | User-overridable spacing: don't lock line-height too tight |
| **2.4.4 Link purpose (in context)** (A) | Link text describes destination ("Read the case study" not "Read more") |
| **2.5.5 Target size** (AAA, but de-facto AA for mobile) | 44×44 px CTA targets |
| **3.1.1 Language of page** (A) | `<html lang="en">` (or appropriate code) at top of email HTML |
| **4.1.2 Name, role, value** (A) | Use real `<a>` and `<button>`, not `<div onclick>` |

Hitting AA on the above 12 covers most legal exposure. The remaining WCAG criteria are largely web-platform features that don't apply to email.

## Regulatory landscape (as of 2026-05)

**European Accessibility Act (EAA).** Took effect **2025-06-28**. Applies to "digital services for consumers" — explicitly includes marketing emails sent to EU consumers, regardless of where the sender is based. Fines up to **€100,000** per violation in most member states; some go higher. Member-state implementation varies, but enforcement bodies are real and litigation is starting.

**US ADA.** Title III of the ADA has been interpreted by federal courts to cover digital content since the 9th Circuit's 2017 ruling (Robles v Domino's). The DOJ's 2024 NPRM strengthens this. Private-litigation risk is the main driver — class-action suits over inaccessible commercial content settle for $20k-$500k typically.

**Section 508.** Applies if you send email to or on behalf of US federal agencies. Mandatory AA conformance.

**State laws.** California, New York, and several others have layered additional digital accessibility requirements. California's Unruh Civil Rights Act has been the most-litigated.

The practical operator response: **comply with WCAG 2.2 AA across all consumer-facing email**, regardless of where recipients are. The compliance cost is small (a one-time template audit + ongoing discipline); the litigation risk is real.

## Tools for ongoing testing

- **[WAVE Email Accessibility tool](https://wave.webaim.org/)** — paste your email HTML, get a structured audit report. Free.
- **[axe DevTools browser extension](https://www.deque.com/axe/devtools/)** — runs WCAG checks on any rendered page (including the AcelleMail "Preview as HTML" view of a sent message). Free tier.
- **[Litmus](https://litmus.com)** — paid, but covers accessibility checks alongside email-client rendering tests. ~$99-199/month.
- **VoiceOver (macOS)** / **NVDA (Windows free)** / **TalkBack (Android)** — actual screen-reader tools for hands-on testing.
- **iOS Accessibility Inspector** (Xcode) — for iOS Mail audit.

Free combination that covers most needs: WAVE + axe + VoiceOver. The paid tools add scale (testing many templates) and email-client-specific rendering depth.

## Common myths to ignore

**"Accessible email looks ugly."** False. Most-engaged senders (Apple's marketing, Google's, government / nonprofit) ship accessible-by-design templates that look excellent. Constraint forces clarity.

**"I only need to care if I have disabled subscribers."** False. (a) You don't know who's disabled; assume some are. (b) Situational disability — bright sun, moving train, tired eyes — affects everyone. (c) Legal frameworks don't ask whether your specific recipients are disabled; they ask whether your content is accessible.

**"My platform handles accessibility for me."** Partially true at best. Platforms ship default templates that are mostly accessible. But the moment you customize colors, fonts, images, or HTML, you're on your own. The audit is on you.

**"Image-only emails are fine if I have alt text."** False. Yahoo, Outlook, and many corporate clients block images by default. An image-only email with alt text shows the recipient... just the alt text, in a tiny gray box. Use real text for content.

**"WCAG is for websites, not email."** Increasingly false. EAA explicitly covers email. US case law and DOJ NPRM are moving in the same direction. Treat email like web for compliance purposes.

## Related reading

- [Email Template Design Best Practices](https://acellesend.com/kb/articles/email-template-design-best-practices) — the broader design reference
- [Mobile-First Email Principles](https://acellesend.com/kb/articles/mobile-first-email-principles) — single-column reflow, mobile sizing
- [Dark Mode Email Design](https://acellesend.com/kb/articles/dark-mode-email-design) — Rule 6 deep dive
- [Bulletproof CTA Buttons for Outlook](https://acellesend.com/kb/articles/bulletproof-cta-buttons-for-outlook) — accessible buttons in the hardest client
- [Understanding Email Client Rendering Differences](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences) — why HTML behaves differently per client
- [Email Preheader Text: The Hidden Conversion Booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster) — preheader is the first thing screen readers announce
- [How to Write Email Copy That Converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts) — clarity-first copy is also accessibility-first copy
- [Writing Email for Skimmers](https://acellesend.com/kb/articles/writing-email-for-skimmers) — scannability benefits screen-reader users and visually-able users equally
- [Personalization Beyond First Name](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags) — accessible personalization (avoid "Hi {first_name}" fail cases)

---

# ActiveCampaign → AcelleMail Migration — Visual Walkthrough

> Export contacts from ActiveCampaign, import into AcelleMail in 6 clicks. UI walkthrough plus optional API migration for large accounts.

- Canonical URL: https://acellesend.com/kb/articles/migrating-from-activecampaign-to-acellemail
- Author: Martin Keller, SaaS & Business Editor
- Category: Migration & Comparison — Switching from Mailchimp/Sendinblue, cost comparison, data migration
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 4 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- An ActiveCampaign account with contact-export permission (Admin role)
- An AcelleMail installation up and running
- ~45 minutes for <100k contacts + custom fields + tags

## Export from ActiveCampaign

In ActiveCampaign's left sidebar, click **Contacts** → **Manage Contacts**. Top-right toolbar → **Export**.

The Export dialog asks for filters (all contacts vs. specific list/tag). Pick the scope you want. Format: **CSV**.

ActiveCampaign emails you the file when ready (usually <10 minutes for <100k contacts). The CSV includes: email, first name, last name, phone, all custom fields, tags (comma-separated), list memberships, account-level fields.
## Import into AcelleMail (the same 6 clicks for every source)

Once you have the CSV exported from your previous platform, the import flow in AcelleMail is identical regardless of where the data came from.

### 1. Open your destination list

In AcelleMail's sidebar, click **Audience** → choose the list that will receive the migrated subscribers (or create a new one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

You'll see a per-list overview card with subscriber counts:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the CSV file from the previous platform into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. Adjust manually for any non-standard column from the source:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

In the same screen, choose what AcelleMail does when a subscriber already exists in this list:

- **Skip** — keep the existing row, don't overwrite
- **Update** — overwrite name/tags/custom fields with values from the CSV
- **Unsubscribe** — mark existing rows as unsubscribed (rare; used when re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

You'll see *Pending* → *Running* → *Complete* per import job, with rows-imported / rows-skipped / errors counts.

## After the import

- **Verify list count** matches your expected size (rows that failed validation appear in the *Errors* column with a downloadable error CSV).
- **Re-tag if needed** — for behavioural data that doesn't fit a CSV column (e.g. "opened campaign X"), you may need to re-create the tag via segmentation rules.
- **Pause for warm-up** — if the imported list is large (>10k) and your sending IP is new or recently rotated, run a short warm-up campaign to your most-engaged 10% before the full send. See [IP warm-up best practices](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) for the schedule.

## Rebuilding ActiveCampaign automations

ActiveCampaign's **automations** map to AcelleMail's **Automations**. Equivalent triggers:

| ActiveCampaign trigger | AcelleMail trigger |
|---|---|
| Subscribes to list | Subscribed to list |
| Tag added | Tag added |
| Custom field changes | Field value change |
| Opens/clicks email | Link clicked |
| Date-based field | Date relative |
| Site/event tracking | Custom event (API) |
| Goal achieved | (rebuild as workflow exit) |

ActiveCampaign's "conditional content" inside emails (different blocks per segment) maps to AcelleMail's tag-driven email variants — set up separate campaigns per tag, OR use Liquid-style conditionals in the email body referring to `{{ subscriber.tags }}`.

## Merge-tag syntax differences

| ActiveCampaign | AcelleMail |
|---|---|
| `%FIRSTNAME%` | `{{ subscriber.first_name }}` |
| `%LASTNAME%` | `{{ subscriber.last_name }}` |
| `%EMAIL%` | `{{ subscriber.email }}` |
| `%UNSUBSCRIBELINK%` | `{{ unsubscribe_url }}` |
| `%FORWARDLINK%` | `{{ web_view_url }}` |

<details>
<summary><strong>Advanced: API-driven migration with ActiveCampaign's contacts endpoint</strong></summary>

For >250k contacts or scheduled sync:

**Export from ActiveCampaign via API:**

```bash
# AC API URL + token from Settings → Developer
curl -X GET "https://<account>.api-us1.com/api/3/contacts?limit=100&offset=0" \
  -H "Api-Token: <AC_API_TOKEN>" \
  -o ac-contacts.json
```

Paginate via `offset`; AC returns max 100 per page.

**Import into AcelleMail:**

```bash
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

jq -c '.contacts[] | {email: .email, first_name: .firstName, last_name: .lastName}' ac-contacts.json \
  | while read sub; do
      curl -X POST "https://acellemail.com/api/v1/subscribers" \
        -H "Authorization: Bearer $ACELLE_TOKEN" \
        -d "{\"list_uid\":\"$ACELLE_LIST_UID\",\"subscriber\":$sub}"
    done
```

ActiveCampaign's "deals" / "accounts" CRM features don't have direct AcelleMail equivalents — for CRM-adjacent workflows you'd typically keep ActiveCampaign for CRM + use AcelleMail for marketing email, syncing tags via Zapier or webhook bridges.

</details>

## Related articles

- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Migration from Mailchimp to AcelleMail](https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail)
- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

# ConvertKit (Kit) → AcelleMail Migration — Visual Walkthrough

> Export subscribers from ConvertKit (now Kit), import into AcelleMail in 6 clicks. Visual walkthrough on both sides with API-driven option.

- Canonical URL: https://acellesend.com/kb/articles/migrating-from-convertkit-kit-to-acellemail
- Author: Martin Keller, SaaS & Business Editor
- Category: Migration & Comparison — Switching from Mailchimp/Sendinblue, cost comparison, data migration
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- A ConvertKit (Kit) account with subscriber-export permission
- An AcelleMail installation up and running
- ~30 minutes for <100k subscribers

ConvertKit was rebranded to Kit in 2024. The UI shifted but the export flow remains the same — this guide uses the current Kit screen names.

## Export from Kit (ConvertKit)

In Kit's top nav, click **Subscribers**. Filter by tag, segment, form, or "all subscribers" — whichever scope you want to migrate. Then click **Export** in the toolbar.

Kit asks for format (CSV) and field selection — pick all the custom fields you've defined. The file prepares in the background; **Account → Imports & Exports** shows the download link when ready (usually <5 minutes).

Exported CSV includes: email, first name, all custom fields, tags (comma-separated), confirmed-at timestamp, source.
## Import into AcelleMail (the same 6 clicks for every source)

Once you have the CSV exported from your previous platform, the import flow in AcelleMail is identical regardless of where the data came from.

### 1. Open your destination list

In AcelleMail's sidebar, click **Audience** → choose the list that will receive the migrated subscribers (or create a new one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

You'll see a per-list overview card with subscriber counts:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the CSV file from the previous platform into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. Adjust manually for any non-standard column from the source:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

In the same screen, choose what AcelleMail does when a subscriber already exists in this list:

- **Skip** — keep the existing row, don't overwrite
- **Update** — overwrite name/tags/custom fields with values from the CSV
- **Unsubscribe** — mark existing rows as unsubscribed (rare; used when re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

You'll see *Pending* → *Running* → *Complete* per import job, with rows-imported / rows-skipped / errors counts.

## After the import

- **Verify list count** matches your expected size (rows that failed validation appear in the *Errors* column with a downloadable error CSV).
- **Re-tag if needed** — for behavioural data that doesn't fit a CSV column (e.g. "opened campaign X"), you may need to re-create the tag via segmentation rules.
- **Pause for warm-up** — if the imported list is large (>10k) and your sending IP is new or recently rotated, run a short warm-up campaign to your most-engaged 10% before the full send. See [IP warm-up best practices](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) for the schedule.

## Rebuilding Kit (ConvertKit) automations

Kit's **automations** (formerly called "sequences" + "rules") map to AcelleMail's **Automations**. Equivalent triggers:

| Kit trigger | AcelleMail trigger |
|---|---|
| Subscribes via form | Subscribed to list |
| Tag added | Tag added |
| Clicks link in email | Link clicked |
| Purchases (via integration) | Custom event (API) |
| Date condition | Date relative |
| Form submission | Subscribed to list (form's target list) |

Kit's "Visual Automations" (drag-and-drop sequence builder) closely mirror AcelleMail's automation canvas — the rebuild is largely 1:1 with the same node primitives.

## Merge-tag syntax differences

| Kit (ConvertKit) | AcelleMail |
|---|---|
| `{{ subscriber.first_name }}` | `{{ subscriber.first_name }}` (no change!) |
| `{{ subscriber.email_address }}` | `{{ subscriber.email }}` |
| `{{ subscription.unsubscribe_url }}` | `{{ unsubscribe_url }}` |

Kit uses Liquid templating, very close to AcelleMail's own syntax. Most templates copy-paste with only the `email_address` → `email` rename and unsubscribe-URL rename.

<details>
<summary><strong>Advanced: API-driven migration with Kit's subscribers endpoint</strong></summary>

**Export from Kit via API:**

```bash
# Kit (v4) API key from Account → API & Apps
curl -X GET "https://api.kit.com/v4/subscribers?per_page=1000" \
  -H "Authorization: Bearer <KIT_API_KEY>" \
  -o kit-subscribers.json
```

Paginate via `after` cursor; loop until response `pagination.has_next_page` is false.

**Import into AcelleMail:**

```bash
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

jq -c '.subscribers[] | {email: .email_address, first_name: .first_name}' kit-subscribers.json \
  | while read sub; do
      curl -X POST "https://acellemail.com/api/v1/subscribers" \
        -H "Authorization: Bearer $ACELLE_TOKEN" \
        -d "{\"list_uid\":\"$ACELLE_LIST_UID\",\"subscriber\":$sub}"
    done
```

For Kit's "Commerce" features (paid subscriptions, products), AcelleMail doesn't have direct equivalents — those workflows typically stay on Kit or move to a dedicated subscription tool.

</details>

## Related articles

- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Migration from Mailchimp to AcelleMail](https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Re-engagement campaigns — win back inactive subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# Klaviyo → AcelleMail Migration — Visual Walkthrough

> Export your Klaviyo profiles as CSV, import into AcelleMail via the 6-click wizard. Visual walkthrough plus power-user API path for large Klaviyo accounts.

- Canonical URL: https://acellesend.com/kb/articles/migrating-from-klaviyo-to-acellemail
- Author: Martin Keller, SaaS & Business Editor
- Category: Migration & Comparison — Switching from Mailchimp/Sendinblue, cost comparison, data migration
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- A Klaviyo account with profile-export permission (Owner or Admin role)
- An AcelleMail installation up and running
- ~45 minutes for <100k profiles + tags + custom properties

Klaviyo's profile model is rich (event history, conditional segments, predictive metrics). The CSV migration preserves identity + custom properties; behavioural segments + flows are rebuilt manually in AcelleMail's visual builder.

## Export from Klaviyo

In Klaviyo's left sidebar, click **Audience** → **Lists & Segments** → choose the list to export. On the list page, **Manage List** → **Export List to CSV**.

Klaviyo asks which properties to include — select **All** (or trim down if you have hundreds of custom properties you don't need). The file prepares in the background; **Downloads** sidebar item shows the download link when ready (usually <5 minutes for <100k).

Exported CSV includes: email, all standard + custom profile properties, opt-in date, source, last activity timestamp.

For segment-based migration (e.g. only profiles matching "Engaged in last 30 days"), use **Segments → [segment name] → Export Segment** — same flow, scoped output.
## Import into AcelleMail (the same 6 clicks for every source)

Once you have the CSV exported from your previous platform, the import flow in AcelleMail is identical regardless of where the data came from.

### 1. Open your destination list

In AcelleMail's sidebar, click **Audience** → choose the list that will receive the migrated subscribers (or create a new one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

You'll see a per-list overview card with subscriber counts:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the CSV file from the previous platform into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. Adjust manually for any non-standard column from the source:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

In the same screen, choose what AcelleMail does when a subscriber already exists in this list:

- **Skip** — keep the existing row, don't overwrite
- **Update** — overwrite name/tags/custom fields with values from the CSV
- **Unsubscribe** — mark existing rows as unsubscribed (rare; used when re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

You'll see *Pending* → *Running* → *Complete* per import job, with rows-imported / rows-skipped / errors counts.

## After the import

- **Verify list count** matches your expected size (rows that failed validation appear in the *Errors* column with a downloadable error CSV).
- **Re-tag if needed** — for behavioural data that doesn't fit a CSV column (e.g. "opened campaign X"), you may need to re-create the tag via segmentation rules.
- **Pause for warm-up** — if the imported list is large (>10k) and your sending IP is new or recently rotated, run a short warm-up campaign to your most-engaged 10% before the full send. See [IP warm-up best practices](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) for the schedule.

## Rebuilding Klaviyo flows in AcelleMail

Klaviyo's **flows** map to AcelleMail's **Automations**. Equivalent triggers:

| Klaviyo trigger | AcelleMail trigger |
|---|---|
| Subscribed to list | Subscribed to list |
| Added to segment | Tag added (via segment-to-tag mapping) |
| Metric event (Shopify, custom) | Custom event (API) |
| Profile property change | Field value change |
| Date property matches | Date relative |
| Clicked email link | Link clicked |

For Klaviyo's predictive analytics + AI features (predicted CLV, churn risk), AcelleMail doesn't have direct equivalents — these are Klaviyo-specific advantages. If you depend on them, evaluate whether the cost savings of self-hosting justify rebuilding those signals via your own data pipeline.

## Merge-tag syntax differences

| Klaviyo | AcelleMail |
|---|---|
| `{{ first_name }}` (root scope) | `{{ subscriber.first_name }}` |
| `{{ last_name }}` | `{{ subscriber.last_name }}` |
| `{{ email }}` | `{{ subscriber.email }}` |
| `{{ organization.web_view_url }}` | `{{ web_view_url }}` |
| `{{ organization.unsubscribe_url }}` | `{{ unsubscribe_url }}` |

Klaviyo's Jinja-style conditionals (`{% if profile.tags contains 'vip' %}`) work in AcelleMail's email builder with minor adjustments (use `subscriber.tags` instead of `profile.tags`).

<details>
<summary><strong>Advanced: API-driven migration with Klaviyo's profile export endpoint</strong></summary>

For >250k profiles or for ongoing sync from a CRM-of-record, Klaviyo's API supports bulk export.

**Export from Klaviyo via API:**

```bash
# Klaviyo Private API key from Settings → API Keys
curl -X GET "https://a.klaviyo.com/api/profiles/?page[size]=100&page[cursor]=" \
  -H "Authorization: Klaviyo-API-Key <KLAVIYO_PRIVATE_KEY>" \
  -H "revision: 2024-10-15" \
  -o klaviyo-profiles.json
```

Klaviyo paginates via cursor; loop until `links.next` is null.

**Import into AcelleMail via REST API:**

```bash
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

jq -c '.data[] | {email: .attributes.email, first_name: .attributes.first_name, last_name: .attributes.last_name}' klaviyo-profiles.json \
  | while read sub; do
      curl -X POST "https://acellemail.com/api/v1/subscribers" \
        -H "Authorization: Bearer $ACELLE_TOKEN" \
        -d "{\"list_uid\":\"$ACELLE_LIST_UID\",\"subscriber\":$sub}"
    done
```

For Klaviyo-specific behavioural data (events table, metric aggregates), you'll need to bridge via your own ETL — AcelleMail doesn't have direct event-stream ingestion. The path most teams take: keep the rich behavioural store in your data warehouse (Postgres / BigQuery / Snowflake), use AcelleMail's API to push tag/segment updates based on warehouse-computed signals.

</details>

## Related articles

- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Migration from Mailchimp to AcelleMail](https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail)
- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

# AI-Assisted Email Copy — Without Sounding Like AI

> AI tools can draft your first version 10× faster. They can also make your copy read like a textbook. This guide is the practical pattern: use AI for the draft, then surgically rewrite for your voice. With before/after examples.

- Canonical URL: https://acellesend.com/kb/articles/ai-assisted-email-copy-without-sounding-ai
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Best Practices — Subject lines, send timing, warmup strategies, engagement optimization
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The honest position

AI writing tools (ChatGPT, Claude, Gemini) are excellent for drafting. They're terrible for shipping.

Why? Default AI output has tells: overuse of "exciting," "robust," "leverage." First sentence in passive voice. Multiple adjectives where one would do. Generic openings ("In today's fast-paced world...").

Recipients notice. Engagement drops. Trust erodes over months as readers start to recognize AI-typical phrasing.

The fix: use AI for the 80% draft, then spend the 20% editing time that makes it sound like YOU.

## The 4-step workflow

### Step 1: Brief the AI

Give the AI specific context, not generic prompts:

**Bad:**
> "Write an email promoting our spring sale."

**Better:**
> "Write a 100-word email from Brand to active customers about our spring sale (20% off through March 15). Sound conversational, not corporate. Lead with the benefit. End with one direct CTA. Avoid 'exciting,' 'thrilled,' 'leverage.'"

The brief shapes the output 10×.

### Step 2: Generate the draft

Get 2-3 variations from the AI. Pick the best opening line. Discard the others.

### Step 3: Surgical rewrite

This is where you spend your editing time. The patterns:

| Cut | Why |
|---|---|
| "We are excited to announce..." | Always cut. Recipients don't care if you're excited; they care what's in it. |
| "In today's fast-paced world..." | Bot-tell. Cut entirely. |
| "Robust" / "Leverage" / "Innovative" | Buzzword saturation. Replace with specifics. |
| Multiple sentences with the same structure | Vary sentence length. |
| Adverbs ("very," "really," "extremely") | Usually noise. Cut them. |
| "I hope this email finds you well." | Cut. Always. |

| Add | Why |
|---|---|
| First-person specific ("I noticed...") | Adds humanity that AI never adds |
| Concrete numbers ("47% faster" not "much faster") | Specific = trustworthy |
| Direct second-person ("You can...") | AI defaults to "Our platform helps customers...", which is weak |
| Variable sentence length (short + medium + occasional long) | AI defaults to uniform medium-length |
| Honest acknowledgments ("This is the email I should have sent in February") | AI never does this; instantly humanizes |

### Step 4: Read aloud + Preview

Read the final draft aloud. If anything sounds off, rewrite.

Then click **Preview** in AcelleMail to verify formatting + mobile rendering:

![Preview — desktop](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

Also send a test to yourself for final read.

## Before / after rewrites

### Sale email

**AI draft (unmodified):**
```
Subject: Don't miss our exciting spring sale!

Dear valued customer,

We are thrilled to announce that our highly anticipated spring sale is now live!
For a limited time, you can leverage incredible discounts of up to 20% off
on our innovative product line. This is the perfect opportunity to take
advantage of our cutting-edge solutions and revolutionize your daily routine.

Don't miss out on this exclusive offer! Visit our website today to explore
our wide range of premium products and find the perfect fit for your needs.

Best regards,
The Brand Team
```

**Surgically rewritten:**
```
Subject: 20% off through Friday — your size first

Hey {{ subscriber.first_name | default('there') }},

The spring sale is live. 20% off everything through Friday.

If you've been thinking about that jacket from last month — here's your window.
Shipping is free over $50.

Check what's left in your size →
```

Cuts: "exciting," "thrilled," "leverage," "innovative," "cutting-edge," "revolutionize," "exclusive," "valued customer," "Best regards."

Adds: specific time-bound ("through Friday"), concrete benefit ("Shipping free over $50"), personal acknowledgment ("If you've been thinking..."), direct CTA.

Result: half the length, twice the conversion.

### Onboarding email

**AI draft:**
```
Subject: Welcome to Brand — let's get started!

Hi there,

Welcome to Brand! We're so excited to have you join our community. We're
committed to providing you with the best possible experience and helping you
achieve your goals.

To get the most out of our platform, we recommend completing your profile,
exploring our features, and reaching out to our support team if you have any
questions. Our team is here to help you every step of the way.

We're looking forward to seeing the amazing things you'll accomplish!

Best regards,
The Brand Team
```

**Rewritten:**
```
Subject: One quick step to get going

Hey,

Three things you'll get the most out of, fastest:

1. Connect your first list (15 seconds — paste an email).
2. Send a test campaign (use our default template; takes 30 seconds).
3. Watch the first dashboard report (open rates show up in 4 hours).

Stuck on any of these? Reply to this email; a human reads them.

Cheers, Maya
```

Cuts: "Welcome to Brand," "excited," "amazing things you'll accomplish," every generic phrase.

Adds: 3 numbered concrete actions, time estimates per action, escape hatch ("Reply to this email"), human signature.

## AI prompts that produce better drafts

| Goal | Prompt addition |
|---|---|
| Conversational tone | "Sound like Maya texting a friend, not a CEO emailing investors." |
| Cut buzzwords | "Avoid: exciting, thrilled, leverage, innovative, robust, cutting-edge, revolutionary." |
| Concrete specifics | "Use specific numbers and timeframes. No generalities." |
| Second person | "Lead every paragraph with 'You can...' or 'You'll see...'. Never 'We are excited to...'." |
| Sentence variety | "Vary sentence length. 1 short, 1 medium, 1 long-ish. Then repeat." |
| Honest acknowledgment | "Open with what's NOT working or what surprised you — not what you're proud of." |

## Common AI-output tells (to grep + cut)

| Phrase | Replacement |
|---|---|
| "We are excited to" | (cut entirely) |
| "In today's fast-paced world" | (cut entirely) |
| "It is worth noting" | (cut) |
| "Furthermore" / "Moreover" | "Plus" / "Also" |
| "Thrilled to share" | (cut; just say what you're sharing) |
| "Best-in-class" / "Cutting-edge" | (cut; show specific evidence instead) |
| "Game-changing" | (cut; let the result speak) |
| "Robust solution" | (cut; describe what it does) |
| "Take a moment to" | "Click to" / direct action |
| "We hope you enjoy" | (cut; assumption-language) |
| "Don't hesitate to reach out" | "Reply to this email" |
| "I hope this email finds you well" | (cut; never works) |

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Open rates dropped after switching to AI-drafted subjects | Subjects sound generic / formulaic | Hand-write subjects; use AI only for body draft |
| Replies stopped after switching to AI-drafted welcomes | Tone lost the human signal | Add personal sign-off; restore second-person |
| AI-edited emails get 2-star NPS responses | Detection of AI patterns | Cut the AI-tells; read aloud before sending |
| Long body sections feel like they drone on | AI default = uniform medium-length sentences | Vary sentence length manually |
| Mobile rendering looks great, desktop feels off | AI defaulted to short-mobile-style | Add 1-2 longer sentences for desktop visual rhythm |

## The 80/20 rule

Use AI for:

- ✅ First-draft of body copy (10× faster than blank page)
- ✅ Multiple subject variations to A/B test
- ✅ Translating tone (formal → conversational)
- ✅ Adapting one email for different segments
- ✅ Generating preheader options

Don't use AI for:

- ❌ Final published copy without editing
- ❌ Personalized 1:1 outreach
- ❌ Crisis or apology emails (humans must write)
- ❌ Transactional emails (consistency matters more than creativity)

<details>
<summary><strong>Advanced: prompt engineering for email drafts + segment-aware AI prompts + style-guide auto-application</strong></summary>

**Prompt engineering for email drafts:**

Long-form prompts that produce significantly better drafts:

```
ROLE: You are Maya, the head of growth at Brand, writing to active subscribers.

VOICE: Conversational, direct, occasionally self-deprecating. Sound like
       you're typing a quick message to a friend, not delivering a speech.

GOAL: Promote the spring sale (20% off, through March 15).

CONSTRAINTS:
- 80-150 words maximum
- Single direct CTA at the end
- Open with what's specifically happening (not "exciting")
- Avoid: exciting, thrilled, leverage, innovative, robust
- Use second-person ("You can...") not third-person ("Our customers...")
- One concrete number or timeframe in the body

TONE EXAMPLES:
- "If you've been thinking about it, here's your window."
- "We screwed up. Here's how we're fixing it."
- "This is the email I should have sent last month."

OUTPUT: 2 variations.
```

Compared to "write a sale email," this prompt produces 5-10× better drafts.

**Segment-aware AI prompts:**

```
Engaged segment prompt:
"They opened our last 3 emails. Write a high-trust message that assumes context."

At-risk segment prompt:
"They haven't opened in 60 days. Acknowledge the gap honestly. Restate value briefly. Make re-entry easy."

New segment prompt:
"They signed up <14 days ago. They don't know us yet. Welcome warmly without overwhelming."
```

Same campaign, different copy per segment. AI assists with each.

**Style-guide auto-application:**

Maintain a style-guide document; feed it to AI as system prompt:

```
SYSTEM: Apply the Brand Style Guide:

YES use:
- Second-person ("You can...")
- Concrete numbers ("47% faster")
- Direct CTAs ("Get my 20% discount")
- Personal sign-offs ("Cheers, Maya")

NEVER use:
- "Exciting," "Thrilled," "Leverage"
- "I hope this email finds you well"
- Generic "Best regards"
- Excessive adverbs

Sentence length: Vary. 1 short, 1 medium, 1 long-ish, repeat.
```

The AI applies the rules automatically. Reduces editing time per draft from 20 min to 5 min.

**A/B testing AI-drafted vs human-written:**

For the analytical mind:

```
Campaign A: Pure-AI draft (with editing)
Campaign B: Pure-human written
Same list segment, same offer, same schedule.
```

Compare open rate, click rate, conversion. Most teams find: AI-drafted + edited matches human-written quality at 1/3 the time cost. The edit step is what matters.

**Editing rubric:**

Before sending AI-drafted copy, run through:

1. Cut all "exciting," "thrilled," "leverage"
2. Replace generic numbers with specifics
3. Add ONE honest acknowledgment if not present
4. Vary sentence length manually
5. Read aloud — anything awkward?
6. Verify Subject + body alignment

5 minutes of disciplined editing. Compounds across thousands of sends.

**Multi-language AI drafts:**

For multilingual senders:

```
Generate the same email in:
- English (original)
- Spanish (Latin American variant)
- French (European variant)
- Japanese (business-casual register)

Each variant should sound NATIVE, not translated. Adjust idioms, examples, and sentence structure as needed.
```

Quality varies per language; English is typically strongest, smaller languages weakest. Spot-check each language before sending.

</details>

## Related articles

- [How to write email copy that converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Writing email for skimmers](https://acellesend.com/kb/articles/writing-email-for-skimmers)
- [Email body structure — hook value CTA](https://acellesend.com/kb/articles/email-body-structure-hook-value-cta)

---

# Decoding Bounce Messages — Read the Tracking Log Like a Pro

> Hard bounces, soft bounces, deferred — what AcelleMail's bounce log is actually telling you, and which bounces you can ignore vs. which need action. Read the UI first, dive into the SMTP codes only when needed.

- Canonical URL: https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Troubleshooting — Common errors, debugging, delivery issues, logs, performance
- Content type: reference · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Read the bounce log from the campaign report

AcelleMail tracks every send attempt — including bounces — in the campaign's **Tracking log**. Open any sent campaign → **Bounces** tab:

![Campaign bounce log — hard/soft mix with DSN codes](https://acellesend.com/plugins/acelle/landing/images/help/kb/smtp-error-codes/01-bounce-log-mixed.png)

Each row shows:

- **Recipient email** — who didn't receive
- **Bounce type** — *Hard* (permanent — invalid address, domain doesn't exist) or *Soft* (temporary — mailbox full, server unreachable)
- **DSN reason** — the receiving server's response, in plain-ish English
- **Time** — when AcelleMail tried to deliver

## What AcelleMail does automatically

You don't need to manually clean up most bounces. AcelleMail's bounce handling runs automatically:

- **Hard bounces** are marked as `unsubscribed` after the first occurrence. They're removed from future sends to the same list. **No action needed.**
- **Soft bounces** are retried with backoff (next campaign batch). After 5 consecutive soft bounces, AcelleMail auto-promotes to hard bounce (treats as permanent).
- **Spam complaints** (recipient hit "This is spam") flow through SMTP-vendor webhooks (SES, SendGrid, Mailgun, Postmark) and immediately unsubscribe the subscriber.

The campaign overview shows the totals:

![Campaign overview — Failed count in stats](https://acellesend.com/plugins/acelle/landing/images/help/kb/smtp-error-codes/02-overview-bounce-stats.png)

If **Failed** is <5% of the list, you're fine — that's the industry-typical churn from invalid + abandoned addresses on any list. If **Failed** is >10%, something needs attention (see "When to take action" below).

## When to take action — UI-driven decisions

| What you see in the log | What it means | What to do |
|---|---|---|
| Most bounces say "User unknown" / "Mailbox does not exist" | Stale list — addresses that no longer exist | Normal at <5%. Above that, run **Subscribers → Email verification** before next send. |
| Most bounces say "Mailbox full" / "Quota exceeded" | Temporary issue on the recipient side | Wait — AcelleMail retries automatically. No action. |
| Bounces concentrate on ONE recipient domain (e.g. all @example.com fail) | The receiving server may be blocking your sending IP | Check **Settings → Sending servers** for the IP being used. If multiple domains block you, IP reputation issue. See [Why are my emails going to spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder). |
| Bounces say "Authentication failed" / "DMARC failure" | Your domain authentication (SPF / DKIM / DMARC) isn't configured correctly | Run **Settings → Domains → Verify** and follow the wizard. |
| Bounces growing during a send (started low, spiking) | Your sending IP is being rate-limited mid-send | Sending server warm-up is incomplete OR daily quota exceeded. Wait or split campaign. |

## The Subscribers view — clean up manually if needed

If you want to manually mark certain addresses unsubscribed (e.g. an entire former-customer list), open **Subscribers** in a list, filter by status, and bulk-change:

- **Filter** by status `Failed` to see hard-bounced addresses.
- **Bulk-select** rows → top toolbar → *Change status* → *Unsubscribed*.
- The list count + future sends update immediately; no separate suppression step needed.

## Common UI signals — quick recap

- Failed count <5% of list → ignore, AcelleMail handles it
- Failed count >10% of list → run email verification before next send
- Bounces clustered on one domain → IP/DNS issue, check sending server
- "Authentication failed" bounces → fix SPF/DKIM/DMARC

<details>
<summary><strong>Advanced: SMTP DSN code reference for operators</strong></summary>

SMTP responses follow RFC 3463 (Enhanced Mail System Status Codes). Three digits, separated by dots: `X.Y.Z`.

**Class (X)** — severity:
- `2.X.X` — Success (delivered)
- `4.X.X` — Persistent transient failure (will retry — soft bounce)
- `5.X.X` — Permanent failure (will NOT retry — hard bounce)

**Subject (Y)** — what failed:
- `X.1.X` — Addressing status (recipient address)
- `X.2.X` — Mailbox status (full, disabled, doesn't exist)
- `X.3.X` — Mail system status (overloaded, down)
- `X.4.X` — Network and routing status
- `X.5.X` — Mail delivery protocol status
- `X.6.X` — Message content or media status
- `X.7.X` — Security or policy status (authentication, blacklist, DMARC)

**Common codes you'll see:**

| Code | Meaning | Action |
|---|---|---|
| `2.0.0` | Message accepted for delivery | Success — no bounce |
| `4.2.2` | Mailbox full | Soft — retried automatically |
| `4.7.0` | General temporary failure (often greylisting) | Soft — retried |
| `5.1.1` | User unknown (recipient address doesn't exist) | Hard — auto-unsubscribe |
| `5.1.2` | Domain doesn't exist | Hard — auto-unsubscribe |
| `5.2.0` | Mailbox issue (suspended, archived) | Hard |
| `5.2.1` | Mailbox disabled | Hard |
| `5.7.1` | Delivery not authorized (blocked) — common when sender on blocklist | Hard — investigate IP reputation |
| `5.7.26` | DMARC failure (alignment policy) | Hard — fix DKIM/SPF setup |

**Reading the full DSN** in the log:

The "DSN reason" column shows the full response string from the receiving server, e.g.:

```
550 5.1.1 <bob@example.com>: Recipient address rejected: User unknown in local recipient table
```

- `550` — SMTP-level rejection code (matches `5.X.X` class)
- `5.1.1` — Enhanced status code (RFC 3463)
- The text after — server's human-readable reason

**Querying the bounce log from the database** for offline analysis:

```bash
php artisan tinker --execute='
  \App\Model\BounceLog::where("campaign_id", <id>)
    ->where("dsn_status", "like", "5.7%")
    ->select("recipient", "dsn_status", "diagnostic_code")
    ->get()
    ->each(fn($r) => print("{$r->recipient}\t{$r->dsn_status}\t{$r->diagnostic_code}\n"));
'
```

**Filtering FBL (Feedback Loop) entries** — recipient marked your message as spam:

```bash
php artisan tinker --execute='
  \App\Model\FeedbackLog::orderByDesc("created_at")
    ->limit(50)->get()
    ->each(fn($r) => print("{$r->recipient}\t{$r->feedback_type}\t{$r->created_at}\n"));
'
```

When the FBL volume rises sharply, your IP reputation is in jeopardy — pause campaigns to that segment, investigate content + frequency.

</details>

## Related articles

- [Campaign stuck in Sending status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status)
- [Why are my emails going to spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder)
- [Email list hygiene — clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Debugging common email delivery issues](https://acellesend.com/kb/articles/debugging-common-email-delivery-issues)

---

# Email Body Structure — The Hook · Value · CTA Framework

> A simple 3-part framework that scales from 50-word notes to 500-word deep-dives. Hook earns attention. Value delivers the promise. CTA gets the action. This guide walks each piece with examples and AcelleMail builder integration.

- Canonical URL: https://acellesend.com/kb/articles/email-body-structure-hook-value-cta
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Best Practices — Subject lines, send timing, warmup strategies, engagement optimization
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The framework

Every effective email body has three components:

```
[Hook]
  Why should I keep reading?

[Value]
  What's in it for me?

[CTA]
  What do you want me to do next?
```

Get all three right → conversion happens. Miss any one → recipient drops off.

This framework scales:

- 50-word transactional confirmation → 1 hook line, 1 value line, 1 CTA
- 200-word promotional → 1 hook paragraph, 2-3 value paragraphs, 1 CTA
- 500-word deep dive → 1 hook paragraph, multi-section value, multiple touchpoint CTAs converging on 1 primary

## Component 1: Hook

The first 1-2 lines after the subject decide if the reader keeps reading.

### Hook patterns

| Pattern | Use | Example |
|---|---|---|
| **Concrete number** | Educational | "We analyzed 1,847 campaigns. Only 12 hit >50% opens. Here's what they share." |
| **Personal admission** | Story / opinion | "I almost canceled our marketing budget last quarter." |
| **Direct question** | Thought-provoking | "What if your campaigns sent themselves?" |
| **Reference behavior** | Behavioral | "You clicked on pricing last week. I think this is what you wanted." |
| **News with stakes** | Announcement | "We shipped automation triggers — finally." |
| **Empathetic** | Support / re-engagement | "We noticed you haven't engaged in a while. No pressure." |

### Hook anti-patterns

| Mistake | Why it fails |
|---|---|
| Generic greeting ("Hi {{ subscriber.first_name }}!") | Wastes the most valuable opening line |
| Long preamble ("Before we get into it...") | Skimmer scrolls past |
| Topic-first vs benefit-first ("Today we're going to discuss X") | Recipient doesn't care about your agenda |
| Multiple ideas crammed into the first line | Confusion = bounce |

### Hook length

1-2 lines. 15-30 words. Read out loud — should feel natural, not stilted.

## Component 2: Value

The middle of the email delivers what the hook promised.

### Value structure options

**Option A: Sequential reveal**
```
Hook → "We analyzed 1,847 campaigns"
Value → 3 specific findings, each in its own paragraph
CTA → Read the full analysis
```

**Option B: Story / case study**
```
Hook → "I almost canceled our marketing budget"
Value → Story (what happened, what changed, what worked)
CTA → Apply this to your own campaigns
```

**Option C: Numbered framework**
```
Hook → "5 things to never put in your subject line"
Value → 5 numbered items with brief explanation each
CTA → Read the full list with examples
```

**Option D: Direct benefit + supporting evidence**
```
Hook → "Get 20% off through Friday"
Value → What the offer includes; social proof; deadline reminder
CTA → Shop the sale
```

### Value patterns that work

| Pattern | Tip |
|---|---|
| **Subheadings every 100-150 words** | Helps skimmers; signals structure |
| **Bold first sentence per paragraph** | Skimmer reads these |
| **Specific numbers/timeframes** | Concrete > abstract |
| **Second-person language** | "You can save..." beats "Customers save..." |
| **One core idea per paragraph** | Multiple ideas in one paragraph = confusion |
| **White space between sections** | Lets the eye rest |

### Value length

| Email type | Word count |
|---|---|
| Transactional | 30-80 |
| Promotional | 80-200 |
| Newsletter | 150-400 |
| Deep-dive | 300-600 |
| Long-form essay | 500+ |

Front-load the most important value. If you cut after the first 100 words, the email should still be coherent.

## Component 3: CTA

The action you want them to take. Single primary CTA per email.

### CTA patterns that work

| Pattern | Example |
|---|---|
| **Direct command** | "Get my 20% discount" |
| **First-person** | "I want to try this" |
| **Question** | "What does this look like in practice?" |
| **Numbered benefit** | "Save $40 in the next 7 days" |
| **Time-bound** | "Lock in pricing before Friday" |
| **No-friction** | "Watch the 60-second demo" |

### CTA anti-patterns

| Anti-pattern | Why it fails |
|---|---|
| "Click here" | Vague; no value |
| "Learn more" | Boring; lacks urgency |
| "Submit" | Robotic |
| "Read more →" | Most effective only when content is good |
| 3+ equal CTAs | Dilutes focus; click rate drops vs single CTA |
| Buried CTA | Skimmer misses |

### CTA design in AcelleMail

In the template builder, add a Button block (not just a link). Configure:

- **Button text**: 2-5 word direct action
- **Button color**: high contrast (typically brand primary)
- **Width**: full-width or 60-70% width (mobile-friendly)
- **Height**: 48px+ (touch-tappable)
- **Position**: end of email, with white space above

![Template editor with button](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/05-template.png)

Verify in Preview:

![Preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

Mobile too:

![Mobile preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

CTA button should be clearly tappable on mobile.

### Multiple CTAs (when allowed)

Sometimes you have a primary CTA + a fallback:

```
[Get my 20% discount →]  ← Primary

[or watch the 60-second demo]  ← Fallback link, less prominent
```

Primary CTA = button. Fallback = text link. Visual hierarchy makes clear which is the main ask.

## Putting it all together

### 50-word transactional confirmation

```
Hook:    Your password was reset successfully.
Value:   You're back in your account. The reset email expires in 30 minutes for security.
CTA:     [Sign in to your account →]
```

### 100-word promotional

```
Hook:    Spring sale: 20% off through Friday.

Value:   Every product in our catalog gets a 20% discount applied at checkout.
         No code needed — it's automatic. Free shipping on orders over $50.

CTA:     [Shop the sale →]
```

### 200-word newsletter

```
Hook:    We analyzed 1,847 campaigns. Only 12 hit >50% open rate. Here's what they share.

Value:   **All 12 used personalized subject lines.** Either first-name (8/12) or
         behavioral reference (4/12). Generic subjects underperformed by 18 percentage points.

         **10/12 sent on Tuesday or Wednesday morning.** Weekend sends and afternoon
         sends correlated with lower opens. Weekday morning wins consistently.

         **9/12 had subjects under 50 characters.** Long subjects truncate in mobile
         inbox previews. Short subjects survive truncation.

         The pattern: personalize + time it right + keep it short.

CTA:     [Read the full case study →]
```

### 350-word case study

```
Hook:    I almost canceled our marketing budget last quarter.

Value:   Three months ago I looked at our open rates over 6 quarters. They'd
         dropped from 28% to 19% — a quiet, sustained decline.

         I almost decided to slash the budget. Then I tried something
         different: I stopped writing for our marketing team and started
         writing for our most engaged customers specifically.

         **What changed:**

         Cut sends by 40%. Three campaigns/week → one campaign/week.
         The "every-quiet-week-needs-an-email" pressure dropped.

         **Targeted the engaged segment for the first 2 sends each week.**
         Engagement-30d-3+ subscribers got my best content. They re-engaged.
         The at-risk segment got the same content one week later (a "ICYMI"
         resend).

         **Wrote in first-person, not branded.** "I noticed..." vs "Our team
         is excited to announce..."

         **Cut subject lines to <50 chars.** "Spring sale: 20% off" became
         "20% off through Friday."

         The Q4 results: open rate climbed back to 26%. Click rate hit 7.2%
         (up from 4.1%). Unsubscribes dropped 30%.

         What I'd do differently next time: I'd test these changes BEFORE
         the budget panic. The decline was real, the panic was premature,
         and the solution was structural — not budget.

CTA:     [Read how we redesigned our automation flows →]
```

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Open strong, click weak | Hook overpromises; body underdelivers | Tighten body alignment |
| Open weak, click strong (of opens) | Body is great, subject is mediocre | Pull body's strongest line into subject |
| Replies asking "what is this about?" | Body buried the message | Re-write with sharper hook + clearer value |
| CTA gets <2% clicks | CTA copy is weak or button is too small | Test direct action verbs; bigger button |
| 3 CTAs perform worse than 1 | Decision paralysis | Single primary CTA |
| Long email gets <40% engagement read-through | Recipient abandoned mid-way | Cut to half length; tighten value section |

## The structure isn't the message

Structure helps. But structure without a real message = polished emptiness.

Before formatting:

1. What's the ONE thing this email needs to communicate?
2. Why should the recipient care?
3. What do you want them to DO?

Answer these in plain language. Then apply Hook · Value · CTA structure.

<details>
<summary><strong>Advanced: hook · value · CTA matrix by email type + multi-stage CTA + measuring component effectiveness</strong></summary>

**Matrix by email type:**

| Email type | Hook strategy | Value strategy | CTA strategy |
|---|---|---|---|
| **Newsletter** | Curiosity gap / story opening | 3-5 brief sections, each with own subhead | Single primary "read more" + text link to archives |
| **Promotional** | Direct benefit | Restate offer + deadline + social proof | Action button "Shop the sale" |
| **Transactional** | Quick confirmation | Specifics (date, amount, next step) | Specific action ("View receipt") |
| **Re-engagement** | Empathetic | Brief restate + 2 options | Two-button "keep" or "pause" |
| **Welcome** | Welcome + specific actionable | 3-step onboarding | "Get started" |
| **Apology / recovery** | Direct + honest | What happened + what's changed | "Take another look" |

**Multi-stage CTA:**

Long-form emails benefit from progressive CTAs:

```
Hook
[CTA #1: "Read the analysis" — text link]

Value section 1
[CTA #2: "See the data" — inline link]

Value section 2
[CTA #3: "Get the spreadsheet" — text link]

End
[CTA #4 PRIMARY: "Read the full case study" — button]
```

Multiple touchpoint CTAs. All converge on the same destination but match user's reading position. Increases overall click-through 15-30% vs single-CTA.

**Measuring component effectiveness:**

If your install supports per-element click tracking:

- Subject line A/B → measure opens
- Hook A/B → measure read-through (proxy: time spent in email)
- Value section A/B → measure click rate from middle of email
- CTA A/B → measure click-through on the button

Discrete signals per component. Most senders can only realistically test subject + body holistically; per-component requires sophisticated tracking.

**Per-segment Hook · Value · CTA tuning:**

```
Engaged segment:    Curiosity hook + long-form value + low-friction CTA
At-risk segment:    Empathetic hook + brief value + high-clarity CTA
New segment:        Welcome hook + 3-step value + onboarding CTA
```

Same framework, different tones per segment. AcelleMail's Liquid templating supports this in body; separate campaigns is easier.

**The "single-screen" test:**

Modern email clients show roughly 600-800 pixels of body before scrolling. Your hook + first paragraph + at least one CTA should fit in this first screen.

```
[Subject + preheader visible in inbox preview]
↓ Open email ↓
[Hook]
[First value paragraph]
[Visual element — image, divider]
[First touchpoint CTA]

← First screen of body ↑
↓ Scrolling reveals ↓
[More value paragraphs]
[Primary CTA button]
```

If your CTA only appears after 3 screens of scrolling, you're losing engagement at each scroll boundary.

**Word-count discipline:**

After writing, count words per component:

```
Hook: 25 words
Value: 145 words
CTA: 3 words (button text)
Total: 173 words
```

Target ratios:

- Hook: 10-20% of total
- Value: 75-85% of total
- CTA: <5% of total

If hook is dominant (>30%), trim it. If value is bloated (>90%), tighten or break into multiple sends.

**Reading-aloud test:**

Before sending, read your final draft aloud. Notice:

- Anywhere you stumble = rewrite that sentence
- Anywhere you skip ahead = that section can be cut
- Anywhere you slow down to understand = clarify
- The CTA button text — does it flow as a natural conclusion?

The voice of your writing should match the voice of how you'd speak.

</details>

## Related articles

- [How to write email copy that converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)
- [Writing email for skimmers](https://acellesend.com/kb/articles/writing-email-for-skimmers)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Email preheader text — the hidden conversion booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster)
- [Email template design best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)

---

# Brevo (Sendinblue) → AcelleMail Migration — Visual Walkthrough

> Export contacts from Brevo, upload to AcelleMail in 6 clicks. UI walkthrough on both sides plus optional API-driven migration for large lists.

- Canonical URL: https://acellesend.com/kb/articles/migrating-from-brevo-sendinblue-to-acellemail
- Author: Martin Keller, SaaS & Business Editor
- Category: Migration & Comparison — Switching from Mailchimp/Sendinblue, cost comparison, data migration
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 4 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- A Brevo account with contact-list export permission
- An AcelleMail installation up and running
- ~30 minutes for <50k contacts

## Export from Brevo

In Brevo's left sidebar, click **Contacts** → **Lists** → choose the list to export. On the list detail page, click **Export contacts** in the top-right.

Brevo asks for export format — pick **CSV**. The file is prepared in the background; check **Export history** for the download link (usually <2 minutes).

The exported CSV includes: email, all custom attributes (FIRSTNAME, LASTNAME, custom fields), list memberships, opt-in date, blacklist status.

For attribute mapping, note Brevo's field names — they map to AcelleMail's custom fields in the next step.
## Import into AcelleMail (the same 6 clicks for every source)

Once you have the CSV exported from your previous platform, the import flow in AcelleMail is identical regardless of where the data came from.

### 1. Open your destination list

In AcelleMail's sidebar, click **Audience** → choose the list that will receive the migrated subscribers (or create a new one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

You'll see a per-list overview card with subscriber counts:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the CSV file from the previous platform into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. Adjust manually for any non-standard column from the source:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

In the same screen, choose what AcelleMail does when a subscriber already exists in this list:

- **Skip** — keep the existing row, don't overwrite
- **Update** — overwrite name/tags/custom fields with values from the CSV
- **Unsubscribe** — mark existing rows as unsubscribed (rare; used when re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

You'll see *Pending* → *Running* → *Complete* per import job, with rows-imported / rows-skipped / errors counts.

## After the import

- **Verify list count** matches your expected size (rows that failed validation appear in the *Errors* column with a downloadable error CSV).
- **Re-tag if needed** — for behavioural data that doesn't fit a CSV column (e.g. "opened campaign X"), you may need to re-create the tag via segmentation rules.
- **Pause for warm-up** — if the imported list is large (>10k) and your sending IP is new or recently rotated, run a short warm-up campaign to your most-engaged 10% before the full send. See [IP warm-up best practices](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) for the schedule.

## Rebuilding workflows

Brevo's "automations" map to AcelleMail's **Automation** workflows. Equivalent triggers:

| Brevo trigger | AcelleMail trigger |
|---|---|
| Subscribed to list | Subscribed to list |
| Custom attribute change | Field value change |
| Date attribute matches | Date relative |
| Webhook event | Custom event (API) |
| Email opened/clicked in campaign | Link clicked |

Re-create each automation via **Automations → New automation** in AcelleMail — same drag-and-drop builder, same step types.

## Merge-tag syntax differences

Brevo uses `{{contact.FIRSTNAME}}`; AcelleMail uses `{{ subscriber.first_name }}`. Find/replace patterns:

| Brevo | AcelleMail |
|---|---|
| `{{contact.FIRSTNAME}}` | `{{ subscriber.first_name }}` |
| `{{contact.LASTNAME}}` | `{{ subscriber.last_name }}` |
| `{{contact.EMAIL}}` | `{{ subscriber.email }}` |
| `{{mirror}}` | `{{ web_view_url }}` |
| `{{unsubscribe}}` | `{{ unsubscribe_url }}` |

<details>
<summary><strong>Advanced: API-driven migration for large lists</strong></summary>

For lists >500k or scripted CI-driven sync, both platforms expose REST APIs.

**Export from Brevo via API:**

```bash
curl -X GET "https://api.brevo.com/v3/contacts?limit=1000&offset=0" \
  -H "api-key: <BREVO_API_KEY>" \
  -o brevo-contacts.json
```

Brevo's API returns 1000-row pages; paginate via `offset`.

**Import into AcelleMail via REST API:**

```bash
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

jq -c '.contacts[] | {email: .email, first_name: .attributes.FIRSTNAME, last_name: .attributes.LASTNAME}' brevo-contacts.json \
  | while read subscriber; do
      curl -X POST "https://acellemail.com/api/v1/subscribers" \
        -H "Authorization: Bearer $ACELLE_TOKEN" \
        -d "{\"list_uid\":\"$ACELLE_LIST_UID\",\"subscriber\":$subscriber}"
    done
```

The CSV-through-UI path is faster for one-time migration; API path is for scheduled sync.

</details>

## Related articles

- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Migration from Mailchimp to AcelleMail](https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Email list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)

---

# MailerLite → AcelleMail Migration — Visual Walkthrough

> Export subscribers from MailerLite, import into AcelleMail in 6 clicks. Visual walkthrough on both sides with API-driven option for large lists.

- Canonical URL: https://acellesend.com/kb/articles/migrating-from-mailerlite-to-acellemail
- Author: Martin Keller, SaaS & Business Editor
- Category: Migration & Comparison — Switching from Mailchimp/Sendinblue, cost comparison, data migration
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 4 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- A MailerLite account with subscriber-export permission
- An AcelleMail installation up and running
- ~30 minutes for <100k subscribers

## Export from MailerLite

In MailerLite's left sidebar, click **Subscribers** → choose the group/list to export → **Export** in the toolbar.

MailerLite asks for format (CSV) and which fields to include — select all custom fields you've defined. The file prepares in the background; check **Subscribers → Imports / Exports** for the download link (usually <2 minutes).

Exported CSV includes: email, name, all custom fields, signup date, opt-in source, segments memberships.
## Import into AcelleMail (the same 6 clicks for every source)

Once you have the CSV exported from your previous platform, the import flow in AcelleMail is identical regardless of where the data came from.

### 1. Open your destination list

In AcelleMail's sidebar, click **Audience** → choose the list that will receive the migrated subscribers (or create a new one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

You'll see a per-list overview card with subscriber counts:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the CSV file from the previous platform into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. Adjust manually for any non-standard column from the source:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

In the same screen, choose what AcelleMail does when a subscriber already exists in this list:

- **Skip** — keep the existing row, don't overwrite
- **Update** — overwrite name/tags/custom fields with values from the CSV
- **Unsubscribe** — mark existing rows as unsubscribed (rare; used when re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

You'll see *Pending* → *Running* → *Complete* per import job, with rows-imported / rows-skipped / errors counts.

## After the import

- **Verify list count** matches your expected size (rows that failed validation appear in the *Errors* column with a downloadable error CSV).
- **Re-tag if needed** — for behavioural data that doesn't fit a CSV column (e.g. "opened campaign X"), you may need to re-create the tag via segmentation rules.
- **Pause for warm-up** — if the imported list is large (>10k) and your sending IP is new or recently rotated, run a short warm-up campaign to your most-engaged 10% before the full send. See [IP warm-up best practices](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) for the schedule.

## Rebuilding MailerLite automations

MailerLite's **automation workflows** map to AcelleMail's **Automations**. Equivalent triggers:

| MailerLite trigger | AcelleMail trigger |
|---|---|
| Joins group | Subscribed to list |
| Updates field | Field value change |
| Anniversary of date | Date relative |
| Completes a form | Subscribed to list (form's target list) |
| Clicks a specific link | Link clicked |

## Merge-tag syntax differences

| MailerLite | AcelleMail |
|---|---|
| `{$name}` | `{{ subscriber.first_name }}` |
| `{$last_name}` | `{{ subscriber.last_name }}` |
| `{$email}` | `{{ subscriber.email }}` |
| `{$url_unsubscribe}` | `{{ unsubscribe_url }}` |
| `{$url_webversion}` | `{{ web_view_url }}` |

<details>
<summary><strong>Advanced: API-driven migration with MailerLite's subscribers endpoint</strong></summary>

For large MailerLite accounts or scheduled sync:

**Export from MailerLite via API:**

```bash
# MailerLite v2 API token from Integrations → API
curl -X GET "https://connect.mailerlite.com/api/subscribers?limit=100&page=1" \
  -H "Authorization: Bearer <MAILERLITE_API_TOKEN>" \
  -H "Accept: application/json" \
  -o mailerlite-subscribers.json
```

Paginate via `page`; MailerLite returns max 100/page.

**Import into AcelleMail:**

```bash
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

jq -c '.data[] | {email: .email, first_name: .fields.name, last_name: .fields.last_name}' mailerlite-subscribers.json \
  | while read sub; do
      curl -X POST "https://acellemail.com/api/v1/subscribers" \
        -H "Authorization: Bearer $ACELLE_TOKEN" \
        -d "{\"list_uid\":\"$ACELLE_LIST_UID\",\"subscriber\":$sub}"
    done
```

</details>

## Related articles

- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Migration from Mailchimp to AcelleMail](https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Email list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)

---

# Re-Engagement Copy That Doesn't Feel Desperate

> Subscribers go quiet. Most senders panic — sending escalating "we miss you!!!" emails. The truth: desperate-feeling re-engagement fails 5× more than honest, confident re-engagement. This guide is the calm-and-confident pattern.

- Canonical URL: https://acellesend.com/kb/articles/re-engagement-copy-that-doesnt-feel-desperate
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Best Practices — Subject lines, send timing, warmup strategies, engagement optimization
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The desperate pattern (don't do this)

```
Subject: 😢 We MISS you!!! Come back???

Hi {{ subscriber.first_name }},

We noticed you haven't opened our emails in a while. We're so sad! 😢

We've been working so hard to bring you amazing content, and we'd hate for
you to miss out! Please please come back to us. We promise we'll only send
you the best of the best!

Click below to confirm you're still interested! ❤️❤️❤️

[I'M STILL HERE!!!]

Don't leave us! 🥺
```

What this signals to the recipient:

- You're desperate (low confidence)
- You're emotionally manipulative (emoji manipulation)
- You're not respecting their time
- You're more focused on your needs than theirs

Response: most subscribers either ignore or unsubscribe. The very thing you're trying to prevent.

## The calm-confident pattern (do this)

```
Subject: One thing before we stop sending you emails

Hey {{ subscriber.first_name | default('there') }},

You haven't opened our emails in 3 months. That's fine — life happens.

Before we stop sending, two options:

1. Keep us — click below to confirm you're still interested.
2. Take a break — we'll re-introduce ourselves in 6 months.

We won't email again either way. No pressure.

[Yes, keep me on the list]

Cheers,
Maya at Brand
```

What this signals:

- You respect their time
- You're confident in your value
- You're explicit about what happens next
- You're not manipulating

Response: 30-50% click "keep me on the list." The rest opt out cleanly (clean list = better future deliverability).

## The framework: Acknowledge · Restate · Easy out

```
[Acknowledge]
  └─ Honestly: "You haven't opened in X months."

[Restate value]
  └─ Briefly: "Our digest covers X, Y, Z."

[Easy out + easy stay]
  └─ Two clear options. Either is fine.
```

Three steps. Under 100 words. Single CTA. No emoji. No exclamation marks.

## Where to write this in AcelleMail

Open the campaign builder. Set:

- Recipients: segment "no-open in last 90 days" (or whatever your inactivity threshold)
- Subject: short, honest, opinion-led
- Body: the framework above
- Schedule: send during recipient business hours

The template editor:

![Template editor](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/05-template.png)

Write directly into a text block. Keep formatting minimal — your message doesn't need styling to land.

Verify in Preview:

![Preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

Read it aloud. If you'd cringe receiving it, rewrite.

## Subject lines for re-engagement

| Avoid | Try instead |
|---|---|
| "😢 We miss you!!! 😢" | "One thing before we stop emailing you" |
| "WE WANT YOU BACK!" | "A quick check-in" |
| "Last chance!!! Come back!" | "We're going to stop sending — unless..." |
| "Are we breaking up? 💔" | "Should we keep emailing you?" |
| "PLEASE don't leave!" | "Two questions for you" |

The pattern: factual, honest, single-thought subject. No emoji. No exclamation.

## Body patterns

### Pattern A: The honest gap

```
You haven't opened in 3 months. We're going to stop sending after this one
unless you tell us otherwise.

If our weekly digest is still useful, click below.
If not, no worries — we'll respectfully stop.

[Yes, keep sending]
```

Tight. Two sentences of acknowledgment. Single CTA. Direct.

### Pattern B: The restate + ask

```
We send one email per week — a curated 5-minute read on email marketing.
That's it. No promotions, no manipulation, no urgency theater.

If that's still valuable to you, confirm below.
If not, just ignore this — you'll come off our list automatically.

[I'd still like the weekly]
```

Restates what they signed up for. Names what you DON'T do. Easy out via inaction.

### Pattern C: The "two choices" pattern

```
We've noticed you haven't opened lately. Two things:

1. Keep getting our weekly: click below.
2. Pause for 6 months: click "pause" — we'll check back in.

Either is great. Whatever fits your life right now.

[Keep emails coming] | [Pause for 6 months]
```

Two-way choice. Removes the binary "stay or quit" pressure.

### Pattern D: The "what changed?"

```
What changed in the last 3 months?

If it's us — our content drifted from what you signed up for — would love
to hear what you'd prefer.

If it's you — life got busy — we get it. No need to explain. Click here
when you're ready to engage again.

Reply with your thoughts, or hit the button below to keep us in your inbox.

[Stay subscribed]
```

Honest curiosity. Two-way conversation. Often gets meaningful replies.

## What NOT to do

| Anti-pattern | Better |
|---|---|
| "Come back to us 🥺" | "Click here to stay subscribed" |
| Multiple consecutive re-engagement emails | One email; respect the response (any) |
| Discounts as last-ditch | Don't discount your way back; quality content wins |
| Subject starting with "Re:" or "Fwd:" (fake-reply) | Honest direct subject |
| "Important! Action required!" urgency manipulation | Direct: "Two options below" |
| Long emotional appeals | Under 100 words; respect their time |
| Imagery / illustrations | Text-only often works better here |

## Cadence guidance

| Days since last open | Action |
|---|---|
| 30 | Continue normal sending; no special treatment |
| 60 | Continue; maybe reduce frequency for this segment |
| 90 | Send ONE re-engagement email |
| 100 (after re-engage send + no response) | Set segment to "low-engagement" — reduce send frequency 50% |
| 180 | Send SECOND re-engagement email (last attempt) |
| 200+ | Auto-unsubscribe / sunset segment |

ONE re-engagement email is the cap. Two is the maximum. Three is desperate.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Re-engagement opens at <5% | Subject too desperate / emotional | Try pattern A subjects |
| Click rate <2% on re-engagement | Body too long / too emotional | Cut to <80 words; remove emoji |
| High unsubscribe on re-engagement (good signal!) | List cleansing successfully | Expected; clean list = better future delivery |
| Replies arrive saying "stop emailing me" | Email made them feel pressured | Pattern is too emotional; switch to calm tone |
| Reactivation rate <10% | Audience genuinely gone | Accept it; sunset the segment |
| Subscribers click "keep me" but never open again | Subject worked, body weak | Revisit the offer / value proposition |

## Anti-patterns

| Mistake | Why it hurts |
|---|---|
| Sending the same re-engagement 4× | Annoying; complaint risk |
| Free shipping / discount in the re-engagement | Trains audience to wait for discounts |
| Manipulative urgency ("Last chance!") | Recipients see through it |
| Mass-emailing the entire inactive base | Sending to confirmed-inactive damages reputation; pre-clean first |
| Re-engagement as a sales pitch | Wrong moment; sales pitch should come AFTER re-engagement confirms |

<details>
<summary><strong>Advanced: re-engagement automation + per-segment re-engagement copy + measuring true reactivation</strong></summary>

**Re-engagement automation:**

Build a sunset workflow in AcelleMail:

```
Trigger: subscriber's last open >90 days ago

Step 1: Tag as 'inactive-90d'
Step 2: Send re-engagement email A (calm tone)
Step 3: Wait 14 days
Step 4: Condition: did they open?
  YES → un-tag inactive; restore normal cadence
  NO → tag as 'inactive-confirmed'

Step 5: Reduce normal send frequency 50% for inactive-confirmed

Step 6: Wait 60 days
Step 7: Send re-engagement email B (last chance, still calm)
Step 8: Wait 14 days
Step 9: Did they open?
  YES → reactivate
  NO → soft-unsubscribe (auto-sunset)
```

Set this up once. Handles the lifecycle automatically.

**Per-segment re-engagement copy:**

Different segments → different re-engagement message:

```
High-value-customers segment:
  "We noticed you haven't engaged in 90 days. As one of our top customers,
   we wanted to check in. What can we do better?"

New-but-quiet segment:
  "You signed up 3 months ago and haven't opened. Want a quick reset?
   Click below for the welcome series, restart fresh."

Lapsed-subscriber segment:
  "It's been 6 months. We'd love to know what changed — or just hear that
   you're no longer interested."
```

Per-segment messaging respects the subscriber's specific journey. Improves response rates.

**Measuring true reactivation:**

A "reactivated" subscriber clicked the keep-me button. The real metric: did they engage in the NEXT campaign?

```
30 days post-re-engagement: did they open the next campaign? (60% expected)
60 days post-re-engagement: did they engage 2+ campaigns? (40% expected)
180 days post-re-engagement: are they still opening monthly? (25% expected)
```

If reactivation drops sharply between 30 and 180 days, the re-engagement worked initially but didn't restore long-term habit. Revisit content quality.

**Reactivation rate benchmarks:**

| Reactivation rate | Verdict |
|---|---|
| 5-15% | Industry baseline |
| 15-30% | Good; copy + timing working |
| 30-50% | Excellent; you have a strong audience |
| >50% | Suspicious — verify metric calculation (often a methodology error) |

**Sunset segment management:**

After re-engagement fails (no opens despite multiple attempts), sunset the subscriber:

- Set status to `bounced` or `unsubscribed`
- Stop all future sends
- Keep the row in DB for compliance audit

Some platforms auto-sunset after 180 days no-open. AcelleMail has admin-side auto-sunset config.

**Win-back vs re-engagement:**

Distinction:

- **Re-engagement**: subscriber is INACTIVE (haven't opened) — try to get them to open
- **Win-back**: customer has CHURNED (canceled subscription / hasn't ordered) — try to get them to buy again

Different copy patterns. Re-engagement is about restoring engagement habit. Win-back is about restoring revenue. Use distinct campaigns.

**Cross-channel re-engagement:**

Email re-engagement failing? Try other channels:

- Push notification ("Open the app this week to keep your account active")
- SMS ("Hey {{ first_name }}, we'll stop emailing you unless you click here")
- Postal mail (high-value lapsed customers; physical mail breaks through digital noise)

Cross-channel re-engagement often outperforms email-only. Worth it for high-value lapsed customers.

</details>

## Related articles

- [Re-engagement campaigns — win back inactive subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)
- [How to write email copy that converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Managing subscriber preferences and frequency](https://acellesend.com/kb/articles/managing-subscriber-preferences-and-frequency)

---

# Subject Line Formulas That Consistently Work — The Testing Discipline

> Stop guessing which formula will work. Test 2-3 per campaign + accumulate evidence. After 10-20 sends, you'll know YOUR audience's sweet spot. This guide is the testing discipline + result interpretation.

- Canonical URL: https://acellesend.com/kb/articles/subject-line-formulas-that-consistently-work
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Best Practices — Subject lines, send timing, warmup strategies, engagement optimization
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The myth: "best subject lines"

Online articles list "the 50 best subject lines ever." Don't read them.

There is no universal "best" — your audience is different from theirs, your product is different, your timing is different. The patterns that worked for them might not work for you.

What DOES work: test your audience systematically. Accumulate evidence. Build your own "what works for our audience" pattern doc.

## The discipline

For every campaign with >5,000 subscribers:

```
1. Pick ONE formula hypothesis to test (e.g. "urgency increases opens for our audience")
2. Write 2 subject variants embodying the hypothesis
3. A/B test (20% sample, 4-hour decision window)
4. Record the winner + the lift in your testing log
5. After 10 tests, identify which formulas consistently win
6. Use the winning formulas as defaults; keep testing the next hypothesis
```

10-20 tests = enough evidence to know your audience.

## Setup the test in AcelleMail

In the campaign type selector:

![Select A/B test campaign type](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/01-select-ab-test.png)

Pick A/B test. The setup:

![A/B test setup](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/02-ab-test-setup.png)

Configure:

- **What to test**: Subject (not body — body comes later)
- **Sample**: 20% (or 30% if list is small)
- **Decision time**: 4 hours
- **Winner rule**: Highest open rate

Define the 2 variants:

![Variants tab](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/03-variants-tab.png)

Each variant should embody the SAME hypothesis differently. Not random ideas.

After launch, the winner-rules step:

![Winner rule](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/04-winner-rules.png)

After 4 hours, AcelleMail auto-picks the winner + sends to remaining 80%.

The post-test report:

![A/B report](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/05-ab-report.png)

Record the result.

## Hypothesis-driven tests (examples)

### Hypothesis 1: "Urgency increases opens"

| Variant | Subject |
|---|---|
| Control (A) | "Spring sale: 20% off everything" |
| Test (B) | "Spring sale: 20% off — final 24 hours" |

Result: B wins by 8 percentage points → urgency works. Use urgency in promotional subjects going forward.

### Hypothesis 2: "Personalization (first-name) increases opens"

| Variant | Subject |
|---|---|
| Control (A) | "Your weekly digest is ready" |
| Test (B) | "{{ subscriber.first_name }}, your weekly digest is ready" |

Result: A wins by 3 percentage points → personalization doesn't help YOUR audience (sometimes counter-intuitive results emerge). Skip personalization in newsletters.

### Hypothesis 3: "Question subjects beat statement subjects"

| Variant | Subject |
|---|---|
| Control (A) | "5 ways to improve your bounce rate" |
| Test (B) | "Your bounce rate is too high — here's why" |

Result: A wins → numbered-list outperforms problem-statement for educational content.

### Hypothesis 4: "Short subjects beat long subjects"

| Variant | Subject |
|---|---|
| Control (A) | "Get our new email automation triggers (now live in your account)" |
| Test (B) | "New: automation triggers" |

Result: B wins by 12 percentage points → short subjects work better. Future subjects → 30-50 char range.

## After 10-20 tests, build your doc

```
== Brand Subject Line Patterns ==

WORKS FOR OUR AUDIENCE:
- Urgency for promotional sends (8-15 percentage points lift consistently)
- Numbered-list format for educational ("7 tips", "5 things")
- Short subjects (30-40 chars typical winner)
- Direct benefit over curiosity
- Specific numbers in subject ("47% faster", "1,247 customers")

DOESN'T WORK FOR OUR AUDIENCE:
- Personalization (no measurable lift)
- Emoji (no lift; sometimes hurts)
- Questions (always lose to statements)
- "Re:" fake-reply (penalized by Gmail)

UNSURE / KEEP TESTING:
- Curiosity-gap subjects (mixed results)
- Long subjects (60+ chars) (occasional wins)

== Sample winner patterns ==
- "Spring sale: 20% off — final 24 hours"
- "New: automation triggers"
- "7 tips for better deliverability"
- "Your weekly digest is ready"
- "Big update: bounce handling is now 3× faster"
```

Maintain + update this doc. Reference before every campaign.

## Sample-size + statistical confidence

```
Sample per variant: 1,000+
Open rate: 20%+
Open count per variant: 200+ (minimum for meaningful comparison)

If your test has fewer opens, the result is noisy. Lift may be real or may be coincidence.
```

Below 5,000 list size, A/B testing is hard. You can still test, just expect more noise.

## Common testing mistakes

| Mistake | Why it hurts |
|---|---|
| Testing subject + body simultaneously | Can't attribute the winner |
| Testing 2 random subjects with no hypothesis | Win is unrepeatable |
| Stopping a test early | Insufficient sample = unreliable |
| Repeating the same hypothesis on every test | Stops you from exploring other dimensions |
| Calling a result definitive after 1 test | One test = directional; 3-5 confirms |
| Treating winning subjects as evergreen | Audiences adapt; rotate over time |

## Interpreting non-results

Sometimes A and B finish nearly tied:

```
Variant A: 22.5% open rate
Variant B: 22.8% open rate
Lift: 0.3 percentage points (negligible)
```

This is informative:

- The hypothesis didn't significantly affect opens (your audience doesn't care about this dimension)
- Save engineering attention for hypotheses that do show lift

Record the null result. Move to the next hypothesis.

## Re-test after 18 months

Audience composition changes. Subject patterns that worked 18 months ago may not work now. Re-test your top-3 winning patterns annually.

```
Year 1: Personalization shows 5% lift → use it
Year 2: Personalization shows 3% lift → still works, less strongly
Year 3: Personalization shows 0% lift → audience adapted; drop
```

The "rules" you build are time-bounded. Continuously test.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Same variant wins every test | Variants too similar | Make B differ more from A |
| Winner changes across re-tests | Hypothesis is weak; differences are within statistical noise | Need larger samples OR drop the hypothesis |
| Open rate flat over 10 tests | You haven't tested the dimensions that matter | Brainstorm new hypotheses; try variations you've avoided |
| Decision window too short, no clear winner | 4h not enough for your low-open-rate audience | Extend to 8h or 12h |
| Subject + body tied to same hypothesis | Methodology error | Always vary only one dimension at a time |

<details>
<summary><strong>Advanced: bayesian A/B vs frequentist + multi-arm bandits + meta-learning across campaigns</strong></summary>

**Bayesian A/B vs Frequentist:**

AcelleMail's default is frequentist (winner declared at fixed sample size). Bayesian alternative:

```
After Y opens for each variant, calculate probability that A is better than B.
Declare a winner when probability >95% or <5%.
```

Pros: faster decisions, more intuitive interpretation
Cons: requires bayesian calculation; some statistical sophistication

Tools exist (Optimizely, VWO) but most senders are fine with frequentist for the 4-hour decision window.

**Multi-arm bandit for ongoing optimization:**

For high-volume senders, don't A/B test each campaign — train a multi-arm bandit:

```
Each campaign:
  - 80% sent with historically-best subject pattern
  - 20% sent with slightly-varied pattern (exploration)
  - Update the best-pattern estimate based on new results
```

After ~50 campaigns, the bandit converges on your audience's optimal pattern automatically. Continual learning vs episodic testing.

**Meta-learning across campaigns:**

Aggregate test results across all your past A/B tests:

```
Across 47 tests over 2 years:
- Urgency wins 38/47 (78%) — STRONG SIGNAL
- Numbered-list wins 22/47 (47%) — MODERATE
- Personalization wins 12/47 (26%) — WEAK
- Questions win 8/47 (17%) — INVERSE: questions LOSE to statements
- Emoji wins 5/47 (11%) — INVERSE: emoji LOSES
```

This is your audience's TRUE preference, not single-campaign noise.

Update your subject-line-pattern doc based on this meta-data. Use winning patterns as defaults.

**Segment-aware testing:**

The "best" pattern may vary by segment:

```
Across high-engagement segment: Curiosity-gap wins 8/10 tests
Across at-risk segment:          Direct-benefit wins 7/10
Across new-subscriber segment:   Social-proof wins 8/10
```

Test within each segment independently. Build per-segment subject formulas.

**Time-of-day + subject interaction:**

```
Morning send + curiosity subject: 28% open
Morning send + direct subject:    24%
Evening send + curiosity subject: 21%
Evening send + direct subject:    24%
```

The interaction matters: curiosity works in morning, direct works in evening. Test combinations.

**Cross-channel subject patterns:**

If you also run SMS/Push campaigns, do subject patterns translate?

```
Email subject: "20% off through Friday"  (works in email)
SMS preview:   "20% off through Friday"  (works in SMS — same)
Push title:    "20% off through Friday"  (less effective; push needs urgency cue)
```

Some patterns universal; others channel-specific. Don't assume identity.

**Documenting test history:**

```
Test ID: 2026-W18-T01
Campaign UID: ...
Date: 2026-05-04
Hypothesis: "Urgency increases opens"
Variant A: "Spring sale: 20% off everything"
Variant B: "Spring sale: 20% off — final 24 hours"
Sample: 20% of 30,000 list = 6,000 per variant
Result: B won by 5.4 percentage points (24.1% vs 18.7%)
Verdict: STRONG WIN — use urgency for promotional sends
Reflection: Want to test if "final 12 hours" beats "final 24 hours" next
```

Build this log over time. Future-you references it; future-team learns from it.

</details>

## Related articles

- [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Email preheader text — the hidden conversion booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster)
- [Re-engagement copy that doesn't feel desperate](https://acellesend.com/kb/articles/re-engagement-copy-that-doesnt-feel-desperate)

---

# My Emails Are Landing in Spam — UI-First Diagnostic

> Spam-folder placement happens for measurable reasons. Walk the Insights page, the Sending Server config, and the Test diagnostic — three UI checks isolate the cause before anyone has to look at headers.

- Canonical URL: https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Troubleshooting — Common errors, debugging, delivery issues, logs, performance
- Content type: troubleshooting · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What to check from the dashboard

Spam-folder placement isn't a single failure — it's a signal that something measurable is off about your sender setup or your audience engagement. AcelleMail's dashboard exposes the three signals receivers use to make the spam vs. inbox decision.

Open a recently sent campaign and click **Insights** (or Subscribers → Engagement tab):

![Campaign Insights — per-recipient engagement signal](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/01-campaign-insights.png)

What you're reading:

- **Open rate < 5%** for a list you regularly engage → recipients aren't seeing it in their inbox at all. Spam placement likely.
- **Open rate normal but click-through 0** → message rendering broken (suspicious content) or recipients are opening, scanning, and not engaging. Reputation drift.
- **High open rate but high unsubscribe + complaint rate** → content mismatched to subscriber expectations. ISPs penalize this.

## Open the sending server configuration

Most spam-folder cases trace back to the sending side. **Settings → Sending servers** → click your active server:

![Sending server detail — deliverability config](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

Three things to verify here:

1. **Authentication status** — SPF / DKIM / DMARC verification chips should all be green. Red means the receiving servers can't confirm you're authorized to send for the domain.
2. **Daily quota** — if you're hitting the quota every send, sends spill over into the next day's batch. Some ISPs interpret this as bulk-spam pattern.
3. **Bounce + complaint rate** (last 24h) — if either is >5%, ISPs throttle your IP. Sustained high rates push you into spam folders.

## Run the test diagnostic

Inside the sending server detail, **Test** button sends a one-off message you can inspect:

![Sending server test page — diagnostic send](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/03-sending-server-test.png)

Send to your own address. Open the email in Gmail / Outlook / Apple Mail.

- **Email arrived in Inbox?** → sending setup is OK. The spam problem is content / recipient-side (engagement, subject lines, frequency).
- **Email arrived in Spam?** → sending setup is the issue. Move to operator section.
- **Email didn't arrive at all** → outright blocked. IP is on a blocklist. Operator section.

In Gmail, click the email → three-dot menu → **Show original**. The headers reveal:
- `Authentication-Results: ... spf=pass dkim=pass dmarc=pass` (all three should pass)
- `Received-SPF` and `DKIM-Signature` presence

If any of the three says `fail` or is missing, that's your fix target.

## Common UI causes and the fix from the dashboard

| What you see | Likely cause | What to do |
|---|---|---|
| Sending server: red SPF / DKIM / DMARC chip | DNS records missing or incorrect | Click *Verify* → AcelleMail walks you through the DNS records to add. Re-verify after publishing. |
| Insights: open rate dropped 40% in last 3 sends | IP reputation degrading | Pause campaigns. Run **Subscribers → Email verification** on the list. Investigate recent content for unsubscribe spikes. |
| Test diagnostic lands in spam, headers show "spf=fail" | SPF record doesn't include your sending IP / provider | Update SPF record at your DNS host. Add the sending vendor's SPF include (e.g. `include:amazonses.com`). |
| Test diagnostic lands in spam, body has spam-trigger words | Content filtering | Re-review subject line + first paragraph. Avoid ALL CAPS, excessive punctuation, link-only content. |
| Test diagnostic doesn't arrive at all | IP blocklisted | Check the sending IP at mxtoolbox.com → blocklist lookup. Request delisting per the blocklist's process. |

## Pause and isolate the bad cohort

If only some recipients land in spam, segment the list:

- **Subscribers → Filter by domain** → check open rates per ISP (Gmail / Outlook / Yahoo / corporate).
- A 0% open rate on @gmail.com while @outlook.com is normal → your Gmail-side reputation is hurt. Investigate recent send patterns to Gmail addresses.
- Pause sends to the problem segment until you've remediated content / reputation.

<details>
<summary><strong>Advanced: SPF / DKIM / DMARC tuning for operators</strong></summary>

The three authentication standards form the deliverability foundation. All three must pass for ISPs to trust the sender.

**SPF (Sender Policy Framework — RFC 7208)**

A DNS TXT record listing which IPs / domains are authorized to send for your domain. Lookup:

```bash
dig TXT yourdomain.com +short | grep spf
# Expected: "v=spf1 include:amazonses.com -all"
```

Common gotchas:

- Multiple `v=spf1` records on the same domain — SPF requires exactly ONE. Merge into one record.
- `~all` (soft fail) instead of `-all` (hard fail) — `-all` is preferred for production.
- Sending IP not in the record. Use `dig` to verify; add the include statement.

**DKIM (DomainKeys Identified Mail — RFC 6376)**

Cryptographic signature on every outgoing message. The public key is published in DNS at `<selector>._domainkey.yourdomain.com`. AcelleMail generates the keypair via the sending-server's *Verify domain* wizard.

```bash
# Check the DKIM record exists
dig TXT default._domainkey.yourdomain.com +short
# Expected: "v=DKIM1; k=rsa; p=MIGfMA0..."
```

Common gotchas:

- DNS record published but with line breaks — receivers can't parse. Re-publish as single-line.
- Selector mismatch — AcelleMail uses `<servername>._domainkey` by default; if you customized the selector during install, verify the record name matches.

**DMARC (Domain-based Message Authentication, Reporting & Conformance — RFC 7489)**

Tells receivers what to do when SPF or DKIM fails:

```bash
dig TXT _dmarc.yourdomain.com +short
# Expected: "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com"
```

Migration path (always start permissive, ramp to strict):

1. `p=none; rua=mailto:reports@yourdomain.com` — monitor; receivers send aggregate reports, no policy applied.
2. After 2-4 weeks of clean reports → `p=quarantine; pct=10` — 10% of failing mail goes to spam.
3. Ramp `pct` to 100 over 2 weeks → switch to `p=reject` only after 100% pass rate.

Going straight to `p=reject` without ramping breaks ALL your mail if any auth is misconfigured.

**Inspecting Authentication-Results** of a received message:

```bash
# Send a test → grep the headers
grep -E "Authentication-Results|Received-SPF|DKIM-Signature" /path/to/saved-email
```

Look for `dmarc=pass` AND `spf=pass` AND `dkim=pass`. Any failure → fix that side specifically before retrying.

**Engagement-driven reputation** — ISPs increasingly weight subscriber engagement over pure auth. Even with green SPF/DKIM/DMARC, if your open rate is <10% over a 30-day window, Gmail / Outlook may demote you. Mitigation: aggressive list hygiene (unsubscribe inactive subscribers after 6 months), re-engagement campaigns to dormant cohorts before re-engaging them in regular sends.

**Blocklist monitoring** — periodic check at mxtoolbox.com for your sending IP + sending domain. Major lists: Spamhaus SBL/XBL/CSS, SpamCop, Barracuda. Each list has its own delisting form.

</details>

## Related articles

- [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Email list hygiene — clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [SPF record deep dive](https://acellesend.com/kb/articles/spf-record-deep-dive)

---

# My Automation Isn't Triggering — How to Diagnose from the UI

> A welcome series or drip that won't fire on new subscribers. The Automations dashboard shows you whether the workflow is active, what trigger it's listening for, and which subscribers are eligible — three checks resolve most cases.

- Canonical URL: https://acellesend.com/kb/articles/why-is-my-acellemail-automation-not-triggering
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Troubleshooting — Common errors, debugging, delivery issues, logs, performance
- Content type: troubleshooting · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What to check from the dashboard

Automations that "don't fire" almost always have a UI-visible reason — wrong status, wrong trigger, or the subscriber doesn't match the condition the workflow is waiting for. Walk these three checks before logging into the server.

Open **Automations** in the left sidebar:

![Automations index — status badges visible](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/01-automations-index.png)

Find your workflow row. Three columns matter:

1. **Status** — `Active` means it will trigger; `Inactive`, `Draft`, or `Paused` will NOT.
2. **Last run** — when did a subscriber last enter this workflow?
3. **Subscribers in flow** — current count of recipients somewhere in the steps.

If status is anything but **Active**, that's your fix. Click the row → **Settings** → toggle Active. Save.

## Check the trigger node

If status is Active but new subscribers still aren't entering, click the workflow name to open the flow canvas:

![Automation flow canvas — trigger + steps](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/02-automation-flow.png)

The first node is the **trigger**. Common misconfigurations:

| Trigger type | What it listens for | Common pitfall |
|---|---|---|
| **Subscribed to list** | New rows in a specific mailing list | Wrong list selected — workflow fires on List A but subscribers are being added to List B |
| **Tag added** | A specific tag attached to a subscriber | Tag name typo / case mismatch — `vip` vs `VIP` are different |
| **Custom event (API)** | Your application calls `POST /api/automations/{uid}/event` | API call missing or hitting wrong workflow uid |
| **Date relative** | A specific date field (birthday, signup_date) matches today | Field empty for most subscribers — workflow only fires for the few with that field populated |
| **Link clicked** | Subscriber clicks a specific link in a campaign | Link not yet clicked by anyone — wait for engagement |

Hover over the trigger node — the right-hand panel shows the exact configuration. Verify it matches your list, tag, or event name.

## Verify the workflow settings

Click **Settings** in the top-right of the flow canvas:

![Automation settings — eligibility + cooldown](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/03-automation-settings.png)

Three settings commonly block firing:

- **Allow re-entry?** — If OFF, a subscriber who entered once (even years ago) can't re-enter. Toggle ON if you want every signup to flow through.
- **Cooldown period** — A subscriber who entered N hours/days ago can't re-enter until cooldown expires.
- **Eligibility filter** — Optional segment/tag filter. Subscribers who don't match the filter skip the workflow entirely.

## Common UI causes and the fix from the dashboard

| What you see | Likely cause | What to do |
|---|---|---|
| Status: Inactive | Workflow toggled off (common after edits — AcelleMail un-activates on save). | Open Settings → toggle Active → Save. |
| Trigger = Subscribed to list, wrong list | Workflow listens on List A but new signups go to List B. | Edit trigger → pick correct list → Save. |
| Trigger = Tag added, no recent tagging | Tag never added to any subscriber. | Either tag a subscriber to test, or change the trigger to a more reachable condition. |
| Trigger = Date relative, empty fields | Most subscribers don't have the date field populated. | Import the field via CSV update, or change to a different trigger. |
| Status: Active but `Subscribers in flow` = 0 | Trigger fires correctly but the first delay node hasn't elapsed yet. | Wait for the delay duration, then refresh. |
| Status: Active, Subscribers in flow > 0, no emails sent | Send-step is failing (sending server issue). | Check **Settings → Sending servers** → run *Test connection*. See [Campaign stuck in Sending](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status). |

## Test the workflow by hand

The fastest way to confirm "is this workflow actually firing?" is to manually trigger it:

1. Create a test subscriber with an address you control (your own email).
2. Add that subscriber to the list / apply the tag / fire the API event — whatever the trigger expects.
3. Wait the configured delay (start with workflows that have <5 min first-step delay for fast iteration).
4. Check your inbox.

If the test subscriber receives the email → workflow is fine, your original signups aren't matching the trigger (back to dashboard checks).

If the test subscriber does NOT receive → either the workflow isn't firing, or the send is failing. See operator section.

<details>
<summary><strong>Advanced: server-side checks for the operator</strong></summary>

AcelleMail automations run on the same queue worker that processes campaigns. If campaigns are sending but automations aren't, the worker is processing the wrong queue mix.

**Queue priorities** — automation jobs go onto the `automation` queue by default. Verify the worker is listening to it:

```bash
ps aux | grep "queue:work" | grep -v grep
# Expected: includes --queue=high,default,low,automation
```

If `automation` is missing from the worker's `--queue` flag, restart Supervisor with the correct config:

```bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart acelle-queue-worker:*
```

**Cron and the master scheduler** — `acelle:run` ticks every minute and picks up time-based automation triggers (date-relative, delayed step transitions). If cron is broken, time-driven steps stall while immediate triggers (subscribe / tag / API event) still work.

```bash
crontab -l | grep acelle:run
# Expected: * * * * * php /path/to/artisan acelle:run >/dev/null 2>&1
```

**Inspect a specific automation's history** via the database — every entry/exit is logged:

```bash
# Recent automation runs
php artisan tinker --execute='
  \App\Model\AutomationCustomerInteraction::orderByDesc("id")
    ->limit(20)->get()
    ->each(fn($r) => print("uid={$r->automation_uid} sub={$r->subscriber_uid} step={$r->step_uid} at={$r->created_at}\n"));
'
```

**Failed-job inspection** — if a step throws an exception, the job goes to `failed_jobs`. Look for entries with `Automation` in the payload:

```bash
php artisan queue:failed
php artisan queue:retry all  # retry every failed job
```

**Workflow-uid → log mapping** — to find why a SPECIFIC subscriber didn't enter:

```bash
grep "subscriber_uid=<uid>" storage/logs/laravel.log | tail -20
```

</details>

## Related articles

- [Campaign stuck in Sending status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status)
- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Setting up queue workers and cron jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs)
- [Using API triggers for custom automation workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows)

---

# My Campaign Is Stuck in "Sending" — What to Check

> A campaign that still shows "Sending" hours after you hit send. Walk through the three UI signals that tell you what's actually happening — then the two-click fix that resolves it for most teams.

- Canonical URL: https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Troubleshooting — Common errors, debugging, delivery issues, logs, performance
- Content type: troubleshooting · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What to check from the dashboard

A campaign that says **Sending** hours after you launched it isn't always actually stuck — most of the time AcelleMail's auto-rerun has already picked it back up and is finishing the send in the background. The dashboard tells you whether to wait or whether to act.

Open **Campaigns** in the left sidebar. Look at the row for your campaign — there are three things to read:

![Campaign list — Sending badge visible](https://acellesend.com/plugins/acelle/landing/images/help/kb/troubleshoot-stuck/01-list-sending-badge.png)

1. **The status badge** — "Sending" means the campaign is in progress.
2. **The "Sent" counter** — does it match your list size, or is it stuck at a lower number?
3. **The "Last updated" timestamp** — when did the worker last touch this campaign?

If "Last updated" is **less than 10 minutes ago**, AcelleMail is actively sending. Refresh in a few minutes — it'll catch up.

If "Last updated" is **30+ minutes ago and Sent is below the list size**, the worker hasn't been processing this campaign — keep reading.

## Open the campaign and look at the live counts

Click the campaign name. The overview page shows the per-status breakdown — *Delivered*, *Bounced*, *Failed*, *Opened*, *Clicked*:

![Campaign overview — sending state](https://acellesend.com/plugins/acelle/landing/images/help/kb/troubleshoot-stuck/02-overview-sending.png)

The two numbers that matter:

- **Sent vs. list size** — if Sent < list size and not moving, AcelleMail isn't picking up the campaign for processing.
- **Failed count** — if this is growing, the SMTP server is rejecting messages and your campaign is stuck on the same recipient batch.

## Check the tracking log for what AcelleMail tried to send

Inside the campaign, the **Tracking log** tab is the per-message audit. Every send attempt — successful, bounced, deferred — has a row:

![Tracking log — per-message audit](https://acellesend.com/plugins/acelle/landing/images/help/kb/troubleshoot-stuck/03-tracking-log.png)

Scroll to the latest entries:

- **All rows show recent timestamps** → AcelleMail is sending right now, the dashboard is just showing slightly stale counts. Refresh in 5 minutes.
- **Latest row is hours/days old** → no send activity. The worker is the bottleneck (see below).
- **Latest rows show SMTP errors repeatedly** → your sending server credentials, IP reputation, or DNS records need attention. Open **Settings → Sending servers** and click *Test connection* on the active server.

## Common UI causes and the fix from the dashboard

| What you see | Likely cause | What to do |
|---|---|---|
| Status: Sending · Sent count not moving · Tracking log empty | The sending worker is idle. | Ask the operator to restart workers (see Advanced below). If you're self-hosted as a solo operator, see the operator section. |
| Status: Sending · Sent count = part of list · Last updated >1 hour | A batch failed mid-run; AcelleMail auto-rerun will pick it up next 10-minute window. | Wait 15 minutes. Refresh. If Sent still doesn't move, jump to the operator section. |
| Status: Sending · Failed count growing fast | SMTP rejection. | **Settings → Sending servers → Test connection**. Common causes: expired API key, exceeded daily quota, IP blocked. |
| Status: Sending · *all* recipients in tracking log show "deferred" | Receiving servers are throttling. | Wait — deferred messages are retried automatically. If still deferred after 24h, the sending server's IP needs a warm-up. See [Why are my emails going to spam](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder). |

## Pause and resume from the campaign UI

If you need to stop the send (e.g. wrong list, urgent edit), the campaign overview has a **Pause** button while it's in Sending state. Pause → fix the issue → click *Resume* — AcelleMail picks up where it left off, no duplicates.

<details>
<summary><strong>Advanced: server-side checks for the operator</strong></summary>

If the dashboard checks above point at the worker being idle, the operator needs to verify the queue worker process is running. AcelleMail pushes campaign sends onto a Laravel queue; if the worker process dies (OOM, missing cron, supervisor not running), campaigns sit in the "sending" state indefinitely.

**Quick worker check (SSH to the AcelleMail host):**

```bash
# Is the queue worker process up?
ps aux | grep "queue:work" | grep -v grep

# How big is the queue backlog?
php artisan queue:size

# How many failed jobs need a retry?
php artisan queue:failed | wc -l
```

**Restart the worker** (assumes Supervisor is managing it — the recommended install pattern):

```bash
supervisorctl restart acelle-queue-worker:*
```

If you're running the worker manually without Supervisor:

```bash
nohup php artisan queue:work --queue=high,default,low --sleep=3 --tries=3 &
```

**Force a campaign rerun** — AcelleMail's `SendCampaign` console command picks up stuck campaigns in 10-minute windows. To trigger it immediately:

```bash
php artisan acelle:send-campaign --force
```

**Cron check**: AcelleMail's `acelle:run` command is the master scheduler. If it stopped running (cron disabled, crontab edited), campaigns never get picked up. Confirm with:

```bash
crontab -l | grep acelle:run
# Expected: * * * * * php /path/to/acelle/artisan acelle:run >/dev/null 2>&1
```

**Inspect failed jobs**:

```bash
php artisan queue:failed
# If a campaign send failed and the worker gave up after `tries` exhausted,
# retry it with:
php artisan queue:retry all
```

**Logs to read** when the dashboard counts don't match worker behavior:

- `storage/logs/laravel.log` — Laravel exceptions during send
- `storage/logs/queue-worker.log` (if configured) — per-job timing
- The campaign tracking log inside the dashboard remains the source of truth for per-recipient outcome; the operator logs explain WHY the worker stalled.

</details>

## Related articles

- [Why is my automation not triggering](https://acellesend.com/kb/articles/why-is-my-acellemail-automation-not-triggering)
- [Why are my emails going to the spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder)
- [Decoding SMTP error codes and bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages)
- [Setting up queue workers and cron jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs)

---

# Writing Email for Skimmers — Structure That Survives 5-Second Reads

> 90% of recipients skim. They read for 5-10 seconds, scrolling fast, looking for the answer. Most emails are written for the 10% who read carefully — they fail with the skimmer majority. This guide is the skimmer-first structure.

- Canonical URL: https://acellesend.com/kb/articles/writing-email-for-skimmers
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Best Practices — Subject lines, send timing, warmup strategies, engagement optimization
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The skimmer reality

Email reading research (Nielsen Norman, Litmus, multiple studies):

- **90%+ of recipients skim** — read for 5-10 seconds
- **Only 10-15% read carefully** — line-by-line
- **Skimmers scan top-down** — they read the first 1-2 lines per paragraph
- **Mobile makes it worse** — 60% of email opens are mobile, where attention spans are even shorter

If your email requires careful reading to understand the offer + CTA, 90% of recipients will miss the point.

## The skimmer-first structure

```
[Bold headline]
Optional brief paragraph explaining the headline

[Bold sub-headline]
Optional brief paragraph

[Bold sub-headline]
Optional brief paragraph

[Single primary CTA button]

[Sign-off]
```

The skimmer reads ONLY the bold lines. The bold lines must self-narrate the message.

Recipients who read carefully get the full body. Skimmers get the essence. Both audiences served.

## Write in AcelleMail

Open the campaign builder. In the template editor:

![Template editor](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/05-template.png)

Add text blocks; use the builder's bold formatting on the leading sentence of each paragraph. The structure becomes:

```
**Our spring sale is live — 20% off through Friday**
For the next 4 days, every product gets a discount applied at checkout.

**Free shipping on orders >$50**
Combine with the 20% off — no extra code needed.

**Shipping completes by April 1**
Order by Friday to receive in time for the start of the month.

[Get my 20% discount →]
```

A skimmer reading only the bold lines learns:

```
Our spring sale is live — 20% off through Friday
Free shipping on orders >$50
Shipping completes by April 1
```

Full message conveyed in 3 lines. Single CTA at the end. Done.

## Verify in Preview

After writing, verify the skimmer-pass works:

![Desktop preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

Squint at the preview. Can you read only the bold lines and still get the message?

Then mobile:

![Mobile preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

Mobile makes skimmer-discipline even more important — less screen real estate per scroll.

## The 5-second test

Show your final draft to a colleague. Time them. After 5 seconds of looking:

- Did they understand the offer?
- Did they know what to do next?
- Did they feel motivated to click?

If yes to all three: skimmer-proof.

If no to any: rewrite. The bold first-lines aren't doing enough work.

## Patterns that survive skimming

| Pattern | Why it works |
|---|---|
| Short paragraphs (1-3 lines) | Visual chunking helps skimming |
| Bold first sentences | Skimmer reads these |
| Bulleted lists | Easy to scan; clear breaks |
| Numbered steps (1, 2, 3) | Sequence visible at glance |
| Single primary CTA | Skimmer can't miss it |
| White space between sections | Reduces visual noise |
| Subheadings every 100-150 words | Re-anchor the skimmer's attention |

## Patterns that fail skimmers

| Anti-pattern | Why it fails |
|---|---|
| One long paragraph | Skimmer's eyes glaze over |
| All-caps headlines | Hard to scan; feels yelling |
| Buried CTA | Skimmer scrolls past |
| 3 equal-weight CTAs | Decision paralysis; clicks fragment |
| Dense formatting | Looks like a wall of text |
| Embedded links in the middle of paragraphs | Skimmer misses; visible CTAs at section ends work better |

## Mobile-specific skimmer guidance

On mobile (60% of opens):

- **One column maximum** (multi-column emails compress badly)
- **Bigger fonts** (14-16pt minimum for body)
- **48px+ tappable CTA buttons** (recipients miss-tap on small buttons)
- **Front-load the message** (above-fold matters more on small screens)
- **Avoid sideways scrolling** (test mobile preview)

The AcelleMail builder auto-adapts most layouts for mobile. But always verify with Preview's mobile toggle:

![Mobile preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

## Examples

### Skimmer-friendly newsletter

```
**This Week's Top 3**

We sent 12 emails. Here are the 3 your subscribers would have skipped without you.

**1. Subject lines (10 articles)**
Most senders use the same formula for 18 months. We tested 3 alternatives.
[Read the deep dive →]

**2. Bounce rates (7 articles)**
Why your 5.2% bounce rate is actually fine — and when 1.8% is the problem.
[Read the analysis →]

**3. AI tools (5 articles)**
Where AI in email actually saves time vs where it ships garbage.
[Read the breakdown →]

Cheers,
Maya
```

Skimmer reads: 3 bold subheadings + a single hook line. Got the gist.

### Skimmer-friendly promotional

```
**Spring sale: 20% off everything**
Live now through Friday at midnight ET.

**Free shipping on orders over $50**
Combine with the discount — no extra code.

**Returns + exchanges still hassle-free**
Same 60-day return policy. Same easy exchanges.

[Shop the sale →]
```

3 lines = full understanding. Single CTA at the end.

### Skimmer-friendly product update

```
**We shipped automation triggers**
You asked. It's done. Available in your account now.

**Triggers based on subscriber behavior, custom events, or API webhooks**
The 3 most-requested trigger types. Each has its own config page.

**Migration from manual sequences takes 5 minutes per workflow**
The new builder imports your existing AcelleMail automations.

[See what's new →]
```

Three subheadings narrate the launch.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Open rate strong, click rate <2% | Body buried; skimmer didn't see the CTA | Single primary button; bold sub-headlines |
| Open rate strong, replies asking "what is this about?" | Body confusing or too long | Cut to <100 words; clearer structure |
| Mobile opens are 60% but mobile clicks are 30% | Mobile rendering hurts skimming | Test mobile preview; reduce columns |
| Long emails get fewer clicks | Skimmers tire and abandon | Cut to <150 words OR break into multi-section landing page |
| Multiple CTAs perform poorly | Dilutes focus | Single primary CTA per email |
| Bullets help; numbered lists don't | Audience prefers visual breaks | Use both; experiment with which pattern wins |

## Common anti-patterns

| Mistake | Fix |
|---|---|
| Hiding the "ask" until the bottom | Move it up; let skimmer see it on first scroll |
| Subheadings styled as plain text | Bold them; visual hierarchy |
| Subject promises specific; body buries it | Subject + first-line must align |
| "TL;DR" at the bottom | The whole email should be skimmable; TL;DR shouldn't be needed |
| Walls of text with no breaks | Add subheadings every 100 words |
| Image-only emails | Skimmers can't navigate; alt-text helps but real text helps more |

<details>
<summary><strong>Advanced: F-pattern reading + heat-mapping + segment-aware skimmer optimization</strong></summary>

**F-pattern reading:**

Eye-tracking studies show readers scan in an F-pattern on long content:

```
1. Read the first 1-2 lines (top horizontal of F)
2. Read the next 1-2 lines (middle horizontal)
3. Scan the rest vertically (vertical line of F)
4. Possibly skim 1-2 paragraphs that match what they're looking for
```

This applies to email too. Front-load the most important information. The CTA should be in the first scroll-screen, not at the bottom.

**Heat-mapping email engagement:**

If your install supports click heat-mapping, use the AcelleMail Click Map view:

![Click map visualization](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/02-click-map.png)

Shows where clicks accumulate. If 60% of clicks are on the bottom-left button and 40% on the top-right link, restructure to put the top-right link MORE prominent (it's earning attention despite being smaller).

**Segment-aware skimmer optimization:**

Different segments skim differently:

```
Engaged segment: reads carefully; longer copy OK
At-risk segment: skims more; shorter copy needed
New segment: reads carefully (first impressions); medium-length
Mobile-dominant segment: shorter is mandatory
Desktop-dominant segment: tolerates slightly longer
```

Per-segment templates. AcelleMail's per-segment send approach supports this; or use Liquid templating in body.

**Sub-segment optimization within campaign:**

If a portion of your audience reads everything, optimize the deep-read experience too:

```
For the 90% skimmer: bold-line structure conveys message
For the 10% deep-reader: full paragraphs give context, nuance, supporting data
```

Both audiences served by one email. No tradeoff — structure helps everyone.

**Email length vs engagement curve:**

Research shows:

| Email length | Open rate | Click rate |
|---|---|---|
| <100 words | Average | Above-average |
| 100-200 words | Average | Average |
| 200-400 words | Average | Slightly below |
| 400-600 words | Average | Below |
| >600 words | Slightly above | Below |

Sweet spot for most senders: 100-200 words. For deep-dive newsletter content, 400+ if your audience has demonstrated long-read affinity.

**Skimmer-first writing checklist:**

Before sending:

1. ✅ Subject + first body line align
2. ✅ First 3 bold lines self-narrate the message
3. ✅ Single primary CTA visible in first scroll
4. ✅ No section >100 words without a subheading
5. ✅ Mobile preview tested
6. ✅ Squint test passed (5-second read = understanding)

If all 6 pass, ship.

</details>

## Related articles

- [Email body structure — hook value CTA](https://acellesend.com/kb/articles/email-body-structure-hook-value-cta)
- [How to write email copy that converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Email template design best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)

---

# BIMI with VMC — Cost, Process, and Whether It's Worth It

> VMC (Verified Mark Certificate) costs $1,500/yr per domain and takes 4-8 weeks to obtain. This guide walks the cost math, the trademark requirements, the alternative paths, and a decision framework for whether VMC is right for your sending program.

- Canonical URL: https://acellesend.com/kb/articles/bimi-with-vmc-cost-walkthrough
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Quick decision framework

| You should get a VMC if... | You should skip VMC if... |
|---|---|
| Gmail is >30% of your audience | Audience is mostly Yahoo / Apple Mail / B2B |
| List size >100k subscribers | List <10k subscribers |
| Brand has registered trademark in target market | No registered trademark; just a logo |
| Engagement (open rate) is your conversion driver | You sell on click-through, not visibility |
| Budget for $1,500/yr + 4-8 week setup | Tight budget; need BIMI now |

Below 100k subscribers, the VMC cost rarely pays back from incremental opens. Above that, payback is typically 3-6 months for Gmail-heavy lists.

## Cost breakdown

| Item | Cost | Notes |
|---|---|---|
| **VMC certificate** | $1,500-$2,000/yr per domain | From Entrust or DigiCert |
| **Trademark application** (if not already registered) | $250-$2,000 | $250 USPTO filing fee + attorney time if needed |
| **SVG logo prep** | 0-$500 | Free if designer can do BIMI-compatible SVG; paid service if not |
| **DNS / hosting setup** | 0 | Standard DNS + your web host |
| **Ongoing renewal** | Same as cert cost | Renew yearly |
| **TOTAL Year 1** | $1,500-$5,000 | Depends on trademark status |
| **Year 2+** | $1,500-$2,000 | Just renewal |

## The 8-week setup timeline

```
Week 1:  Submit trademark application (if not already registered)
Week 1:  Submit VMC application at Entrust or DigiCert
Week 2-6: VMC issuer verifies trademark registration via USPTO / equivalent
Week 4-6: VMC issuer requests SVG logo (must match trademark exactly)
Week 6-7: SVG validation + certificate creation
Week 7-8: Certificate delivered + customer publishes BIMI DNS record
Week 8+: Gmail's BIMI cache picks up the record (24-48h)
```

For trademark applications, allow 3-6 MONTHS for USPTO approval if starting from scratch. Most senders already have registered trademarks for their brand — VMC is a renewal-style purchase.

## VMC providers

Two main providers:

| Provider | Cost | Notes |
|---|---|---|
| **Entrust** | $1,500-$1,800/yr | Larger market share; more documentation |
| **DigiCert** | $1,500-$2,000/yr | Faster issuance for fully-prepared applications |

Both offer multi-year discounts (Year 1 + Year 2 paid upfront = ~10% off).

## Trademark requirements

VMC requires a registered trademark for your logo. Specifics:

- **United States**: USPTO registration (live application; not pending)
- **EU**: EUIPO or national trademark
- **Canada**: CIPO
- **Asia**: National-trademark per jurisdiction

VMC issuer checks the public trademark database. Logo on the SVG MUST match the trademarked logo exactly — no minor variations.

If your logo isn't trademarked:

1. File at the appropriate office ($250 USPTO + ~3-6 month wait in the US)
2. Wait for registration approval
3. THEN apply for VMC

Most senders treat this as a brand-protection investment they'd do anyway.

## Alternative: BIMI without VMC

Yahoo + Apple Mail + Fastmail support BIMI without VMC. Skip the VMC; publish only the SVG + BIMI DNS record:

```
TXT default._bimi.yourdomain.com  "v=BIMI1; l=https://yourdomain.com/bimi/logo.svg"
```

Result:
- Logo appears in Yahoo, Apple Mail, Fastmail
- Logo does NOT appear in Gmail (uses default circle-initial fallback)

For senders whose audience is <30% Gmail, this is the cost-effective starting point. Add VMC later if Gmail engagement scaling becomes a priority.

## Verify your BIMI setup

After publishing the DNS record + SVG (with or without VMC):
### Open the sending-domain detail

In AcelleMail's sidebar, **Sending → Sending domains**. The list shows every domain you've registered with status chips (Verified / Pending / Failed) and per-auth indicators:

![Sending domains list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into your domain row. The detail page surfaces exactly which DNS records to publish (TXT for SPF, CNAMEs for DKIM, TXT for DMARC) with copy-paste-ready values + current verification state per check:

![Sending-domain detail — DNS records + auth status](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

Send a test campaign. Open on the supported clients:

- **Gmail (US/EU)**: needs VMC
- **Yahoo**: supports BIMI; no VMC needed
- **Apple Mail (iOS 16+, macOS Sequoia+)**: supports BIMI; no VMC needed
- **Fastmail**: supports BIMI; no VMC needed
- **Outlook**: pilot only as of 2026; check current state

If logo appears: BIMI working. If not: check the [BIMI setup walkthrough](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail) for diagnostic flow.

## Decision: pay for VMC or skip?

Build the math from your own audience + list size:

```
Annual VMC cost: $1,500
Expected open rate lift: 10% (industry baseline for BIMI)
Your current open rate: X% (check your AcelleMail campaign reports)
Your monthly campaign sends: N
Conversion rate of opens to revenue: Y%
Per-conversion value: $V

Annual incremental opens = (open rate lift) × (sends) × (your Gmail %)
Annual incremental revenue = Annual incremental opens × Y × V

If Annual incremental revenue > $1500: VMC pays back. Get it.
If Annual incremental revenue < $1500: Skip VMC; revisit when list grows.
```

For most senders with >100k subscribers + >25% Gmail audience, the math is clear positive.

## Common UI signals + fixes

| Symptom | Likely cause | Action |
|---|---|---|
| Yahoo shows logo, Gmail doesn't | Expected behavior without VMC | Apply for VMC if Gmail audience matters |
| VMC issued but Gmail still doesn't show logo | DNS cache propagation in progress | Wait 24-48h; verify `dig TXT default._bimi.yourdomain.com` includes `a=` parameter pointing to VMC |
| BIMI inspector says SVG invalid | Not SVG Tiny PS format | Re-export via designer's BIMI-compatible profile |
| Trademark application rejected | Logo too similar to existing mark | Brand redesign OR file in different class |
| VMC issuer takes >8 weeks | Trademark verification edge cases | Provide additional documentation; some applications take 12+ weeks |

<details>
<summary><strong>Advanced: multi-domain VMC strategies + selector-based BIMI + cost optimization at scale</strong></summary>

**Multi-domain VMC:**

Per-domain VMC cost — each unique From: domain needs its own VMC + DNS record. If you send from 3 brand subdomains:

```
mail.brand.com    — separate VMC
news.brand.com    — separate VMC
events.brand.com  — separate VMC
```

3 × $1,500 = $4,500/yr. Multi-domain VMC discounts available from both providers (15-25% off for 5+ certificates).

**Selector-based BIMI** for A/B testing or per-campaign logos:

```
TXT default._bimi.brand.com    "v=BIMI1; l=https://.../primary.svg; a=https://.../vmc.pem"
TXT holiday._bimi.brand.com    "v=BIMI1; l=https://.../holiday.svg; a=https://.../vmc.pem"
```

In campaign email headers:

```
BIMI-Selector: holiday
```

Same VMC, different logo per campaign. Useful for seasonal campaigns.

**Cost optimization at scale:**

For SaaS operators offering BIMI as a customer feature:

| Option | Math |
|---|---|
| Per-customer VMC | $1,500/yr × N customers = expensive |
| Single SaaS VMC + customer-pays-trademark | $1,500/yr ÷ N customers — much cheaper |
| White-label aggregator (e.g. ValiMail BIMI service) | $5-50/customer/mo with VMC handling included |

Most SaaS providers offering BIMI use option 3 — turnkey BIMI via partner aggregator vs. building per-customer VMC ops.

**Alternative DNS-only approach (no VMC) for trademark-pending brands:**

If trademark application is pending, you can:

1. Publish BIMI DNS record without `a=` parameter (no VMC)
2. Yahoo + Apple + Fastmail show logo
3. Apply for VMC once trademark approved
4. Update DNS record with `a=` parameter
5. Gmail then shows logo

Two-phase rollout. Easier than waiting for full setup.

**Renewal monitoring:**

```bash
# Daily check — alert if VMC expires within 60 days
expiry=$(openssl x509 -in /path/to/vmc.pem -noout -enddate | cut -d= -f2)
days_left=$(( ($(date -j -f "%b %d %T %Y %Z" "$expiry" +%s) - $(date +%s)) / 86400 ))
if [ $days_left -lt 60 ]; then
  echo "🚨 VMC expires in $days_left days — initiate renewal"
fi
```

Wire to Slack / PagerDuty. VMC expiry without renewal causes Gmail to silently stop showing your logo (no auto-alert from Gmail or Entrust/DigiCert; bring-your-own-monitoring).

**Aggregator services for the hassle-free path:**

- **ValiMail** — manages BIMI + VMC for ~$25-50/customer/month
- **Red Sift / OnDMARC** — comprehensive DMARC + BIMI management
- **DMARCLY** — budget-friendly DMARC + BIMI

For small senders ($1500/yr VMC = significant cost), aggregators amortize the cost across many customers.

</details>

## Related articles

- [BIMI setup — show your logo in Gmail](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)

---

# Custom Tracking Domain — Brand Your Open & Click Links in AcelleMail

> By default your open- and click-tracking links route through the platform's own domain. Point them at your own subdomain (e.g. track.yourdomain.com) so every link recipients hover looks like yours. This guide walks the real Tracking Domains screen, both setup methods (Auto-SSL and Host Redirect), and how to switch a campaign over to it.

- Canonical URL: https://acellesend.com/kb/articles/custom-tracking-domain
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Every link in your campaign gets rewritten at send time so the platform can count opens and clicks before forwarding the recipient to the real URL. Out of the box, that rewritten link uses the platform's own domain — so when a recipient hovers a link, they see a domain that isn't yours.

A **custom tracking domain** swaps that out for a subdomain you own, like `track.yourdomain.com`. Same tracking mechanics, but every link looks like it belongs to your brand. That's better for recipient trust and keeps your sending domain and link domain aligned.

This is the full setup walkthrough. If you just want the short version of why it matters, see [Custom tracking domain for click URLs](https://acellesend.com/kb/articles/custom-tracking-domain-for-click-urls).

## Before you start

- Pick a subdomain you don't already use for anything else — `track.yourdomain.com`, `click.yourdomain.com`, and `links.yourdomain.com` are all common. It'll be dedicated to tracking.
- You'll need access to your DNS provider (Cloudflare, Route 53, Namecheap, etc.) to add a record.
- The verify screen shows one of two setup methods depending on how your install is configured. Read both sections below and follow the one your screen shows — you don't choose; the app picks based on the install.

## Step 1 — Open Tracking Domains

In the left sidebar under **Sending**, click **Tracking Domains**. The page is titled **Tracking Domains** with the subtitle "Use custom domains for open and click tracking links."

If you have none yet, you'll see an empty state ("No tracking domains yet"). Click the green **Add tracking domain** button in the top right.

## Step 2 — Enter your subdomain

On the **Add Tracking Domain** form:

- **Scheme** dropdown — choose `http://` or `https://`. Pick `https://` if your tracking host serves over TLS (recommended).
- **Domain** field — enter the hostname only, no path. For example: `track.yourdomain.com`. The on-screen help says it directly: *"Enter the hostname only — no path (e.g. track.yourdomain.com)."*
- **Port** — leave blank. It's optional and only needed for non-standard setups.

Click **Save and continue**. You'll see the flash *"Tracking domain added — please complete setup below"* and land on the **Setup & Verify** screen. The status badge reads **Unverified** (orange) until you finish.

## Step 3 — Complete setup (the screen tells you which method)

The verify screen shows ONE of the two setups below. Do whichever one your screen displays.

### Method A — Auto-SSL (Caddy)

If your install runs the auto-SSL setup, you'll see a card headed **Auto-SSL Setup (Caddy)** with a small DNS table. It tells you to point your tracking subdomain at the server hostname shown:

| Host name | Type | Value |
|---|---|---|
| `track.yourdomain.com.` | A / CNAME | *(the server hostname shown on screen — there's a copy button)* |

At your DNS provider, add an **A record or CNAME** for your tracking subdomain pointing to that value. The screen notes: *"Point your domain to this server using an A record or CNAME. SSL will be provisioned automatically."* You don't manage a certificate yourself in this mode — it's issued for you once DNS resolves.

### Method B — Host Redirect

If your install uses the host-redirect setup, you'll see a card headed **Host Redirect Setup** with four numbered steps. This is for when your tracking subdomain points at a web server you control:

1. **Download the tracking script** — click **Download tracking script (.zip)**. The zip contains two files: `index.php` and `.htaccess`.
2. **Upload to the document root** — upload **both** files to the document root of your tracking host. Don't put them in a subfolder.
3. **Nginx servers only — add a rewrite rule** — Apache reads the included `.htaccess` automatically; Nginx doesn't, so the screen gives you a small `location` block to paste into your server config. (Skip this step entirely if you're on Apache.)
4. **Verify the setup** — once the files are in place, you're ready to verify.

Either way, give DNS a few minutes. The screen reminds you: *"DNS changes can take 5 minutes to a few hours to propagate."*

## Step 4 — Click Verify now

When DNS has propagated and (for Method B) the script is uploaded, click the **Verify now** button. The app checks that your tracking host is reachable and responding correctly.

On success you'll see *"Domain verified successfully"* and the card flips to the verified state: *"Your tracking domain is active. All campaign links will now use this domain for open and click tracking."* The status badge turns **Verified** (green).

Not ready? Click **I'll do this later** — the domain stays Unverified and you can come back to the Setup / Verify action from the list anytime.

## Step 5 — Use it on a campaign

A verified tracking domain doesn't apply automatically — you select it per campaign. In the campaign wizard, open the **Setup** step and find the **Advanced Settings** section ("Tracking, DKIM signing, and delivery options").

There's a **Tracking domain** dropdown. By default it's set to **None (use default)**. Open it, search if you have several, and pick your verified domain (it shows as the full URL, e.g. `https://track.yourdomain.com`). Send the campaign.

From that send onward, the open- and click-tracking links in that campaign use your branded domain. You can confirm it on the campaign overview, which shows the selected **Tracking domain** in its stats panel.

## Common issues

| What you see | What to do |
|---|---|
| Status stays **Unverified** after you click **Verify now** | The message is: *"Verification failed — make sure the tracking script is uploaded to the document root and the server is reachable, then try again."* For Method B, re-check both files are in the document root (not a subfolder). For both methods, give DNS more time, then click **Verify now** again. |
| Domain shows **Verified** but campaign links still use the default | The tracking domain is selected per campaign. Open the campaign's **Setup → Advanced Settings → Tracking domain** dropdown and pick your domain (it defaults to "None (use default)"). |
| You don't see a **Tracking Domains** item in the sidebar | Your plan may not grant tracking domains. This menu only appears when your plan allows at least one. |
| You edited an already-verified domain | Editing resets it to Unverified (*"Tracking domain updated — please re-verify below"*). Run **Verify now** again. |

## What to do after

- Send a quick test campaign with the new tracking domain selected, then hover a link in the received email to confirm it shows your subdomain.
- If you run multiple campaigns, remember the tracking domain is chosen per campaign — set it each time, or it falls back to the default.
- Tightening up the rest of your sending identity? See [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).

## Related articles

- [Custom tracking domain for click URLs](https://acellesend.com/kb/articles/custom-tracking-domain-for-click-urls)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [DNS propagation explained](https://acellesend.com/kb/articles/dns-propagation-explained)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)

---

# Deliverability Incident Runbook — From Alert to Recovery in AcelleMail

> Bounce rate spiked. Complaint volume jumped. A campaign is stuck on Sending. This is the step-by-step runbook for when something breaks — every check runs from the dashboard, in the order that resolves most incidents fastest.

- Canonical URL: https://acellesend.com/kb/articles/deliverability-incident-runbook
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: troubleshooting · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The 5-minute triage flow

When something looks broken — a campaign stuck on **Sending**, a bounce-rate spike, a customer complaint — work this exact flow in the dashboard first, top to bottom. Most incidents resolve before you reach the last step. Each check tells you *where* the problem is so you don't guess.

Open the affected campaign. On a regular campaign the report tabs sit under the **Sending logs** dropdown: **Delivery log**, **Bounce log**, **Feedback log**, **Open log**, **Click log**, **Unsubscribe log**. (On an A/B-test campaign the same dropdown is labelled **Logs**, and the per-message tabs live under it the same way.) The first three are your triage tools.

### Step 1 — Read the Delivery log

Open the campaign → **Sending logs → Delivery log**:

![Delivery log — per-message audit](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/04-campaign-tracking-log.png)

This is the per-message record of what AcelleMail handed to your sending server. Each row shows the recipient and a **Status** badge — **Sent** (green) when the message was accepted, or **Error** (red) when the send failed (hover the badge for the error text). Read the pattern:

- **Recent rows, all marked Sent** — AcelleMail delivered the messages to the SMTP server fine. The problem is recipient-side or reputation-side, not in your queue. Go to the Bounce log.
- **Recent rows marked Error** — sends are failing at the server. Go straight to the Bounce log to see why.
- **No new rows in the last 30 minutes while the campaign still shows Sending** — nothing is draining the queue. Either the campaign is paused, or the background worker that sends mail has stopped. See [Why is my campaign stuck in Sending status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status).

### Step 2 — Read the Bounce log

Open the campaign → **Sending logs → Bounce log**. Every bounce is listed with the recipient, a **Bounce type** badge, and the raw **Reason** the receiving server sent back (click a reason to see the full text):

![Campaign bounce log — hard + soft bounces with DSN reason](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/02-campaign-bounce-log.png)

Pattern-match what dominates the list:

| What you see | Diagnosis |
|---|---|
| Mostly 5.1.1 "User unknown" | Stale list — addresses no longer exist. Normal under 5%, a problem above it. |
| Mostly 5.7.x "Delivery not authorized" | Authentication broken (SPF/DKIM/DMARC) **or** your IP is blocklisted. |
| Mostly 4.x.x "Mailbox busy / try again" | Receiving server is throttling you — these are soft bounces and get retried automatically. |
| All bounces to one specific domain | That one receiver is blocking you. Investigate that domain on its own. |
| A mix of 5.x bounces + the Delivery log shows no new rows | Worker is struggling or down — circle back to Step 1. |

The verbatim reason text in each row is the receiving server's own response. For the full code reference, see [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages).

### Step 3 — Read the Feedback log (complaints)

Same campaign → **Sending logs → Feedback log**. Each row shows the recipient, a complaint **Type** badge, and the raw reason:

![Campaign feedback log — complaints](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/03-campaign-feedback-log.png)

Complaint volume tells you whether this is a content/list problem or a pure deliverability problem:

- **Spike concentrated on one segment** — that segment's consent was shaky, or the content missed. Pause sending to it and audit before resuming.
- **Spread evenly across the whole list** — content or subject-line mismatch. Audit the campaign before the next send.
- **Zero complaints despite a bounce spike** — this is a deliverability issue (auth or IP), not a content or list issue. That narrows your next steps a lot.

### Step 4 — Check your sending-domain authentication

If the Bounce log is full of 5.7.x "not authorized" lines, your domain authentication is the prime suspect. Open **Sending → Sending domains** in the left sidebar. Each domain shows a **Verified / Unverified** status, with SPF, DKIM, and DMARC record chips:

![Sending domains list — SPF/DKIM/DMARC status per domain](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into the domain to see every DNS record and its individual check state:

![Sending domain detail — DNS records with verified chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

If any record shows Unverified, click **Verify DNS records** to re-check them all at once. AcelleMail tells you which records are still pending — DNS changes can take up to 48 hours to propagate worldwide, so a freshly edited record may need time before it turns green.

### Step 5 — Run a live test send

To confirm whether the problem is reputation or something else, send yourself a real message. Open **Sending → Sending servers**, click into your active server, and use **Send test email** in the toolbar:

![Sending server — Send test email modal](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/05-server-test-modal.png)

Send one to your own Gmail address, one to Outlook, one to Yahoo, then open each one:

- **All land in the inbox** — your reputation is fine. The original issue is content-side or list-side, not your sending setup.
- **Some land in spam** — an ISP-specific reputation hit (often just Gmail or just Outlook). Check that provider's postmaster tooling.
- **None arrive at all** — your IP is likely blocklisted. Look it up on a public blocklist checker and follow that list's delisting process.

## Triage decision tree

Once the five checks above point you at a cause, this maps the cause to the next action:

| Signal | Action |
|---|---|
| Delivery log shows no new rows + campaign still on Sending for >30 min | Worker isn't draining the queue — see [Why is my campaign stuck in Sending status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status) |
| 5.7.x bounces + a DKIM/SPF chip is red | Fix the record at **Sending → Sending domains**, then click **Verify DNS records** |
| 4.x.x bounces and nothing else off | Wait — these soft bounces auto-retry within 24h |
| 5.1.1 bounces above 10% of recipients | List-quality problem — pause, run email verification on the list before resuming |
| Complaint spike above 0.3% | PAUSE remaining sends; audit consent + content; do NOT resume to the full list |
| Test send lands in Spam at Gmail | Gmail-specific reputation — check Google Postmaster Tools |
| Test send doesn't arrive at all | IP blocklisted — look it up on a blocklist checker and follow delisting |
| Every message to one domain fails | That receiver is blocking you — pause sending to that domain and investigate separately |

## Recovery checklist

After you've identified the root cause, work in this order:

1. **Pause active sending** if there's customer-visible impact (queue not draining, complaints climbing).
2. **Fix the source** — the DNS record, the list, or the content.
3. **Send a small test to your most-engaged 5%** to validate the fix — never the full list.
4. **Monitor for 24 hours** — are bounce and complaint rates back to baseline?
5. **Resume normal sending** once everything is green.
6. **Write the post-mortem** within 48 hours (template below).

## When the problem is below the dashboard

If you've worked every step above and the Delivery log shows no new rows while the campaign sits on Sending, the issue is the background process that actually sends mail — the queue worker — not anything you can fix from a campaign screen. That's a separate, infrastructure-level fix (restarting the worker, checking the queue) and it's covered end to end in [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs). Hand that one to whoever administers your install.

## Reputation recovery cycle

If Step 5 confirms a reputation hit (messages going to spam or not arriving), don't blast the full list to "push through" it — that makes it worse. Ramp back in instead, sending only to your most-engaged subscribers first so receiving servers see strong engagement again:

- **Day 0** — Pause all bulk sending. Re-verify your domain at **Sending → Sending domains** and confirm SPF, DKIM, and DMARC are all green. Begin sending only to subscribers who opened in the last 7 days (your highest-engagement group).
- **Day 7** — If those sends stayed clean, widen to the last-30-day openers.
- **Day 14** — Widen to the last-90-day openers. Keep checking Google Postmaster Tools and Microsoft SNDS daily.
- **Day 30** — Resume to your full audience once reputation is back to baseline.

A mild dip recovers in one to two weeks. A severe hit (sustained complaint rate above 1%) can take four to six weeks, or a fresh IP and a full warmup — see [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers).

## Post-mortem template

Capture every incident in a shared doc so the team learns from it. Fill in:

```
INCIDENT: [Date] — [Short description]
DETECTED VIA: [Customer complaint / routine check / alert]
DETECTED AT: [Timestamp]
SEVERITY: [Stalled campaigns / damaged reputation / complaint spike]

ROOT CAUSE: [One sentence — what actually broke]

TIMELINE:
  T+0:   [What happened]
  T+5m:  [What we did]
  T+30m: [What we did]
  T+...: [Resolution]

CONTRIBUTING FACTORS: [What made detection or response slow / impact wider]

PREVENTION: [What we change so it doesn't recur — action items with owners + dates]

LEARNINGS: [Even with no action — what we now know that we didn't before]
```

## Related articles

- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages)
- [Why is my campaign stuck in Sending status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status)
- [Email delivery issues — UI diagnostic checklist](https://acellesend.com/kb/articles/debugging-common-email-delivery-issues)

---

# Rotating Your DKIM Key in AcelleMail — How to Publish a Fresh Key

> AcelleMail has no in-app "rotate key" button — a sending domain's DKIM keypair is fixed at creation. To rotate, you delete the domain and re-add it (which generates a fresh key), publish the new DKIM record at your DNS host, then click Verify DNS records. Here's the safe way to do it.

- Canonical URL: https://acellesend.com/kb/articles/dkim-key-rotation-playbook
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

> You want to replace the DKIM key AcelleMail uses to sign your mail — maybe your DNS host had a security incident, maybe you're following a compliance policy, or maybe you're just doing routine cryptographic housekeeping. This guide shows the actual path AcelleMail gives you for that.

**Set expectations first.** AcelleMail does **not** have a "Rotate key" button, a dual-signing mode, or a per-key selector picker. When you add a sending domain, AcelleMail generates one DKIM keypair and binds it to that domain permanently — there is no Edit or Update on a sending domain for exactly this reason (renaming or re-keying in place would silently break verification and signing).

So "rotating" in AcelleMail means one concrete thing: **delete the domain, re-add it (which generates a brand-new key), publish the new DKIM record at your DNS host, and re-verify.** That's the whole playbook. The rest of this article is how to do that with the least delivery disruption.

## Before you start

- You'll need access to your DNS host (Cloudflare, Route 53, Namecheap, GoDaddy, etc.) — you'll edit one TXT record there.
- Pick a low-traffic window. There's a short gap between deleting/re-adding the domain and your new record verifying, during which fresh sends from that domain won't carry a passing DKIM signature.
- Know your domain's exact name as it appears under **Sending → Sending domains** (e.g. `mail.brand.com`).

## How AcelleMail's DKIM actually works

Worth understanding before you touch anything, because it changes the steps.

- When you add a sending domain, AcelleMail generates an **RSA keypair** locally and stores the private key. It builds the DNS records you need to publish — including the DKIM record.
- The DKIM record AcelleMail asks you to publish is a **TXT** record at `selector._domainkey.yourdomain.com`. (CNAME-style DKIM is something only certain external sending vendors use — for a domain AcelleMail signs itself, it's a TXT record containing the public key.)
- The **selector** is a single value set for your whole install (it defaults to `acelle`). It's the same for every domain you add. That matters for rotation: because the selector stays the same, your **new** DKIM TXT record sits at the **same host name** as the old one — so re-adding replaces the public key at one address rather than creating a second parallel record.

That last point is the key insight. There's no "old key + new key both live" period in AcelleMail. You swap the public key at one DNS host name and re-verify.

## Step 1 — Open Sending domains

In AcelleMail's sidebar, go to **Sending → Sending domains**. The page is titled **Sending Domains** and lists every domain you've added with a Verified / Unverified status.

This is your starting point. Note the domain you're about to rotate — you'll re-create it with the exact same name in a moment.

## Step 2 — Note your current DNS records (so you can compare later)

Click the domain name in the list to open its detail page (titled **Domain authentication**). You'll see:

- A status badge — **Verified** (green) or **Unverified** (orange).
- A **DNS records** card listing each record you need to publish, grouped by purpose. For a domain AcelleMail signs itself, that's **Domain identity** (the ownership TXT record) and **DKIM signature** (the TXT record carrying the public key). An **SPF record** row also appears if your administrator has configured a global SPF value for the install. Each row has a Type, Host, Value, and a per-record **Verified / Pending** status.

Take a screenshot or copy the current **DKIM signature** row's Value. After you re-add the domain, you'll compare the new DKIM TXT value against this one — they should differ (that's how you know you got a fresh key).

## Step 3 — Delete the domain

Back on **Sending → Sending domains**, select the domain's checkbox. A bulk-action bar appears with a **Delete** button. Click it and confirm — AcelleMail warns *"Delete selected sending domains? This cannot be undone."*

Deleting removes AcelleMail's record of the domain and its old private key. Your DNS records still physically exist at your host until you change them — deleting in AcelleMail doesn't touch your DNS.

> Heads up: from this moment until Step 6 completes, mail you send from this domain won't sign with a valid DKIM key. Keep the window short.

## Step 4 — Re-add the domain (this generates the new key)

Click **Add domain**. A popup appears with a single field — the domain name. Type the **exact same domain** you just deleted (e.g. `mail.brand.com`) and submit.

AcelleMail generates a **fresh DKIM keypair** for the domain and lands you back on the **Domain authentication** detail page, now showing **Unverified** with a fresh set of DNS records to publish.

## Step 5 — Publish the new DKIM record at your DNS host

On the detail page, the **DNS records** card shows the records to publish. The one that changed is the **DKIM signature** row — same Host as before (because the selector is unchanged), but a **new Value** (the new public key).

At your DNS host:

1. Find the existing DKIM TXT record at that host name (`selector._domainkey.yourdomain.com`).
2. **Update its value** to the new value AcelleMail shows. Copy the Value field verbatim — DKIM TXT values are long and a single dropped character breaks the signature.
3. Save.

Because the host name is identical, you're editing one record, not adding a second. If your DNS host won't let you edit in place, delete the old TXT and create a new one at the same name with the new value.

The card also shows the **Domain identity** (ownership) row, and an SPF row if your install has one configured. If AcelleMail shows a new ownership token after re-adding, update that record too; if it's unchanged, leave it.

## Step 6 — Re-verify

On the **Domain authentication** page, click **Verify DNS records** in the page header. AcelleMail queries your DNS and re-checks every record. While it works the button shows *"Verifying..."*.

- All records confirmed → *"Domain verified successfully! All DNS records are confirmed."* and the status badge flips to **Verified**.
- Still propagating → *"Verification in progress. Some DNS records are still pending — this can take up to 48 hours."*

DNS changes take anywhere from **15 minutes to 48 hours** to propagate worldwide (AcelleMail's own guide cards say this). If your DKIM row still shows **Pending**, wait and click **Verify DNS records** again — you can re-check as often as you like.

## Step 7 — Confirm the new key is signing

Once the domain reads **Verified**, send yourself a test campaign from an address on that domain. In Gmail, open the message → **⋮ → Show original** and look at the `DKIM-Signature:` line:

- `d=` should be your domain.
- `s=` should be your install's selector (the same one as before — the selector doesn't change; the key behind it did).
- The header should show `dkim=pass`.

That's the whole rotation. There's exactly one DKIM-Signature header (AcelleMail signs with one key), and it now uses your fresh keypair.

## Common issues

| What you see | What to do |
|---|---|
| DKIM row stays **Pending** after re-verify | DNS hasn't propagated. Wait (up to 48h) and click **Verify DNS records** again. Confirm the TXT value at your host matches AcelleMail's Value exactly. |
| Test email shows `dkim=fail` after re-add | The TXT value at your DNS host doesn't match — almost always a truncated or mis-pasted public key. Re-copy the Value from the **DKIM signature** row and replace it. |
| You added a *second* DKIM TXT record by mistake | Since the selector is unchanged, you only want one record at that host name. Delete the stale one so only AcelleMail's current value is published. |
| Domain shows **Unverified** but you didn't touch DNS | If you deleted and haven't re-added yet, that's expected. Finish Steps 4–6. |
| You can't find an **Edit** button on a domain | There isn't one — that's by design. To change a domain's key, you delete and re-add (this guide). |

## When to rotate

There's no enforced schedule and no app reminder, so this is your call:

- **Rotate now** if your DNS host had a security incident, or a compliance audit requires it.
- **Routine rotation** (e.g. once a year) is reasonable cryptographic hygiene but optional.
- **Don't rotate during a critical send window** (a big launch, a seasonal campaign). The short verify gap in Step 3–6 means fresh sends temporarily lose a passing DKIM signature — do it on a quiet day.

If you run several sending domains, rotate them **one at a time** and confirm each reads **Verified** before starting the next — far easier to debug than a batch.

## What to do after

- Confirm the domain reads **Verified** and a test email shows `dkim=pass` (Step 7).
- Re-check your overall authentication posture — see [How to Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).
- Keep an eye on delivery for a day or two after the swap via [Sender Reputation Monitoring Stack for AcelleMail](https://acellesend.com/kb/articles/sender-reputation-monitoring).

## Related articles

- [How to Set Up SPF, DKIM, and DMARC Records — The Basics](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Complete DNS Setup for Email Sending — The 6 Records You Need](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [DNS Propagation Explained — When Will My Records Be Live?](https://acellesend.com/kb/articles/dns-propagation-explained)
- [Sender Reputation Monitoring Stack for AcelleMail](https://acellesend.com/kb/articles/sender-reputation-monitoring)

---

# DMARC Enforcement Migration — From p=none to p=reject in 90 Days

> Going from no DMARC to p=reject all at once breaks your own legitimate mail. The 3-stage migration (none → quarantine → reject) is the safe path. This guide walks the 90-day plan, what to monitor at each stage, when to roll back.

- Canonical URL: https://acellesend.com/kb/articles/dmarc-enforcement-migration
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why staged migration

Jumping straight to `p=reject` (strict enforcement) is risky:

- If your SPF or DKIM has any misconfigurations, your own mail gets rejected
- Forwarded mail (newsletters → mailing lists → recipient inboxes) often fails SPF — sudden rejection means lost engagement
- Third-party services that legitimately send-on-behalf (Salesforce, Marketo, AcelleMail itself) may not be in your SPF — sudden rejection breaks integrations

The staged migration: `p=none` (monitor) → `p=quarantine` (junk folder) → `p=reject` (bounce). 30 days at each stage. Validate before stepping up.

## Stage 1: p=none (Days 0-30)

Publish:

```
TXT _dmarc.yourdomain.com  "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; ruf=mailto:dmarc-forensic@yourdomain.com; fo=1; pct=100"
```

What happens:

- Receivers send you aggregate reports daily (via `rua=`) — each contains: SPF/DKIM pass rate per source IP, per sending domain
- Failed messages still arrive in recipient inboxes — `p=none` doesn't enforce, just observes
- You discover surprises: forgotten third-party senders, mis-aligned envelope-senders, broken DKIM signing

**Monitor:**

1. Read the daily DMARC aggregate reports (XML files; use a parser)
2. In AcelleMail, watch the bounce log for any 5.7.x DSN codes (auth issues):

![Bounce log — DSN reasons](https://acellesend.com/plugins/acelle/landing/images/help/kb/smtp-error-codes/01-bounce-log-mixed.png)

3. Check your sending-server auth chips remain green:
### The auth chips on the sending-server detail

Open Settings → Sending servers → click your active server. The toolbar shows the live SPF / DKIM / DMARC chip status:

![Sending server detail with auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

- **Green chips on all three** = receiving servers can confirm you're authorized to send from this domain.
- **Any chip red** = receiving servers immediately downgrade reputation. Click *Verify domain* in the toolbar to walk through the DNS-fix wizard.

**Validate before Stage 2:**

- DMARC pass rate ≥99% in aggregate reports (last 7 days)
- No unauthorized "spoofing" sources in aggregate (all source IPs are recognized as yours)
- No 5.7.x bounce-spike in AcelleMail

If pass rate <99%: investigate the failing sources before proceeding.

## Stage 2: p=quarantine (Days 30-60)

Update to:

```
TXT _dmarc.yourdomain.com  "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com; fo=1; pct=100"
```

What happens:

- Failed-DMARC messages now go to recipient's Junk folder (not Inbox)
- Legitimate but mis-aligned messages start visibly degrading — engagement drops on poorly-authenticated sources
- Aggregate reports continue daily

**Monitor:**

1. Pass rate should stay ≥99% (your fixes from Stage 1 took effect)
2. AcelleMail's bounce log: 5.7.x rate should be near 0%
3. Open rate / click rate on your campaigns — should be flat (not dropping due to junk-foldering)

**Validate before Stage 3:**

- 14 consecutive days at pass rate ≥99.5%
- No customer complaints about "missing emails" routed to spam
- Aggregate reports show NO unauthorized sources

## Stage 3: p=reject (Day 60+)

Update to:

```
TXT _dmarc.yourdomain.com  "v=DMARC1; p=reject; rua=mailto:dmarc-reports@yourdomain.com; fo=1; pct=100"
```

What happens:

- Failed-DMARC messages are now BOUNCED — neither inbox nor junk
- Maximum enforcement; phishing attempts using your domain hit the wall
- Receiving servers reject at SMTP level (visible in your bounce log if you misconfigure)

**Monitor:**

1. Pass rate should remain ≥99.5%
2. Watch for any third-party-sender issues you missed (Salesforce, Mailchimp, etc. — if they send-as-you and weren't authorized)
3. Customer complaint volume

**This is the target state.** BIMI requires this stage (or quarantine minimum) before logos display.

## Rollback criteria

If at any stage:

- DMARC pass rate <99% AND you can't identify the failing source
- Customer complaints about "emails going to spam" spike
- Critical third-party-sender stops working

**Roll back to previous stage.** Update DNS to previous `p=` value. Wait 24-48h for cache propagation.

Diagnose the failure, fix, then re-attempt the step-up.

## Reading DMARC aggregate reports

Aggregate reports arrive at your `rua=` address as XML (often zipped). Sample:

```xml
<feedback>
  <policy_published>
    <domain>yourdomain.com</domain>
    <p>quarantine</p>
  </policy_published>
  <record>
    <row>
      <source_ip>54.240.0.10</source_ip>
      <count>1247</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>yourdomain.com</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>yourdomain.com</domain>
        <result>pass</result>
      </dkim>
      <spf>
        <domain>yourdomain.com</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
  ...
</feedback>
```

Parse via:

- **dmarcian.com** (free tier)
- **dmarcanalyzer.com** (free tier)
- **EasyDMARC** (free trial + paid)
- **Postmark DMARC Digests** (free; daily email digest)
- **Manual XML parse** (DIY; possible but tedious)

The free / freemium tools show:

- Per-source-IP send volume + pass rate
- Unauthorized senders trying to use your domain (phishing attempts)
- Per-recipient-domain breakdown (Gmail vs Outlook vs Yahoo)

## Common DMARC migration issues

| Symptom | Likely cause | Fix |
|---|---|---|
| Pass rate stuck at 90-95% in Stage 1 | Third-party sender not in your SPF | Add to SPF: `include:vendor.com` |
| Pass rate drops on weekends | Weekly automation script sends from unauthorized IP | Identify in aggregate report; whitelist or fix |
| AcelleMail sends pass; Mailchimp sends fail | Mailchimp's DKIM doesn't sign with your domain | Set up Mailchimp's "authenticate your domain" feature |
| Aggregate reports say "DKIM domain mismatch" | DKIM signing different domain than From: header | Ensure consistent domain alignment |
| Forwarded mail through old mailing-list fails | Forwarders break SPF | Move to DKIM-aligned sending OR set up ARC at the forwarder |
| Customer complaint: legitimate email in spam | Premature `p=quarantine` step | Roll back to `p=none`; investigate; resume |

## What `pct=` does

`pct=50` applies the policy to 50% of failing messages, lets 50% through. Useful for gradual rollout within a stage:

```
Week 1 of p=quarantine: pct=10  (10% to junk, 90% to inbox)
Week 2: pct=25
Week 3: pct=50
Week 4: pct=100  (full quarantine)
```

If problems surface, dial back without losing the staged-rollout progress.

<details>
<summary><strong>Advanced: subdomain DMARC policies + ARC for forwarding + multi-tenant DMARC at scale</strong></summary>

**Subdomain DMARC policies:**

The apex `_dmarc.brand.com` record applies to ALL subdomains by default. To override per-subdomain:

```
TXT _dmarc.brand.com         "v=DMARC1; p=quarantine; sp=reject; ..."
                                                    ^^^^^^^^^^
                                                    Subdomain policy
```

The `sp=` parameter sets the subdomain policy. Useful for strict subdomain enforcement (transactional subdomain) while keeping the apex at quarantine.

For explicit per-subdomain DMARC:

```
TXT _dmarc.mail.brand.com  "v=DMARC1; p=quarantine"
```

Overrides whatever the apex DMARC says for `mail.brand.com` specifically.

**ARC (Authenticated Received Chain) for forwarders:**

When email is forwarded (e.g. recipient's filter forwards to another address), the original SPF + DKIM can break. ARC records the original auth state so the final receiver knows it was legit at the original hop.

```
ARC-Authentication-Results: i=1; ...
ARC-Seal: i=1; a=rsa-sha256; ...
ARC-Message-Signature: ...
```

If you operate a mailing list / forwarder, configure ARC on your relay. AcelleMail signs ARC automatically when relevant.

**Multi-tenant DMARC at scale:**

For SaaS platforms sending on behalf of many customers, each customer's domain needs its own DMARC. The platform doesn't manage customer DMARC directly — customers do — but the platform must:

1. Provide each customer with proper SPF includes
2. Ensure DKIM signing uses customer's domain
3. Document the staged migration process (this article!) for customer guidance

Some platforms offer "managed DMARC" — paid feature where the platform monitors customer DMARC reports and adjusts policies.

**DMARC monitoring automation:**

```bash
#!/bin/bash
# Daily aggregate report processor
# Pulls DMARC reports from inbox, parses, aggregates pass-rate

mailbox_check_dmarc_reports
parse_xml_reports
calculate_pass_rate_last_24h

if [ $pass_rate -lt 99 ]; then
  notify_slack "DMARC pass rate dropped to $pass_rate% — investigate"
fi

if [ $pass_rate -ge 99 ] && [ "$current_policy" = "none" ] && [ $days_at_stage -ge 30 ]; then
  notify_slack "Stage 1 complete — ready to step up to p=quarantine"
fi
```

**Phasing in `pct=` for ultra-conservative rollout:**

```
Week 1: pct=5
Week 2: pct=10
Week 3: pct=25
Week 4: pct=50
Week 5: pct=75
Week 6: pct=100
```

12-week journey from p=none → p=reject with maximum conservatism. Reasonable for risk-averse industries (financial services, healthcare).

**DMARC SaaS platforms:**

If managing DMARC across 5+ domains:

- **EasyDMARC** — comprehensive, $50-200/month
- **DMARCLY** — budget-friendly, $20-100/month
- **Red Sift OnDMARC** — enterprise, $200+/month
- **Postmark DMARC Digests** — free; daily email summary

Most provide one-pane visibility + automated alerts + recommendations.

</details>

## Related articles

- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [SPF record deep dive](https://acellesend.com/kb/articles/spf-record-deep-dive)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [BIMI setup — show your logo in Gmail](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail)
- [Return-path and envelope-sender explained](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained)

---

# Gmail Postmaster Tools Walkthrough — IP Reputation for AcelleMail Senders

> Google Postmaster Tools is free. It shows your Gmail-side spam rate, IP reputation, domain reputation, and authentication pass rates. This guide walks the signup, the daily-check routine, and how to wire findings back into AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why Postmaster Tools

Gmail handles ~30% of consumer email globally and ~50% of business email. Their reputation engine is the most influential signal in your deliverability score. Google Postmaster Tools (free) shows:

| Signal | What it tells you |
|---|---|
| **Spam rate** | % of your mail Gmail users marked as Spam |
| **IP reputation** | Google's verdict on your sending IPs (High / Medium / Low / Bad) |
| **Domain reputation** | Google's verdict on your sending domain (same scale) |
| **Authentication** | Per-day SPF / DKIM / DMARC pass rates |
| **Encryption** | TLS adoption % on outbound mail |
| **Delivery errors** | Rate-limit / temp-fail / permanent-fail breakdowns |
| **Feedback loop** | Spam-trap hits (rare, severe signal) |

## Sign up + verify your domain

### Step 1: Go to postmaster.google.com

URL: https://postmaster.google.com

Click **Get started** → Google account login.

### Step 2: Add your sending domain

Click **+ Add domain**. Enter the domain you send from (e.g. `mail.example.com`).

Google shows a TXT record to add at your DNS host. Add it; click **Verify**.

Within ~5 minutes (often immediate), domain verified.

### Step 3: Wait for data

Google starts collecting Gmail-side signals from the moment you verify. Dashboard becomes populated within 24-48 hours.

If sends are <1,000/day to Gmail addresses, Google may not show meaningful data — too low a sample size. The threshold is ~1,000 Gmail recipients per day before Postmaster Tools generates meaningful panels.

## The 5 panels to check daily

### Panel 1: Spam rate

The most important signal. Daily plot of % users marked as Spam.

| Spam rate | Verdict |
|---|---|
| <0.1% | Excellent — well below Google's threshold |
| 0.1-0.3% | Healthy |
| 0.3-1% | Warning — investigate this week |
| >1% | Critical — Gmail starts throttling immediately |

Action on spike: pause sending; audit content + list; investigate consent. Recovery cycle = 2-4 weeks typically.

### Panel 2: IP reputation

Per-IP rating, one entry per IP you send from.

| Rating | What it means |
|---|---|
| **High** | Most reliable; full inbox delivery |
| **Medium** | Most mail inboxes; some routed to Junk |
| **Low** | Significant Junk routing |
| **Bad** | Most mail to Junk; possibly blocked |

If you see Bad on a fresh IP, expected during warmup — should climb to Medium → High over 2-4 weeks. If Bad on a mature IP, something broke — audit recent campaigns + bounce log.

### Panel 3: Domain reputation

Aggregate across all IPs that send from this domain. Slower to change than IP reputation; reflects long-term pattern.

### Panel 4: Authentication

Per-day SPF/DKIM/DMARC pass rates. Should be >99%. <95% means something's broken — re-verify domain in AcelleMail:

![AcelleMail sending-server with auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

Each chip should show Green. Click *Verify domain* if any are Red.

### Panel 5: Delivery errors

Breakdown of bounces by error type. Useful for comparing Postmaster Tools' Gmail-specific view with AcelleMail's all-recipient bounce log:

![Campaign bounce log](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/02-campaign-bounce-log.png)

If Postmaster shows 5% Gmail-specific errors but AcelleMail shows 1% overall, the issue is Gmail-side specifically — Gmail rate-limiting, Gmail reputation hit, etc.

## Daily-check action matrix

| Postmaster signal | AcelleMail action |
|---|---|
| Spam rate jumps from 0.05% → 0.15% | Audit yesterday's campaign content; pause if not obvious; check signup consent |
| IP reputation flips from High → Medium | Hold current volume; investigate possible reputation drop sources |
| IP reputation hits Bad | Pause that server; ramp-down volume; investigate root cause |
| Domain reputation drops | Slower-moving — usually weeks of degraded sends; bigger investigation needed |
| Authentication pass rate drops to <95% | Re-verify domain in AcelleMail; check DNS records |
| Spam-trap hit count >0 | List source has dead addresses or purchased data — audit imports |
| Delivery error spike (4.x retry) | Greylisting — wait 30 min; AcelleMail handles retry |

## Postmaster Tools confusion fixes

| What you see | What it actually means |
|---|---|
| "No data" panels after Day 7 | Your Gmail volume too low for meaningful aggregation; or domain not properly verified |
| Spam rate higher than expected even though bounce rate is fine | Spam rate = users-marked-as-spam (NOT bounce). High spam rate means content/list issue, not deliverability |
| IP reputation Bad despite high engagement | Your IP is shared with another sender who's getting complaints — request dedicated IP from vendor |
| Domain reputation Bad despite IP reputation High | Different IPs sending from your domain (one bad actor mixing); audit who else sends from this domain |
| Authentication < 95% | DKIM signature failing — check DKIM key matches DNS published record |

<details>
<summary><strong>Advanced: programmatic polling, daily-alert pipelines, and Bad-reputation recovery</strong></summary>

Postmaster Tools doesn't have a public API. Workaround: web-scraping or screen-scraping daily.

**Scraping pattern:**

Use Playwright or Puppeteer + a service account with Postmaster access:

```javascript
// daily-postmaster-scrape.mjs
import { chromium } from 'playwright';
const b = await chromium.launch();
const ctx = await b.newContext({ storageState: 'postmaster-auth.json' });
const page = await ctx.newPage();
await page.goto('https://postmaster.google.com');
const spamRate = await page.locator('.spam-rate-value').textContent();
const ipRep = await page.locator('.ip-reputation-row .rating-chip').first().textContent();
// ... POST to your monitoring system
```

Authenticate once interactively (creates `postmaster-auth.json`); subsequent runs are headless.

**Daily-alert pipeline:**

```
postmaster-scrape → metrics-collector → alert-on-threshold-cross
```

Pipe scraped metrics to Datadog / Grafana / Sentry. Threshold-based alerts:

- Spam rate > 0.2% → page on-call
- IP reputation Bad → page + auto-pause server in AcelleMail
- Auth pass rate < 95% → page

**Bad-reputation recovery cycle:**

When IP reputation hits Bad:

```
Day 0:  AUTO-PAUSE the affected sending server in AcelleMail
        Audit yesterday's campaigns from this server
        Identify root cause (content / consent / volume spike)
Day 1:  Send small volume (~500) ONLY to engaged-last-7d
        Monitor next 24h spam rate / inbox placement
Day 7:  If sustained healthy signals, expand to engaged-last-30d at 30% volume
Day 14: Expand to engaged-last-90d at 60%
Day 21: Resume full volume if reputation back to Medium/High
```

Recovery from Bad takes 3-6 weeks typically. Sometimes irrecoverable — need fresh IP + warmup cycle.

**Comparing Gmail-specific vs aggregate:**

AcelleMail's bounce log + Postmaster Tools = two views of the same delivery reality. Cross-reference:

```bash
# Get AcelleMail's last-24h Gmail-only stats
acelle_gmail_bounces=$(curl -sH "Authorization: Bearer $TOKEN" \
  "https://acellemail.com/api/v1/admin/bounces?domain=gmail.com&since=24h" \
  | jq '.total')

# Compare with Postmaster Tools delivery error count for same period
postmaster_gmail_errors=$(get_from_postmaster_scrape)

# Significant divergence → investigate
if [ $(( acelle_gmail_bounces / postmaster_gmail_errors )) -gt 3 ]; then
  echo "AcelleMail seeing 3x more Gmail bounces than Postmaster reports — audit"
fi
```

When AcelleMail's count significantly exceeds Postmaster's, your DNS attribution may be broken (mail not getting properly attributed to your domain). Investigate.

**Setting up postmaster for sub-domains:**

If you send marketing from `mail.example.com` and transactional from `txn.example.com`, verify EACH subdomain separately. Per-subdomain reputation = isolated. Helps prevent marketing reputation hits from poisoning transactional delivery.

**JMRP (Junk Email Reporting Program)** for Outlook is the Microsoft equivalent — separate signup. See [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough).

</details>

## Related articles

- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)
- [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)
- [Deliverability incident runbook](https://acellesend.com/kb/articles/deliverability-incident-runbook)
- [Why are my emails going to spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder)

---

# Microsoft SNDS Walkthrough — Outlook IP Reputation in AcelleMail Context

> Microsoft's Smart Network Data Services (SNDS) shows per-IP reputation for outlook.com / hotmail.com / live.com. This guide walks the SNDS signup, wiring AcelleMail's sending IPs in, and daily-checking the panel.

- Canonical URL: https://acellesend.com/kb/articles/microsoft-snds-walkthrough
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why SNDS matters

Microsoft's email properties — outlook.com, hotmail.com, live.com, msn.com — combined are ~15% of consumer email worldwide. Their reputation engine is independent of Gmail's: an IP that's High at Google Postmaster Tools can simultaneously be Red at Microsoft SNDS. You need both signals.

SNDS = Smart Network Data Services. Free. Microsoft-run. Shows per-IP:

| Signal | What it tells you |
|---|---|
| **Reputation** (Red/Yellow/Green) | Microsoft's aggregate verdict on the IP |
| **Filter result** (% Inbox / Junk / Blocked) | Where Microsoft routes your mail today |
| **Complaint rate** | Mark-as-spam clicks from Microsoft mailbox users |
| **Trap hits** | Sent to spam-trap addresses (red flag) |
| **Sample HELO + body** | What Microsoft sees — useful for debugging authentication |

## Sign up + register your IPs

### Step 1: Find your AcelleMail sending IPs

In AcelleMail, open the sending-server detail:

![Server detail — find your sending IP](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

For SMTP relay servers (SendGrid, Mailgun, Postmark, etc.), the sending IP is the vendor's shared pool — you usually don't have a dedicated IP. SNDS only gives meaningful data for dedicated IPs.

For Amazon SES with a dedicated IP / IP pool, get the IP from the SES console → Configuration → Dedicated IPs.

For self-hosted SMTP (e.g. Postfix on your own server), the sending IP is your server's outbound IP — run `curl ifconfig.me` from the host.

### Step 2: Sign up at sendersupport.olc.protection.outlook.com

URL: https://sendersupport.olc.protection.outlook.com/snds/

Click **Request access**. Microsoft asks for:

- Your Microsoft account (create one if needed — free)
- Each IP / IP range you control (one IP per line, or CIDR like `192.0.2.0/24`)
- Ownership verification — Microsoft will email each registered IP's `abuse@` and `postmaster@` accounts for confirmation

Within 24-72 hours, Microsoft confirms ownership and grants SNDS access.

### Step 3: Daily SNDS check

Once active, log in daily. The dashboard shows per-IP rows with:

```
IP            Reputation   Filter result    Complaint rate   Trap hits
192.0.2.10    GREEN        Inbox 98%        0.02%            0
192.0.2.11    YELLOW       Inbox 84%        0.18%            1
192.0.2.12    RED          Junk 100%        0.45%            7
```

Green is target; Yellow means watch; Red means PAUSE sending from that IP until you've fixed the underlying issue.

## Daily SNDS-driven action

| SNDS state | What it means | What to do in AcelleMail |
|---|---|---|
| Green, Inbox 95%+, complaint <0.1% | Healthy — full speed | Continue normal volume |
| Yellow, Inbox 70-85%, complaint 0.1-0.3% | Warning — reputation degrading | Hold current volume; do NOT add new segments; audit recent campaigns |
| Yellow, complaint >0.3% | Imminent block | Immediately PAUSE sending; investigate content + list source |
| Red | Already filtered out | PAUSE that server entirely; restore reputation via re-engagement track |
| Trap hits >0 | You hit a spam trap — bad list source | Audit imports; never buy lists; trap hits are reputation poison |

## Wire SNDS state back to AcelleMail
### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account with its status chip, sending limit, and last activity:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page surfaces Connection settings (host / credentials), Configuration (server name, default from, sending limit, bounce + FBL handler), and the **Test connection** / **Send test email** buttons in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

When SNDS flips a server's IP to Red, the appropriate AcelleMail action is to **Disable** that server temporarily — preventing further sends from a poisoned IP while you investigate.

In the sending server detail toolbar, click **Disable**. The server status flips to *Disabled*; the dispatcher routes around it.

After fixing the underlying issue (e.g. bad import purged, content audited), re-enable and ramp back up via the warmup pattern.

## Common SNDS confusion + fixes

| What you see at SNDS | What it actually means | What to do |
|---|---|---|
| "Yellow" but Inbox 90%+ | Microsoft sees mild concern (volume spike?) but mail still inboxes | Monitor closely; usually self-resolves |
| "Green" but complaints in AcelleMail spiked | AcelleMail's tracking captures different complaints than SNDS | Trust both signals; act on the alarming one |
| New IP shows no data | IP not registered with SNDS, OR <72h since registration | Confirm registration; wait for data |
| Filter result "Junk 30%" but you have no complaint signals in AcelleMail | Microsoft filtering for content reasons, not engagement | Audit subject line + body content |
| Trap hit count = 5 | Five separate spam-trap hits | This is a list-source problem. Audit imports. |
| Sample HELO shows unfamiliar domain | Authentication misalignment | Re-verify domain in AcelleMail sending-server detail |

## What SNDS DOESN'T show

| Signal | Where to look instead |
|---|---|
| Aggregate Microsoft mail delivery | AcelleMail campaign report bounce log + Microsoft Junk Email Reporting Program (JMRP) |
| Authentication results (SPF/DKIM/DMARC) | DMARC aggregate reports + AcelleMail server config chips |
| Per-recipient block | AcelleMail bounce log (DSN code 5.7.x patterns) |
| Yahoo / Apple / Other ISPs | Yahoo / Apple have their own (mostly retired) postmaster tools |

<details>
<summary><strong>Advanced: programmatic SNDS polling + automated server-disable on Red</strong></summary>

SNDS doesn't have a public API. Microsoft provides an opt-in IP-data feed: log into SNDS, then on the "Data Selection" page select daily CSV download. The CSV is published to your account's storage URL daily.

**Daily automation pattern:**

```bash
# Pull yesterday's SNDS CSV (you'll need to handle the login flow once, persist
# a session cookie or use SNDS' email-attachment delivery mode)
curl -sH "Cookie: $SNDS_COOKIE" \
  "https://sendersupport.olc.protection.outlook.com/snds/data.aspx?date=$(date -v-1d +%Y%m%d)&output=csv" \
  -o snds-yesterday.csv

# Parse + auto-disable any RED-reputation server
while IFS=, read ip reputation filter_pct complaint_pct trap_hits; do
  if [ "$reputation" = "RED" ]; then
    # Find the AcelleMail server for this IP
    server_uid=$(curl -sH "Authorization: Bearer $ADMIN_TOKEN" \
      "https://acellemail.com/api/v1/admin/sending-servers?filter[sending_ip]=$ip" \
      | jq -r '.data[0].uid')

    if [ -n "$server_uid" ]; then
      # Disable + alert
      curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
        -d "{\"server_uid\": \"$server_uid\"}" \
        "https://acellemail.com/api/v1/admin/sending-servers/disable"

      # Slack alert
      curl -X POST $SLACK_WEBHOOK \
        -d "{\"text\": \"🚨 SNDS RED on IP $ip — auto-disabled AcelleMail server $server_uid. Complaint rate: $complaint_pct. Trap hits: $trap_hits.\"}"
    fi
  fi
done < snds-yesterday.csv
```

Run daily at 04:00. SNDS catches reputation issues before AcelleMail's own bounce/complaint signals — earlier intervention = faster recovery.

**Per-IP rotation playbook when SNDS flips Red:**

```
Day 0:  SNDS shows IP X = Red. AUTO-DISABLE in AcelleMail.
Day 1:  Audit recent campaigns from this IP. Identify root cause:
        - Content issue?  → Edit copy; flag the template
        - List source?    → Pause that list; re-verify
        - Engagement gap? → Re-engagement campaign first
Day 7:  Re-enable IP at 10% of pre-incident volume.
        Send ONLY to engaged-last-7d segment.
Day 14: If SNDS back to Yellow, expand to engaged-last-30d at 30%.
Day 21: If SNDS Green, resume normal ramp.
Day 30: Full volume.
```

**JMRP (Microsoft Junk Email Reporting Program)** — companion to SNDS. Microsoft sends feedback loops (FBL) for every Mark-as-Spam click from Microsoft mailbox users.

Sign up: https://sendersupport.olc.protection.outlook.com/pm/junkmail.aspx

Once active, every complaint generates an email to your `feedback@yourdomain` address. AcelleMail's bounce/feedback handler can parse these automatically — set up the FBL handler in your sending server config. See [Feedback loops FBL setup](https://acellesend.com/kb/articles/feedback-loops-fbl-setup) for the full setup.

**SDDS — Sender Score Display** (deprecated) — older Microsoft signal; no longer maintained. Use SNDS + JMRP exclusively.

</details>

## Related articles

- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Gmail postmaster tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough)
- [Feedback loops FBL setup](https://acellesend.com/kb/articles/feedback-loops-fbl-setup)
- [IP warmup by ISP](https://acellesend.com/kb/articles/ip-warmup-by-isp)
- [Deliverability incident runbook](https://acellesend.com/kb/articles/deliverability-incident-runbook)

---

# Multi-Server Rotation in AcelleMail — How the Dispatcher Picks

> Once you have 2+ sending servers, the AcelleMail dispatcher distributes per-message across them. This guide explains the rotation logic, how to verify it in the admin pool, and how to override for deterministic routing.

- Canonical URL: https://acellesend.com/kb/articles/multi-server-rotation-pattern
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## How the dispatcher picks a server per message

When you launch a campaign with multiple servers in your plan's pool, AcelleMail's send-pipeline picks one server per message. The pick order:

1. **Filter by plan** — only servers attached to the campaign owner's plan are candidates
2. **Filter by quota** — servers that hit their daily/hourly cap drop out for this window
3. **Filter by warmup state** — servers in active warmup get priority for low-volume sends and are skipped for high-volume burst sends
4. **Round-robin within remaining candidates** — even distribution; no stickiness per recipient or per campaign

The pick happens at job-handler time (not at campaign-launch time). Mid-send, if one server hits quota or fails its health check, subsequent messages skip it automatically — no manual failover.

## Verify the rotation in the admin pool
### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account with its status chip, sending limit, and last activity:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page surfaces Connection settings (host / credentials), Configuration (server name, default from, sending limit, bounce + FBL handler), and the **Test connection** / **Send test email** buttons in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

For pool-wide visibility, open the admin view:

![Admin pool — total / active / inactive + per-type breakdown](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/08-admin-pool-list.png)

The "Server types" bar shows the composition of your pool. The per-server row shows daily / hourly usage. After launching a campaign, refresh every few seconds — usage counters increment across multiple servers, confirming rotation is active.

## Deterministic routing — when you need it

Round-robin is the right default. Some scenarios want deterministic routing:

| Scenario | Solution |
|---|---|
| Marketing on IP A, transactional on IP B | Two Plans: "Marketing" attaches Server A only; "Transactional" attaches Server B only. Assign customers/campaigns to the right plan. |
| EU customers sent from EU server (GDPR data residency) | Per-customer Plan assignment based on region tag |
| New IP being warmed — start at 10% traffic | Per-server warmup config sets daily cap that grows automatically |
| Campaign with extra-strict deliverability requirements | Attach to a Plan whose pool is a single high-reputation server |

### Setting it up via Sending Plans

In the admin sidebar **Plans → [plan] → Sending Servers**, attach the specific servers you want this plan's customers to use. Customers on this plan only see those servers; the dispatcher picks among them.

The admin server detail shows which plans each server is attached to:

![Admin server detail — plan attachments visible](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/09-admin-server-detail.png)

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| All campaigns hit Server A, B sits idle | Server B not attached to the plan, OR Server B's daily quota set to 0 | Admin → Plan → Sending Servers (attach); OR Server B → Sending limit (raise from 0) |
| Server A usage hits 100% daily quota at noon | Pool too small for daily volume | Raise per-server quotas OR add a third server |
| Rotation seems uneven (60/40 split) | One server is warming (lower quota) | Expected — the dispatcher avoids overloading the warming server |
| Mid-campaign one server flatlines | Health check failing — connection refused or auth error | Open that server's detail → Test connection → fix the credentials/network |
| Campaign log shows "Used Server X" for ALL messages | Single-server plan attached, OR all-but-one server disabled | Confirm via Admin → Plan; enable additional servers in plan attachment |

## Distribution patterns over time

Healthy multi-server distribution over a 30-day window should look like:

| Pattern | What it means |
|---|---|
| 33/33/34 across 3 servers | Healthy round-robin, all servers similar quota + warmup state |
| 80/15/5 across 3 servers | One large + two warming. Expected during ramp-up. |
| 60/40 across 2 servers | Server B has a lower daily cap or recently warmed up. Adjust caps to align. |
| 100/0 across 2 servers | Server B disabled or out of the plan. Bug — fix. |

<details>
<summary><strong>Advanced: weighted rotation, sticky routing, and quota-orchestration patterns</strong></summary>

AcelleMail's default round-robin is uniform. For non-uniform distribution patterns, the lever is per-server sending limit + how plans attach:

**Weighted rotation via quota** — to send 70% on Server A and 30% on Server B, set:

```
Server A sending_limit: 70,000/day
Server B sending_limit: 30,000/day
```

Pool dispatcher continues round-robin but Server B caps at 30k, after which only A receives more. Approximates a 70/30 weight at high volumes; can drift at low volumes due to round-robin.

**Sticky routing per recipient** — out of the box AcelleMail does NOT stick a recipient to a specific server. For sender-domain consistency (some receivers warm to a specific IP), the workaround is one-IP-per-tenant: create a separate Plan per tenant, each with a dedicated server. Operationally heavy; only worth it for high-stakes tenants.

**Quota orchestration across vendors** — for very high volume, the typical pattern is:

```
Tier 1 (highest reputation, lowest cost):  Amazon SES — 60% of volume
Tier 2 (mid reputation, growing):          Mailgun  — 25%
Tier 3 (newest IP, in warmup):             Postmark — 15%
```

Adjust quotas weekly as warmup-tier servers earn reputation. Auto-grow Postmark from 15% → 25% as bounce rate stays clean.

**Automated rebalancing** via API:

```bash
# Daily script: read per-server bounce rate, raise quota on clean + lower on dirty
for server_uid in $SERVER_UIDS; do
  br=$(curl -sH "Authorization: Bearer $ADMIN_TOKEN" \
    "https://acellemail.com/api/v1/admin/sending-servers/${server_uid}/stats" \
    | jq '.bounce_rate_last_24h')

  current=$(curl -sH "Authorization: Bearer $ADMIN_TOKEN" \
    "https://acellemail.com/api/v1/admin/sending-servers/${server_uid}" \
    | jq '.sending_limit')

  # If bounce rate < 1%, raise quota 10%; if > 3%, cut quota 25%
  new=$(python -c "
  br=$br; c=$current
  print(int(c*1.1) if br<0.01 else int(c*0.75) if br>0.03 else c)
  ")

  curl -X PATCH -H "Authorization: Bearer $ADMIN_TOKEN" \
    -d "{\"sending_limit\": $new}" \
    "https://acellemail.com/api/v1/admin/sending-servers/${server_uid}"
done
```

Run as a daily cron. Self-tuning pool that prefers clean servers, throttles dirty ones automatically.

**Failure-mode rotation** — when a server fails mid-send, AcelleMail's dispatcher marks it `degraded` and skips it for retries within the same campaign run. Re-evaluates per-server every 5 minutes via health-check ping. Tune the health-check interval in `config/sending-server.php` if you need faster failover (cost: more probe traffic).

</details>

## Related articles

- [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)
- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Sending throttling strategies](https://acellesend.com/kb/articles/sending-throttling-strategies)
- [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)

---

# Return-Path and Envelope-Sender — Quick Setup in AcelleMail

> The 5-minute setup: pick a bounce subdomain, publish the MX record, set the sending-server config, verify. This is the practical companion to [Return-path and envelope-sender explained](/kb/articles/return-path-and-envelope-sender-explained) — when you just want to do it.

- Canonical URL: https://acellesend.com/kb/articles/return-path-and-envelope-sender
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What you're doing

You're setting up a custom envelope-sender (also called "return-path" or "MAIL FROM domain") aligned to your sending domain. Instead of AcelleMail using the vendor's default like `bounces+xxx@amazonses.com`, your envelope-sender becomes `bounce@bounce.yourdomain.com` — passing strict DMARC alignment.

This article is the 5-minute setup. For the concept + why it matters, see [Return-path and envelope-sender explained](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained).

## Step 1: Pick a subdomain

Convention: `bounce.yourdomain.com` or `mail.yourdomain.com`. Reserved for envelope-sender / return-path.

```
bounce.brand.com   (recommended; explicit)
mail.brand.com     (also common)
return.brand.com   (sometimes used)
```

## Step 2: Publish the MX record

At your DNS host:

```
Type:     MX
Name:     bounce.yourdomain.com
Value:    feedback.acellemail.com   (or your install's bounce processor)
Priority: 10
TTL:      3600
```

The exact MX target is shown in your AcelleMail sending-domain detail page. Use that value.

Wait for DNS propagation (5-60 minutes typical).

## Step 3: Configure the sending server in AcelleMail

In AcelleMail's sidebar, **Sending → Sending servers** → click your active server. The detail page surfaces Connection settings + Configuration + Identity:

![Sending server detail — Configuration section](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

In the server detail's Configuration section, find the **MAIL FROM (Return-Path)** field. Enter your subdomain:

```
MAIL FROM: bounce.yourdomain.com
```

Save. From the next send onwards, AcelleMail uses this as the envelope-sender.

## Step 4: Verify

Send a test campaign to yourself. Open the received message → Gmail's "Show original" (or equivalent in your email client). Look for:

```
Return-Path: <bounce@bounce.yourdomain.com>
```

If you see your custom subdomain — done. If you see the vendor default (e.g. `bounces+xxx@amazonses.com`) — Step 3 didn't save; re-check sending server config.

Verify the DMARC alignment in AcelleMail's auth chips:
### The auth chips on the sending-server detail

Open Settings → Sending servers → click your active server. The toolbar shows the live SPF / DKIM / DMARC chip status:

![Sending server detail with auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

- **Green chips on all three** = receiving servers can confirm you're authorized to send from this domain.
- **Any chip red** = receiving servers immediately downgrade reputation. Click *Verify domain* in the toolbar to walk through the DNS-fix wizard.

DMARC chip should remain Green. If it flips Red after this change, your DMARC record needs subdomain alignment too — see [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration).

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Bounces stop arriving in AcelleMail | MX record not propagated | `dig MX bounce.yourdomain.com` — verify target matches AcelleMail's expected value |
| MAIL FROM field grayed out | Vendor doesn't support custom MAIL FROM (some shared-pool setups) | Switch to a vendor that supports custom MAIL FROM (SES, dedicated Mailgun, etc.) |
| Custom MAIL FROM saved but messages still use vendor default | Old campaign sends using cached config | Send a fresh test; only new campaigns use the new MAIL FROM |
| DMARC chip flips Red | DMARC alignment lost | Verify SPF includes the new MAIL FROM domain |

## Aligned vs unaligned

**Aligned:**
```
From:          hello@brand.com
Envelope-Sender: bounce@bounce.brand.com
SPF check on:  bounce.brand.com (subdomain of brand.com)
DMARC: pass via aligned SPF
```

**Unaligned (default vendor):**
```
From:          hello@brand.com
Envelope-Sender: bounces+xxx@amazonses.com
SPF check on:  amazonses.com (different domain)
DMARC: pass via DKIM only (or fail if strict alignment)
```

Both work for most receivers. Aligned is preferred for B2B + strict-alignment-enforcing receivers.

<details>
<summary><strong>Advanced: multi-vendor envelope-sender management + per-tenant subdomains + automated verification</strong></summary>

**Multi-vendor envelope-sender:**

If you use multiple sending vendors, each can have its own envelope-sender subdomain:

```
Server A (SES):       bounce-ses.brand.com  → feedback-ses.acellemail.com
Server B (Mailgun):   bounce-mg.brand.com   → feedback-mg.acellemail.com
Server C (Postmark):  bounce-pm.brand.com   → feedback-pm.acellemail.com
```

Per-vendor MX. Per-vendor sending-server config. All envelope-senders align to brand.com via subdomain leeway.

**Per-tenant in SaaS context:**

For SaaS sending-on-behalf-of-customers:

```
Customer A: bounce.customerA.com → managed by your platform
Customer B: bounce.customerB.com → managed by your platform
```

Each customer adds the MX record at their DNS host. Your platform's AcelleMail config sets per-customer envelope-sender.

**Automated verification:**

```bash
#!/bin/bash
# Daily check — verify all sending-server MAIL FROM domains have correct MX
servers=$(curl -sH "Authorization: Bearer $TOKEN" \
  "https://acellemail.com/api/v1/admin/sending-servers" | jq -r '.data[].uid')

for server in $servers; do
  mail_from=$(curl -sH "Authorization: Bearer $TOKEN" \
    "https://acellemail.com/api/v1/admin/sending-servers/$server" \
    | jq -r '.mail_from_domain')

  if [ -n "$mail_from" ]; then
    mx=$(dig MX $mail_from +short | head -1)
    if [ -z "$mx" ]; then
      echo "🚨 Server $server: MAIL FROM=$mail_from has no MX record"
    fi
  fi
done
```

**Custom envelope-sender for transactional vs marketing:**

```
Transactional server: bounce-txn.brand.com  (separate reputation pool)
Marketing server:     bounce-mkt.brand.com  (separate reputation pool)
```

Bounces from each route separately. Marketing reputation problems don't pollute transactional reputation.

**Vendor-specific notes:**

- **Amazon SES**: Set "MAIL FROM domain" in SES Verified Identities. Add MX record `bounce.brand.com → feedback-smtp.us-east-1.amazonses.com` (region-dependent)
- **SendGrid**: Whitelabel feature (paid tier). Configure via SendGrid Senders → Domain Authentication
- **Mailgun**: Built-in; configure via Domain → DNS Records
- **Postmark**: Built-in; configure via Servers → Server Settings → Custom MAIL FROM

Each vendor has slightly different MX target. Refer to vendor docs OR check AcelleMail's sending-domain detail page for the right value.

</details>

## Related articles

- [Return-path and envelope-sender explained](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)

---

# Amazon SES Sending Limits — Cookbook for AcelleMail Operators

> SES starts at 200/day sandbox, scales to 50k+/day in production, $0.10 per 1,000 emails. This guide is the practical cookbook: how to grow from sandbox to scale, region-by-region orchestration, what AcelleMail does at each tier.

- Canonical URL: https://acellesend.com/kb/articles/ses-sending-limits-cookbook
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## SES quota tiers

| Tier | Daily quota | Notes |
|---|---|---|
| Sandbox | 200/day | Default. Recipients must be verified. Free. |
| Production (new) | 50,000/day | After production-mode request. Approval ~24h. |
| Production (raised) | Up to 1,000,000/day | After 30+ days clean sending; manual request. |
| Dedicated IP | Independent of shared-pool quotas | $24.95/mo per IP. Higher reputation control. |

Plus a per-second send rate: 1/sec sandbox, 14/sec new-production, up to 500+/sec with raised limits.

## Recipe 1: Get out of sandbox

Sandbox = 200/day, recipients must be verified. Useless for marketing.

To leave sandbox:

1. AWS Console → SES → Account dashboard → **Request production access**
2. Form: use case, expected volume, complaint/bounce handling
3. Approval: 24-48 hours typical

Be specific + honest. AWS rejects:
- "Send to a purchased list" — instant denial
- Vague descriptions — back-and-forth, slows approval
- No mention of unsubscribe + consent process — denial

Good template:

```
Use case: Marketing email for SaaS customers who explicitly opted in via signup form. We currently have 25,000 subscribers across 3 lists in [region], all collected via double-opt-in over the past 2 years. Expected volume: 50,000/month initially, growing to 250,000/month over 12 months. Bounces and complaints are auto-processed via AcelleMail's bounce + FBL handlers; unsubscribe links present in every email.
```

## Recipe 2: Configure SES in AcelleMail
### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account with its status chip, sending limit, and last activity:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page surfaces Connection settings (host / credentials), Configuration (server name, default from, sending limit, bounce + FBL handler), and the **Test connection** / **Send test email** buttons in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

For SES specifically:

### Pick the SES type

Type picker:

![Type selector](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/06-type-selector.png)

Two SES options:

- **Amazon SES (API)** — uses SES SendEmail API. Faster, better tracking. Recommended.
- **Amazon SES (SMTP)** — uses SES SMTP relay. Vendor-neutral, slightly slower.

Pick API for the AcelleMail-side install. The API form:

![Amazon SES API create form](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/12-create-form-amazon-api.png)

The SMTP alternative — if you need the vendor-neutral integration:

![Amazon SES SMTP create form](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/20-create-form-amazon-smtp.png)

### Configure credentials + region

```
AWS Access Key ID:     [from IAM SES-FullAccess user]
AWS Secret Access Key: [paired with above]
Region:                [where you verified your domain — us-east-1, eu-west-1, etc.]
Default from email:    [verified address on your domain]
Sending limit:         50000 (for production-default daily quota)
```

Save. AcelleMail validates immediately — *Active* badge or inline error.

### Verify the auth chips

Server detail shows the auth status:

![Sending server with SPF/DKIM/DMARC chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

All three should be Green if your SES setup is correct. If any Red, the verify-domain wizard will walk you through DNS records.

## Recipe 3: Raise the per-account quota

After ~30 days of clean sending, you can request raised limits:

1. AWS Console → SES → Account dashboard
2. **Service quotas** (in the AWS account, not just SES) → search "ses"
3. Find: "Sending quota" + "Sending rate"
4. **Request quota increase**

Approval: ~24-48h. AWS sees your bounce rate + complaint rate; if clean, approval is automatic.

| Current | Request up to | Typical approval |
|---|---|---|
| 50k/day | 200k/day | Immediate if metrics clean |
| 200k/day | 1M/day | Up to 1 week, manual review |
| 1M/day | 5M+/day | Account-team negotiation |

## Recipe 4: Dedicated IP for premium reputation

At $24.95/mo, dedicated IP gives:

- Full reputation control (your traffic only)
- Better postmaster signals (per-IP rep)
- Pool of dedicated IPs available for rotation

Setup:

1. AWS SES → **Dedicated IPs** → **Request dedicated IP**
2. Wait 1-2 days for provisioning
3. AcelleMail: no config change required; SES routes via dedicated IP automatically

For a pool of dedicated IPs:

```
AWS SES → Configuration Sets → "production-marketing"
  Dedicated IP pool: [ip1, ip2, ip3]
  Tracking: enabled
  CloudWatch destination: enabled

AWS SES → Configuration Sets → "production-transactional"
  Dedicated IP pool: [ip4]
  Tracking: disabled (transactional doesn't need engagement tracking)
```

In AcelleMail admin: set the Configuration Set in the sending-server advanced config. Campaigns automatically use the right IP pool.

## Recipe 5: Multi-region for global audience

If your audience is global, set up SES in 3 regions:

```
us-east-1     (Virginia)   - US recipients, best AWS-global integration
eu-west-1     (Ireland)    - EU recipients, GDPR data residency
ap-southeast-1 (Singapore)  - APAC recipients
```

Process:

1. Verify your domain in EACH region (separate verification per region)
2. Add 3 sending servers in AcelleMail (one per region)
3. Tag subscribers by region: `tag:us`, `tag:eu`, `tag:apac`
4. Create 3 Plans, each scoped to a single regional server
5. Assign campaigns to the right Plan based on segment

Cost: SES is $0.10/1k regardless of region. Multi-region adds zero marginal cost beyond the verified-domain effort.

## Recipe 6: Monitoring

Daily checks:

1. **AcelleMail bounce log** — last 24h pattern:

![Campaign bounce log](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/02-campaign-bounce-log.png)

Target: bounce rate <2%, complaint rate <0.1%.

2. **AWS CloudWatch SES metrics:**
   - `BounceRate` → keep <5%
   - `ComplaintRate` → keep <0.1%
   - `Reputation` → high
   - `SendsForGeneralRecipient` → trending up

3. **Google Postmaster Tools** for Gmail-specific signals (see [walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough))

4. **Microsoft SNDS** for Outlook-specific (see [walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough))

If any signal goes red, AWS may auto-suspend your account. Recovery requires audit + AWS engagement.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| New SES server status *Testing* indefinitely | Region mismatch (verified in us-east, server set to eu-west) | Edit credentials → match region |
| Test send fails "Email address not verified" | Still in sandbox; can only send to verified addresses | Request production access |
| Production approved but 200/day cap still applied | Daily quota allocation is gradual; AWS raises over days | Send at max daily; AWS auto-raises if clean |
| Bounce rate over 5% | SES auto-pauses accounts above 5% | Pause; clean list; resume slowly |
| Complaint rate over 0.1% | SES auto-pauses accounts above 0.1% | Pause; audit consent; investigate before resume |
| All emails to one region failing | That region's verification expired or DKIM key rotated | Re-verify in that region; update sending server config |
| Tracking shows 0% opens | SES open-tracking requires Configuration Set with tracking enabled | Add Configuration Set in admin advanced |

## Cost math

```
50k/day SES production = 1.5M/month = $150/month
With 3 dedicated IPs = $150 + $75 = $225/month

vs Postmark same volume = $1500+/month
vs SendGrid same volume = $1500+/month
vs Mailgun same volume = $1000+/month
```

SES is the cheapest at scale by 5-10×. Worth the AWS-side complexity.

<details>
<summary><strong>Advanced: SES auto-pause recovery, IAM least-privilege, multi-account orchestration</strong></summary>

**SES auto-pause recovery:**

When SES auto-pauses your account due to bounce/complaint rate spike:

```
Day 0: Auto-paused notification from AWS
       AWS console: SES → Account dashboard → Status: Paused
Day 1: Audit + identify root cause
Day 2: Compose detailed response to AWS, including:
       - Root cause analysis
       - Remediation actions taken (list cleaned, sending paused, etc.)
       - Process changes preventing recurrence
Day 3-7: Submit request to AWS Support to lift the pause
Day 7-14: AWS reviews; if convinced, lifts the pause
Day 14: Resume sending at 50% of pre-incident volume, slowly ramp back
```

Frequency: most accounts hit auto-pause once (the learning event). Repeat offenses get manual review + potentially account suspension.

**IAM least-privilege:**

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "SESSendOnly",
    "Effect": "Allow",
    "Action": [
      "ses:SendEmail",
      "ses:SendRawEmail",
      "ses:GetSendQuota",
      "ses:GetSendStatistics"
    ],
    "Resource": "*",
    "Condition": {
      "StringEquals": {
        "ses:FromAddress": "noreply@yourdomain.com"
      }
    }
  }]
}
```

Restricts the AcelleMail IAM user to send-only from a specific address. If credentials leak, blast radius is contained.

**Multi-account orchestration:**

For SaaS operators with high volume, split across multiple AWS accounts:

```
AWS Account A: SES production, dedicated IPs 1-5
  Verifies domains for Customer-Tier-1

AWS Account B: SES production, dedicated IPs 6-10
  Verifies domains for Customer-Tier-2

AWS Account C: SES production, shared pool only
  Verifies domains for Customer-Tier-Free
```

Per-account quotas + dedicated IPs scale linearly. Some operators use 5+ AWS accounts to total 5M/day SES capacity.

In AcelleMail: configure each AWS account as a separate sending server. Customer plans attach to the appropriate server tier.

**Bounce/complaint webhook via SNS:**

```bash
# SES → Configuration Set → Event Destinations → Add SNS
# Topic: "ses-bounces"
# Events: bounce + complaint

# Subscribe AcelleMail to the SNS topic via HTTP endpoint
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:ACCT:ses-bounces \
  --protocol https \
  --notification-endpoint https://acellemail.com/webhooks/ses-bounces
```

AcelleMail's bounce handler receives bounces in real-time, updates subscriber statuses, removes hard-bounced addresses immediately.

**Configuration Sets for advanced features:**

```
# Set up tracking + dedicated pool
aws ses put-configuration-set-tracking-options \
  --configuration-set-name production-marketing \
  --tracking-options CustomRedirectDomain=track.yourdomain.com

aws ses put-configuration-set-event-destination \
  --configuration-set-name production-marketing \
  --event-destination Name=CloudWatch,Enabled=true,MatchingEventTypes=send,reject,bounce,complaint
```

Configuration Sets are powerful but complex. Use them when you need per-campaign-class tracking or pool routing.

**SES + AcelleMail performance tips:**

1. Use API (not SMTP) for higher throughput
2. Set Configuration Set tracking; AcelleMail captures opens/clicks via your tracking domain
3. Per-region servers in AcelleMail for global audience
4. Match sending limit to vendor quota explicitly
5. Daily AWS CloudWatch + AcelleMail bounce/complaint dual-check
6. Quarterly review: are you outgrowing SES capacity? Plan multi-vendor or multi-account.

</details>

## Related articles

- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail)
- [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)
- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)

---

# SPF Record Deep Dive — Mechanisms, Limits, and Edge Cases

> SPF looks simple: list your sending IPs. The real-world version: 10-lookup limits, include-chains, the difference between -all and ~all, SPF flattening at scale. This is the operator's deep dive.

- Canonical URL: https://acellesend.com/kb/articles/spf-record-deep-dive
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## SPF in one line

SPF tells receiving servers which IPs (or which other domains' SPF records) are authorized to send from your domain. Published as a TXT record at the apex domain.

```
v=spf1 include:amazonses.com -all
```

Translation: "Allow Amazon SES to send for me; REJECT everything else."

## The mechanisms

| Mechanism | Meaning | Example |
|---|---|---|
| `ip4:1.2.3.4` | Authorize this specific IPv4 | `ip4:54.240.0.10` |
| `ip4:1.2.3.0/24` | Authorize this CIDR | `ip4:1.2.3.0/24` |
| `ip6:...` | Authorize IPv6 | `ip6:2001:db8::1` |
| `include:other.com` | Pull in other.com's SPF record | `include:amazonses.com` |
| `a` | Authorize this domain's A record IP | `a` (the domain's own A) |
| `a:mail.brand.com` | Authorize a specific subdomain's A record | `a:mail.brand.com` |
| `mx` | Authorize the MX records' IPs | `mx` |
| `exists:%{i}.spamcheck.brand.com` | Macro-based check (rarely used) | (advanced) |

## The qualifiers (before each mechanism)

| Qualifier | Action |
|---|---|
| `+` (or none) | PASS — authorized |
| `~` | SOFTFAIL — receiver-discretion (typically goes to spam) |
| `-` | FAIL — REJECT |
| `?` | NEUTRAL — no policy stated |

The `all` at the end is the catch-all: `-all` (hard fail) is the most strict; `~all` (soft fail) is conservative for new setups.

```
v=spf1 ip4:1.2.3.4 ~all
# Allows 1.2.3.4; soft-fail everything else (likely spam folder)

v=spf1 ip4:1.2.3.4 -all
# Allows 1.2.3.4; HARD-FAIL everything else (rejected)
```

Use `~all` until production stable, then switch to `-all`.

## The 10-lookup limit

SPF lookups are recursive: `include:amazonses.com` triggers a DNS lookup for amazonses.com's SPF record, which may include further records, recursively.

RFC 7208 caps recursive lookups at **10 per evaluation**. Beyond 10, receivers return `PermError` and the SPF check fails.

Each of these counts as 1 lookup:

- Every `include:` (and the includes inside it count too, recursively)
- Every `a` and `mx`
- Every `ptr` (deprecated)
- Every `exists`

`ip4:` and `ip6:` do NOT count (no DNS lookup needed).

Multi-vendor blowup example:

```
v=spf1 include:amazonses.com include:_spf.mailgun.org include:sendgrid.net include:spf.mtasv.net -all
```

Each include resolves to ~3-4 sub-includes:

- `amazonses.com` → 1 (amazonses.com itself) + 1 (sub-include) = 2 lookups
- `_spf.mailgun.org` → 1 + 2 = 3 lookups
- `sendgrid.net` → 1 + 4 = 5 lookups
- `spf.mtasv.net` → 1 + 1 = 2 lookups

Total: 2 + 3 + 5 + 2 = 12 lookups → SPF PermError → ALL your mail fails SPF check.

## SPF flattening (the fix at scale)

Flattening = pre-resolve all includes + replace with IPs in your published record:

```
# Before (might exceed 10):
v=spf1 include:amazonses.com include:_spf.mailgun.org include:sendgrid.net -all

# After (flattened):
v=spf1 ip4:54.240.0.0/18 ip4:13.32.0.0/15 ip4:54.240.36.0/22 ip4:104.197.0.0/16 ... -all
```

Trade-off:

- ✅ No 10-lookup issue
- ✅ Faster receiver-side check
- ❌ When vendors update IP ranges, your flattened record becomes stale
- ❌ Without automated re-flattening, you'll silently fail SPF when ranges shift

Tools that auto-flatten daily:

- **ValiMail SPF** — paid; handles re-publishing via your DNS host's API
- **EasySPF** — paid; similar
- **DMARCLY SPF** — included in their DMARC platform
- **Manual script + cron** — DIY, possible but high-maintenance

For 1-2 vendor setups, manual is fine. 3+ vendors, use a tool.

## Verify in AcelleMail
### Open the sending-domain detail

In AcelleMail's sidebar, **Sending → Sending domains**. The list shows every domain you've registered with status chips (Verified / Pending / Failed) and per-auth indicators:

![Sending domains list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into your domain row. The detail page surfaces exactly which DNS records to publish (TXT for SPF, CNAMEs for DKIM, TXT for DMARC) with copy-paste-ready values + current verification state per check:

![Sending-domain detail — DNS records + auth status](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

The DKIM CNAME and DMARC TXT records are visible alongside SPF. The detail page shows live verification status — re-run with the *Verify domain* button after publishing changes.

## Common SPF errors + fixes

| Error | Cause | Fix |
|---|---|---|
| **PermError** (10-lookup exceeded) | Too many includes | Flatten via tool; OR remove unused vendor includes |
| **TempError** (DNS timeout) | DNS resolver couldn't reach one of the includes | Usually self-resolves; persistent = check vendor's DNS health |
| **Fail / Hard fail** | Sending IP not in your SPF record | Add the vendor's `include:` OR `ip4:` |
| **None** (no SPF record published) | Missing TXT record | Publish per [Complete DNS setup](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending) |
| **Softfail** intentional but mail goes to spam | Some receivers treat `~all` as suspicious | Switch to `-all` after production-stable |
| **Pass** but DMARC still fails | SPF + DKIM + DMARC alignment issue | Check envelope-sender domain matches From: |

## Real-world SPF records

```
# Small sender, single vendor
v=spf1 include:amazonses.com -all

# Multi-vendor mid-size
v=spf1 include:amazonses.com include:_spf.mailgun.org -all

# Large enterprise with corporate-mail mixed in
v=spf1 include:_spf.google.com include:amazonses.com include:_spf.mailgun.org include:sendgrid.net -all
# (4 includes — close to 10-lookup limit; consider flattening)

# Self-hosted Postfix on a fixed IP
v=spf1 ip4:1.2.3.4 -all

# Hybrid: self-hosted + transactional via SES
v=spf1 ip4:1.2.3.4 include:amazonses.com -all
```

## What SPF doesn't do

SPF alone is NOT sufficient for deliverability:

| Concern | SPF's role | Other records |
|---|---|---|
| Sender authentication | ✅ (per-IP) | DKIM (per-message), DMARC (alignment) |
| Message integrity | ❌ | DKIM (cryptographic signature) |
| Phishing protection | Partial | DKIM + DMARC together |
| BIMI logo display | Required base | DKIM + DMARC + BIMI |

SPF is necessary but not sufficient. Always pair with DKIM + DMARC.

<details>
<summary><strong>Advanced: SPF macro syntax + multi-domain alignment + per-vendor flattening tools</strong></summary>

**SPF macros (rarely needed, mentioned for completeness):**

```
v=spf1 exists:%{i}.spamcheck.brand.com -all
```

Macro `%{i}` expands to the sender IP. The `exists` mechanism checks if that DNS name resolves. Used for per-IP spam-list integration. Almost never needed for AcelleMail use cases.

Other macros:

- `%{s}` — sender (envelope-sender)
- `%{l}` — local-part of envelope-sender
- `%{d}` — domain
- `%{p}` — sender's PTR record (deprecated due to performance)
- `%{h}` — HELO domain

**Multi-domain alignment (for subdomain senders):**

```
Apex (brand.com):       v=spf1 include:amazonses.com -all
Subdomain (mail.brand.com): v=spf1 include:amazonses.com -all
```

Each subdomain needs its own SPF record. Don't rely on the apex to apply to subdomains automatically (DNS doesn't work that way for TXT records).

**Per-vendor SPF flattening tools:**

```
ValiMail SPF       — $50-100/month; vendor-agnostic
EasySPF            — $20-50/month; basic
DMARCLY SPF        — included with DMARC tier
PowerDMARC SPF     — included with DMARC tier
```

For self-managed flattening, the script:

```bash
#!/bin/bash
# Daily SPF flatten + republish
# Replace IP ranges from vendor's published SPF includes

current_spf=$(dig TXT yourdomain.com +short | grep "v=spf1")
new_includes=""

for vendor in amazonses.com _spf.mailgun.org sendgrid.net; do
  ips=$(dig TXT $vendor +short | grep -oE 'ip4:[0-9./]+|ip6:[0-9a-f:/]+')
  new_includes="$new_includes $ips"
done

new_spf="v=spf1$new_includes -all"
echo "Updating SPF: $new_spf"

# Update via Cloudflare API (adjust per your DNS host)
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -d "{\"type\":\"TXT\",\"name\":\"yourdomain.com\",\"content\":\"$new_spf\"}"
```

Run as a daily cron. Manual fallback when tool subscriptions feel expensive.

**SPF compliance audit:**

```bash
# Daily check on all your sending domains
domains=$(curl -sH "Authorization: Bearer $TOKEN" \
  "https://acellemail.com/api/v1/admin/sending-domains" | jq -r '.data[].name')

for d in $domains; do
  spf=$(dig TXT $d +short | grep "v=spf1")
  if [ -z "$spf" ]; then
    echo "🚨 $d: SPF MISSING"
    continue
  fi
  lookups=$(echo $spf | grep -oE "include:|a:|mx" | wc -l)
  if [ $lookups -gt 8 ]; then
    echo "⚠️ $d: high lookup count ($lookups) — risk of PermError"
  fi
done
```

**SPF macros for compliance reporting:**

For per-customer SPF visibility in SaaS context:

```
v=spf1 include:_customer.%{d}.spf.acellemail.com -all
```

Per-customer SPF macro that resolves dynamically. Advanced; typically operator-managed by AcelleMail SaaS providers.

</details>

## Related articles

- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)
- [Return-path and envelope-sender explained](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained)

---

# Domain Warmup — Building Reputation on a New Sending Domain

> IP warmup is half the story. Your sending domain also accrues reputation independently — and a fresh domain takes 30-60 days to reach full standing at major ISPs. This guide is about the domain side: separate from but interlocking with IP warmup.

- Canonical URL: https://acellesend.com/kb/articles/domain-warmup
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Domain reputation vs IP reputation

They're related but distinct.

**IP reputation** is per-sending-IP. New IP → no history → blocked or filtered until you've sent enough clean volume.

**Domain reputation** is per-sending-domain (the part after `@`). New domain → no history → similar filtering.

A typical scenario: you've sent from `mail.example.com` for years (high domain rep). You add a new sending IP (low IP rep). You need to warm the IP, but the domain rep helps — Gmail is forgiving of a high-rep domain on a new IP.

Reverse case: well-established IP (your shared SES IP), but new domain. Gmail's per-domain reputation engine sees `newdomain.com` as unknown. Throttles or filters.

Both must be healthy for inbox placement.

## Why a fresh domain takes 30-60 days

ISPs aggregate signals over time:

- Days 1-7: Almost no domain rep data. Most mail goes to Spam by default.
- Days 7-21: Engagement signals build (opens, click-throughs). Reputation climbs.
- Days 21-45: Domain rep stabilizes. Inbox placement improves.
- Days 45-60: Mature domain rep. Normal sending volume sustainable.

Sending too much from a fresh domain on Day 5 → recipients mark as spam → spam-rate spike → reputation drops permanently → start over with a different domain.

## Subdomain strategy

Best practice: send marketing from a subdomain, NOT from your main brand domain.

```
brand.com           → corporate/transactional (low volume)
mail.brand.com      → marketing
news.brand.com      → newsletters
notify.brand.com    → transactional (password resets, etc)
```

Why subdomains?

1. **Reputation isolation** — bad marketing campaigns don't damage your brand's primary domain rep
2. **Different policies** — easier to set distinct SPF/DKIM/DMARC per use case
3. **Cleaner recovery** — if `mail.brand.com` gets hit, you don't lose the brand domain
4. **Customer-side targeting** — recipients can whitelist `mail.brand.com` without whitelisting everything else

In AcelleMail, configure each subdomain as a separate sending domain. The **Sending → Sending domains** screen lists every domain you've registered with status chips and authentication state:

![Sending domains list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into a domain row to see its full detail: SPF / DKIM / DMARC records to publish at your DNS host, current verification status per check, and the per-domain DKIM keypair fingerprints:

![Sending domain detail — DNS records + auth status](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

Verify SPF/DKIM/DMARC per subdomain. The Verify-domain workflow in this view shows you exactly the DNS records to copy-paste into your DNS host's TXT/CNAME panel.

## Per-domain warmup curve

Day-by-day volume on the new domain:

| Day | Daily volume | Engagement target |
|---|---|---|
| 1 | 200-500 (engaged 7d only) | >30% open rate |
| 3 | 1,000-2,000 | >25% open rate |
| 7 | 5,000-10,000 | >20% open rate |
| 14 | 20,000-30,000 | >15% open rate |
| 21 | 50,000+ | >12% open rate (B2C average) |
| 30 | 100,000+ | Normal segment baseline |
| 45 | Full volume | Normal segment baseline |
| 60 | Same | Mature domain rep |

If engagement target is missed, hold or step down. Engagement is the leading signal for domain rep.

## Authentication is non-negotiable

A fresh domain MUST publish SPF / DKIM / DMARC from Day 0:
### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account with its status chip, sending limit, and last activity:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page surfaces Connection settings (host / credentials), Configuration (server name, default from, sending limit, bounce + FBL handler), and the **Test connection** / **Send test email** buttons in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

The auth chips on the server detail show real-time status:

![Sending server auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

All three Green = receiving servers can trust messages from your domain. Any Red → critical block, no warmup will succeed until fixed.

**SPF** — TXT record at root `yourdomain.com`:
```
v=spf1 include:amazonses.com include:_spf.mailgun.org ~all
```
(Include the sending vendors you use.)

**DKIM** — CNAME records as shown in your sending-server setup wizard.

**DMARC** — TXT at `_dmarc.yourdomain.com`:
```
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com
```
Start with `p=none` (monitor mode) for first 30 days. Move to `p=quarantine` then `p=reject` as auth pass rates settle above 99%.

## Cross-reference with Postmaster Tools

Once Google Postmaster Tools is set up for your domain, the domain reputation panel shows your build-up:

```
Day 7:  Domain Reputation: Low
Day 21: Domain Reputation: Medium
Day 45: Domain Reputation: High
```

If you see Domain Bad at any point, something broke:
- Authentication failures (auth panel <95%) → re-verify
- Spam rate >0.3% → audit content + list
- Volume spike → step back

See [Gmail Postmaster Tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough) for the dashboard setup.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Domain shows in Google Postmaster but no data | Volume too low (<1000/day to Gmail) | Send more Gmail-segment volume; data appears after 7-14 days |
| Domain Reputation Bad on Day 7 | Authentication failing OR content content-trigger spam | Re-verify in AcelleMail; audit recent campaign content |
| Domain Reputation Low even after 30 days | Sending too much too fast | Step back to 30-day baseline; rebuild engagement |
| Authentication pass <95% in Postmaster | DKIM key mismatch or rotation failure | Check DKIM CNAMES at DNS host; re-verify in AcelleMail |
| All metrics fine but inbox placement still <70% | Likely content + frequency issue, not domain rep | Audit subject lines + send cadence; consider engagement-only segments |
| Subdomain rep separate from main brand | Working as designed | This is good — isolated reputation is the safe pattern |

<details>
<summary><strong>Advanced: BIMI for verified brand display + DMARC enforcement progression + multi-subdomain strategy</strong></summary>

**BIMI (Brand Indicators for Message Identification)** lets your verified domain show a logo next to your messages in Gmail (US), Yahoo, Apple Mail, others. Requires:

1. DMARC at `p=quarantine` or `p=reject`
2. SVG-formatted logo published at your domain
3. VMC (Verified Mark Certificate) from Entrust or DigiCert (~$1500/yr per domain)

BIMI doesn't directly boost reputation, but the trust signal does. Recipients see a verified logo → click-through rates rise ~10-15%.

**DMARC enforcement progression:**

```
Day 1:      p=none, rua=mailto:dmarc@yourdomain.com
            (Monitor mode; receivers report SPF/DKIM failures)
Day 30:     Confirm 99%+ pass rate via aggregate reports
Day 45:     p=quarantine; failing emails go to Junk
Day 60:     Confirm still 99%+ pass rate
Day 90:     p=reject; failing emails are bounced
```

The progression is conservative for a reason — if you jump to `p=reject` while your DKIM is broken, you reject your own legitimate mail. Stage carefully.

**Aggregate DMARC reports** (free, from receiving servers) are XML files. Parse them via:

```bash
# DMARC report XML reader
zcat aggregate.xml.gz | grep -E "<source_ip|<count|<disposition" | head -20
```

Tools like dmarcian.com or postmarkapp.com/dmarc parse + visualize for you.

**Multi-subdomain strategy at scale:**

For SaaS operators with multiple brands:

```
Customer A: ┌── mail.customerA.com     (their marketing)
            └── notify.customerA.com   (their transactional)

Customer B: ┌── mail.customerB.com     (their marketing)
            └── notify.customerB.com   (their transactional)
```

Each subdomain warmed independently. Customer A's reputation problems don't bleed to Customer B.

In AcelleMail's admin: configure per-customer Sending Plans + per-plan Sending Domains. Each customer sees only their own subdomains.

**Domain rotation for reputation recovery:**

If a domain gets hit irreversibly, retire it. Move to a fresh subdomain:

```
old-mail.brand.com (poisoned) → Disable
new-mail.brand.com (fresh)    → New warmup cycle, day 1
```

Update all signup forms + DNS + AcelleMail server config to point at the new subdomain.

**Spam trap exposure:**

Spam traps are addresses ISPs use to detect bad senders. Hitting them = severe reputation damage. Common trap categories:

- **Recycled traps** — once-real addresses that haven't been used in years; ISPs repurpose them
- **Pristine traps** — never-real addresses created specifically to catch bad senders
- **Honeypots** — addresses posted publicly to catch scrapers

Avoid by:

1. NEVER buy lists
2. Use double opt-in (cleanest signal)
3. Run email verification before importing
4. Use AcelleMail's bounce log to identify pattern-matching traps (often 5.7.x with specific reason text)

**Domain reputation in the wild — measurement tools:**

- Google Postmaster Tools (primary signal)
- SNDS (Outlook-specific signal)
- mxtoolbox.com → "Email Health"
- mail-tester.com → 0-10 score for a specific message
- DMARC aggregate reports

Use 2-3 signals; no single tool tells you everything.

</details>

## Related articles

- [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)
- [Gmail postmaster tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough)
- [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)
- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)

---

# Feedback Loops (FBL) — Setup + Wire in AcelleMail

> When a recipient clicks "Mark as Spam" on a Microsoft / Yahoo / AOL / Comcast mailbox, those ISPs send you the complaint via Feedback Loop. AcelleMail processes it automatically — unsubscribes the complainer, updates the complaint stats. This guide walks the setup.

- Canonical URL: https://acellesend.com/kb/articles/feedback-loops-fbl-setup
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What FBL does for you

When a recipient on a major ISP (Microsoft, Yahoo, AOL, Comcast) clicks "Mark as Spam," the ISP sends a feedback message to you describing the complaint. AcelleMail's Feedback Loop handler:

1. Receives + parses these messages from a dedicated address (e.g. `feedback@yourdomain.com`)
2. Identifies the complaining subscriber + the original campaign
3. Auto-unsubscribes them (prevents future sends)
4. Updates the campaign's complaint rate stat
5. (Optional) Webhook out to your CRM / Slack

Without FBL setup, you don't see these signals. Complaints pile up silently until your IP reputation drops.

## Setup overview

```
[ISP side]                          [AcelleMail side]
1. Sign up for each ISP's FBL program
   → ISP needs an address to send to
2. Set up a `feedback@yourdomain` mailbox
                                     3. Configure FBL handler in sending server
                                     4. Test + verify
```

## Phase 1: Sign up at each ISP

### Microsoft (JMRP)

URL: https://sendersupport.olc.protection.outlook.com/pm/junkmail.aspx

Provide:
- Each sending IP / IP range
- Your contact + verification address (e.g. `feedback@yourdomain.com`)
- Microsoft email account (free)

Approval: 24-48 hours.

### Yahoo

URL: https://senders.yahooinc.com/

Sign up for the Yahoo Feedback Loop service. Provides per-IP complaint forwarding to your verification address.

### AOL / Verizon

URL: https://postmaster.aol.com/

Similar process. Free.

### Comcast / Xfinity

URL: https://postmaster.comcast.net/

Form-based application.

### Apple iCloud / Yahoo Postmaster

iCloud doesn't have a public FBL program currently. The signals come via:
- Bounce log patterns (5.7.x codes for spam-policy blocks)
- iCloud's "Mail Privacy Protection" data via aggregate analytics

For iCloud, FBL isn't a setup task — accept that aggregate-level signals are all you get.

## Phase 2: Set up the receiving mailbox

Create a regular mailbox at `feedback@yourdomain.com` (any name; convention is `feedback@`). This is where ISP-generated FBL messages arrive.

The mailbox needs:
- IMAP or POP3 access (so AcelleMail can read it)
- Always-accessible (don't delete messages immediately — AcelleMail polls and acts)

Configure at your usual mail host (Google Workspace, Microsoft 365, Mailtrap, anywhere with IMAP).

## Phase 3: Configure AcelleMail's FBL handler
### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account with its status chip, sending limit, and last activity:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page surfaces Connection settings (host / credentials), Configuration (server name, default from, sending limit, bounce + FBL handler), and the **Test connection** / **Send test email** buttons in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

In the server detail's Configuration section (visible in the full-page server detail above), find the **Feedback loop handler** dropdown. The handlers themselves are managed as a reusable list at **Admin → Sending → Feedback loop handlers**:

![Admin Feedback loop handlers list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/15-admin-fbl-handlers.png)

Each handler is a named IMAP mailbox config — reusable across multiple sending servers. Define once, attach where needed. Click **+ New feedback loop handler** to add a fresh one, OR edit the Default FBL Handler to point at your `feedback@yourdomain.com` IMAP credentials.

Back in the sending-server's Configuration field, the FBL handler dropdown lets you pick:

- **None** — FBL processing disabled (default)
- **IMAP** — AcelleMail polls an IMAP mailbox, parses FBL messages, processes them
- **POP3** — Same via POP3 protocol

Pick **IMAP** (most modern). The expanded form asks for:

```
IMAP server:    imap.gmail.com (or your provider)
Port:           993 (TLS)
Username:       feedback@yourdomain.com
Password:       app-specific password
Poll interval:  5 minutes
```

Save. AcelleMail starts polling the mailbox.

## Phase 4: Verify it's working

Send yourself a test campaign. On a Microsoft / Yahoo account you control, click "Mark as Spam."

Wait 24-48 hours (FBL has delay). Then check:

1. The feedback message arrived in `feedback@yourdomain.com`
2. AcelleMail polled it (check the server's FBL processing log)
3. Your test address is now marked `unsubscribed` in the original list
4. Campaign feedback log shows the complaint:

![Campaign feedback log](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/03-campaign-feedback-log.png)

If steps 1-2 work but 3-4 don't: check AcelleMail's FBL parser is recognizing the ISP's specific FBL format. Some ISPs use ARF (Abuse Reporting Format); others have custom headers.

## Common FBL signals + actions

| Signal | Action |
|---|---|
| Steady 0.05-0.1% complaint rate | Healthy baseline; no action |
| Spike to 0.3% on one campaign | Pause; audit content + list; investigate consent |
| Spike concentrated to one segment | That segment's consent is unclear — audit signup flow |
| 0 complaints (suspicious for any volume) | FBL handler not working — check IMAP polling logs |
| Complaints from subscribers who never opted in | Bot signups or imported list — audit acquisition |
| Yahoo complaints high, Microsoft normal | Yahoo-specific list source issue; investigate Yahoo signups specifically |

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| FBL configured but `unsubscribed` count from FBL = 0 after a week | Either FBL handler broken OR you're not sending to FBL-eligible ISPs | Check IMAP credentials + recent campaign log |
| `feedback@yourdomain` mailbox grows but AcelleMail doesn't process | IMAP polling broken or FBL message format unrecognized | Re-check IMAP creds; check AcelleMail's FBL parser config |
| ISP sends bounce messages that look like FBL but get marked as bounces | Bounce handler conflicting with FBL handler | Use different mailboxes for each (bounce@ and feedback@) |
| Complaint rate looks too good (0% on high-volume sends) | FBL probably not wired up or wired to wrong mailbox | Check end-to-end with test send-and-complain |
| Complaints showing in AcelleMail but subscribers not auto-unsubscribed | FBL handler set to log-only, not auto-action | Server config → FBL → set "Auto-unsubscribe complainers" |

<details>
<summary><strong>Advanced: ARF parsing, multi-ISP routing, and complaint-rate-driven server disable</strong></summary>

The Abuse Reporting Format (ARF) is RFC 5965 — the standard FBL message format. Microsoft, Yahoo, AOL use it. Others have custom formats.

**ARF format key fields:**

```
Message-ID: <...>
Feedback-Type: abuse
User-Agent: ARF/1.0
Reported-Domain: ...
Original-Mail-From: ...
Original-Rcpt-To: ...
Source-IP: ...
Reported-URI: ...
```

AcelleMail's parser identifies the `Reported-Rcpt-To` to find the subscriber, looks up the campaign via `Original-Mail-From` headers, and processes the complaint.

For non-ARF formats (some legacy AOL formats), AcelleMail has format-specific parsers. If a particular ISP's format isn't recognized, check the AcelleMail FBL parser config.

**Multi-mailbox routing** — if you have multiple sending servers, each can have its own FBL mailbox:

```
Server A:  feedback-a@yourdomain.com
Server B:  feedback-b@yourdomain.com
```

Each server's FBL handler points to its own mailbox. Allows per-server complaint-rate isolation in the panels.

**Complaint-rate-driven server disable:**

```bash
# Daily cron — disable any server with complaint rate >0.3% yesterday
for server in $SERVER_UIDS; do
  cr=$(curl -sH "Authorization: Bearer $TOKEN" \
    "https://acellemail.com/api/v1/admin/sending-servers/$server/stats?day=yesterday" \
    | jq '.complaint_rate')
  if (( $(echo "$cr > 0.003" | bc -l) )); then
    # Disable + alert
    curl -X POST -H "Authorization: Bearer $TOKEN" \
      -d "{\"server_uid\": \"$server\"}" \
      "https://acellemail.com/api/v1/admin/sending-servers/disable"
    notify_slack "Server $server auto-disabled — complaint rate $cr"
  fi
done
```

**FBL message inspection** — periodically open the `feedback@` mailbox + read messages directly. Useful for:

- Confirming ISPs are still sending FBL (drop = something broke)
- Understanding WHY complaints happen (content, frequency, consent issues — sometimes in the FBL body)
- Verifying complaining subscriber matches what AcelleMail processed

**Suppression list export** — after AcelleMail processes FBL complaints, the suppression list is the cumulative pool of all-time complainers + bounced + manually-suppressed.

```bash
curl -sH "Authorization: Bearer $TOKEN" \
  "https://acellemail.com/api/v1/admin/suppression?format=csv" \
  > suppression-snapshot-$(date +%Y%m%d).csv
```

Backup periodically. If you ever migrate AcelleMail installs, the suppression list MUST come too.

**ISP-specific quirks:**

- **Microsoft JMRP**: also need SNDS for full picture
- **Yahoo**: some delays; complaints can arrive 5-7 days after sending
- **AOL**: very low volume; expect 5-20 complaints/month from large lists
- **Comcast/Xfinity**: form-based; AcelleMail FBL parser handles it

**Privacy / GDPR notes:**

FBL messages contain the complaining subscriber's email. Treat them as personal data:

- Don't share or export externally
- Retain only as long as needed for unsubscribe + statistics
- Honor GDPR right-to-erasure if the complainer later requests

</details>

## Related articles

- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)
- [Email list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Deliverability incident runbook](https://acellesend.com/kb/articles/deliverability-incident-runbook)

---

# Install AcelleMail on AWS EC2

> This guide has moved. The install walkthrough for any server, an AWS EC2 instance included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/install-acellemail-on-aws-ec2
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, an AWS EC2 instance included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# Install AcelleMail on Debian 12 (Bookworm)

> This guide has moved. The install walkthrough for any server, a Debian 12 server included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/install-acellemail-on-debian-12
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, a Debian 12 server included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# Install AcelleMail on a DigitalOcean Droplet

> This guide has moved. The install walkthrough for any server, a DigitalOcean droplet included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/install-acellemail-on-digitalocean
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, a DigitalOcean droplet included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# Install AcelleMail on Rocky Linux 9

> This guide has moved. The install walkthrough for any server, a Rocky Linux 9 server included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/install-acellemail-on-rocky-linux-9
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, a Rocky Linux 9 server included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# Install AcelleMail on Ubuntu 24.04 LTS

> This guide has moved. The install walkthrough for any server, an Ubuntu 24.04 server included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, an Ubuntu 24.04 server included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# IP Warmup by ISP — Per-Receiver Ramp Strategy in AcelleMail

> Gmail, Microsoft, Yahoo, and iCloud each react differently to a brand-new IP. A single uniform warmup curve ramps too fast for the strict receivers and too slow for the forgiving ones. This guide shows how to split warmup by recipient ISP using AcelleMail's tags, segments, and per-server sending limits — all from the UI, no scripts.

- Canonical URL: https://acellesend.com/kb/articles/ip-warmup-by-isp
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why split warmup by ISP

A fresh sending IP has no reputation anywhere, but the major mailbox providers don't all decide that at the same speed. Gmail tends to reward engagement quickly. Microsoft (Outlook / Hotmail / Live) is sensitive to volume spikes. Yahoo and Apple iCloud are more conservative and give you the least feedback when they don't like what they see.

If you run a single warmup curve for the whole list, you're stuck splitting the difference: ramp fast enough to keep Gmail happy and you over-send to the strict receivers; ramp slow enough for iCloud and you waste days of Gmail's appetite. Splitting the warmup by receiving ISP lets each provider get the pace it's comfortable with.

The mechanics are the same three building blocks AcelleMail already gives you — all from the dashboard, no SQL, no scripts:

1. **Tag** each subscriber with the ISP they belong to.
2. Build one **segment** per ISP from those tags.
3. Reserve a **rate-limited sending server** for each ISP's campaign and ramp its limit independently.

## Step 1 — Tag subscribers by their mailbox provider

You don't need to touch the database. The Subscribers table lets you search by email text, select everything that matches, and bulk-apply a tag.

Open your list, go to the **Subscribers** tab, and type a provider's domain into the search box — for example `gmail.com`.

Once the table is filtered, open the **Select** dropdown at the top of the filter bar. It offers **Whole page** (just the visible rows) and **All subscribers** (everything matching the current filter) — choose **All subscribers** so the tag lands on every match, not just page one. Then open the **Actions** dropdown and click **Add tag(s)**. In the popup, enter a tag like `isp:gmail` and confirm.

Repeat for each provider you want to warm separately. A practical starting set:

| Search term | Tag to apply |
|---|---|
| `gmail.com` (and `googlemail.com`) | `isp:gmail` |
| `outlook.com`, `hotmail.com`, `live.com` | `isp:outlook` |
| `yahoo.com`, `ymail.com`, `aol.com` | `isp:yahoo` |
| `icloud.com`, `me.com`, `mac.com` | `isp:icloud` |

Run a separate search + **Select → All subscribers** + **Add tag(s)** pass for each domain. The remainder — corporate domains behind Proofpoint, Mimecast, Cisco, and the like — you can leave untagged and handle as a general group, or tag as `isp:b2b`.

> **Tip:** if you're importing a fresh list, you can carry tags in at import time too. AcelleMail's import recognizes a column literally named `tags`; on the import wizard's settings step, the **Also overwrite tags with the value from your CSV's "tags" column** option lights up when that column is present. So if your export already has a provider/tags column, you won't have to back-fill by hand.

## Step 2 — Build one segment per ISP

A segment is a saved, dynamic filter — as tags change, the segment's membership updates automatically. That's exactly what you want for a multi-week warmup.

In your list, open the **Segments** tab and click **Create segment**. Give it a name like `Gmail — warmup`, then in the **Conditions** block click **Add condition**.

In the condition row:

- Open the field dropdown and pick **Tag**.
- Set the operator to **has tag**.
- Enter `isp:gmail` as the value.

Save. Repeat for `isp:outlook`, `isp:yahoo`, and `isp:icloud`. You now have one segment per ISP that you can target independently.

> The tag operators AcelleMail offers are **has tag** and **does not have tag** — that's all you need here. If you ever want to combine conditions (say, Gmail *and* opened in the last 30 days), the **Matching** dropdown at the top of the form switches the segment between matching *all* conditions and *any* condition.

## Step 3 — Reserve a rate-limited server per ISP

This is where the actual throttling happens. In AcelleMail the sending rate lives on the **sending server**, not on the campaign — so the way to give each ISP its own ramp is to point each ISP's campaign at a server whose limit you control.

### Set the per-server sending limit

In the sidebar, go to **Sending → Sending servers** and click into the server you're warming.

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

On the server's edit page, the **Configuration** card has a **Sending limit** field. It's a dropdown of presets plus a **Custom** option — pick **Custom** to set an exact value, a time base, and a unit (per minute / hour / day).

![Sending limit field — custom quota value, base, and unit](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

AcelleMail enforces this limit for that server: once the server hits its per-period quota, the queue holds back until the period rolls over. There's no separate throttle inside the campaign — you set it once here, per server.

For a true per-ISP warmup you'd run a separate server (and ideally a separate IP) for the strict receivers and a more permissive one for Gmail, then raise each server's limit on its own schedule as that ISP's reputation grows.

### Point the campaign at that server

When you create the warmup campaign, the setup step has a **Show advanced settings** toggle; under it sits the **Sending servers** section. By default a **Use all servers randomly** switch is on, meaning the campaign draws from every server in your pool. Turn that switch off to narrow the pool, then tick only the server you reserved for this ISP. If you leave the switch on, the campaign uses your whole pool — fine for general sends, but for warmup you want the explicit pick so the ISP's traffic only flows through its dedicated, rate-limited server.

## Step 4 — Run one campaign per ISP, ramp independently

For each ISP, on each warmup day:

1. Create a new campaign.
2. On the **Recipients** step, choose that ISP's **list**, then in the **Segment** picker select the ISP segment. Left untouched it stays on **All subscribers** and sends to everyone — so be sure to pick the segment.
3. On the setup step, open **Show advanced settings**, turn off **Use all servers randomly**, and tick the **sending server** you reserved for that ISP.
4. **Schedule** it (or launch immediately).

Because the throttle is on the server, you ramp by editing that server's **Sending limit** — not by changing anything on the campaign. As an ISP's reputation strengthens, raise its server's limit; if you see trouble, lower it. The strict receivers (Microsoft, Yahoo, iCloud) get a slower, lower ramp; Gmail can climb faster.

> **General principle, not exact numbers:** start each ISP small and increase only when the previous send came back clean. The conservative providers should reach full volume days after Gmail does. Resist the urge to ramp all four on the same curve — that's the whole reason you split them.

## Step 5 — Standardize the curve with a Warmup Strategy (admin)

If you operate the install (admin access), the **Warmup Strategies** screen lets you save a reusable ramp definition and attach it to a sending server, so the ramp-up guidance and safety thresholds stay consistent.

![Warmup Strategies — built-in strategies with growth curve and risk level](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/16-admin-warmup-strategies.png)

Each strategy starts from one of three **presets** — **Cautious**, **Balanced**, or **Aggressive** — which you then customize: **Starting volume**, **Daily increment**, **Growth strategy** (linear or exponential), **Max bounce rate**, and so on. You can save as many named strategies as you like; the screenshot above shows several, including a custom long-haul strategy built for enterprise senders.

For per-ISP warmup, the pattern is: attach a **Cautious** (or a custom slow) strategy to the servers carrying Microsoft / Yahoo / iCloud traffic, and a **Balanced** strategy to the Gmail server. Each server then ramps on its own assigned curve.

## Watch the signals as you ramp

You don't need a per-domain dashboard inside AcelleMail to run this — you mainly need to keep each ISP's bounces and complaints low before you step its volume up. Two reliable places to watch:

- **The campaign's Bounce log** — open a campaign → **Bounce log** tab. Each failed delivery is listed with the recipient, a **Bounce type** badge (the kind the receiver reported), and the receiving server's verbatim **Reason**. Because you sent one campaign per ISP, the bounce log for the Gmail campaign *is* your Gmail signal, the Yahoo campaign's log is your Yahoo signal, and so on — no cross-domain filtering needed.
- **The provider's own postmaster tools** — Google Postmaster Tools for Gmail and Microsoft SNDS for Outlook give you reputation and spam-rate data straight from the receiver. Yahoo and iCloud don't publish a postmaster portal, so for those you lean on the per-campaign bounce log.

The rule to ramp by: hold an ISP at its current server limit if that ISP's last campaign shows rising bounces or complaints; only raise the limit after a clean send.

## Common issues

| What you see | What to do |
|---|---|
| Segment shows fewer subscribers than you expected | The tag wasn't applied to everyone — re-run the Subscribers search + **Select → All subscribers** + **Add tag(s)** for that domain. Remember to use **All subscribers** (matches the whole filter), not **Whole page**. |
| One ISP's bounces creep up mid-warmup | Lower that ISP's server **Sending limit** and hold for a few days before trying to step it up again. |
| Campaign went to the whole list instead of one ISP | The **Segment** picker was left on **All subscribers**. Edit the campaign's Recipients step and choose the ISP segment for the list. |
| Campaign used the wrong server | **Use all servers randomly** was left on, so it used the whole pool. Re-open the setup step's **Show advanced settings**, turn the switch off, and tick the server you reserved for that ISP. |

## What to do after

- Once an ISP is fully warmed, fold its segment back into your normal sends — the segment keeps updating as tags change, so it stays useful for ongoing reputation isolation.
- Keep an eye on overall reputation, not just warmup, with [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring).

## Related articles

- [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)
- [Multiple Sending Servers in AcelleMail — Add, Mix, Route](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)
- [Gmail Postmaster Tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough)
- [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)
- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)

---

# Securing Your AcelleMail Install: A Post-Install Checklist

> Your AcelleMail install works the moment the web installer turns green — but two things now live on your server that didn't before: your subscribers' personal data, and your domain's sending reputation. This checklist secures both, in priority order, without turning you into a sysadmin. Most of it is config you set once.

- Canonical URL: https://acellesend.com/kb/articles/post-install-hardening-checklist
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: reference · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 12 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Your AcelleMail install is *working* the moment the [web installer](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404#step-10--run-the-web-installer) turns green. But "working" and "ready to put your subscribers on" are not the same thing. Two valuable things now live on your server that weren't there an hour ago:

1. **Your subscribers' personal data** — names, email addresses, sometimes phone numbers and custom fields. That's personal data, and you're now its custodian.
2. **Your domain's sending reputation** — every campaign signs with your domain. Get this wrong and your own legitimate mail lands in spam.

This checklist secures both, **in the order that matters**. It is not a generic Linux-hardening course — those exist, and we point to one at the end. It's the short list of things that, if you skip them, actually hurt *an email-marketing app specifically*.

Good news up front: self-hosting AcelleMail is genuinely manageable. Most of what follows is configuration you set once and never touch again. Budget about 30–40 minutes, do it in the first hour, and you're done.

> **How to read this:** each item says *why it matters for an email app* first, then *what to do*. If a "why" doesn't apply to you, skip that item consciously rather than blindly running commands.

## Priority 1 — Protect subscriber data

This is the one that turns a bad day into a reportable data breach. Lead here.

### 1.1 — Put the admin panel behind HTTPS, always

Every login, every subscriber export, every API token travels over this connection. On plain HTTP it's readable by anyone on the network path. Your install guide already set up a Let's Encrypt certificate — this step just makes sure AcelleMail *insists* on using it.

In your `.env`, confirm:

```
APP_URL=https://mail.example.com
FORCE_HTTPS=true
```

`FORCE_HTTPS=true` makes AcelleMail emit `https://` links even when it sits behind a proxy that terminates TLS (Cloudflare, an AWS load balancer). After editing `.env`, clear AcelleMail's config cache so the change takes effect (`php artisan config:clear` from the install directory, or your panel's "Clear cache" action).

> Don't have a certificate yet? That's a 5-minute fix — see the **TLS** step of your platform's install guide ([Ubuntu](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404#step-7--tls-with-certbot)).

### 1.2 — Don't run in debug mode

When `APP_DEBUG=true`, any uncaught error renders a full stack trace — which can include your database password and other `.env` values — straight into the browser. On a public site that's a credential leak waiting for the first error. It's the most common self-hosted misconfiguration, so check it explicitly — your `.env` must read:

```
APP_ENV=production
APP_DEBUG=false
```

Fix it if needed, then clear the config cache again.

### 1.3 — Keep the database off the public internet

Your subscriber data lives in MySQL/MariaDB. The AcelleMail installer creates a database user scoped to `localhost`, which is correct — but confirm the database server itself isn't listening to the whole world. On Ubuntu/Debian, `bind-address` in `/etc/mysql/mysql.conf.d/mysqld.cnf` should be `127.0.0.1`, never `0.0.0.0`. On a managed database (RDS, DigitalOcean Managed DB) this lives in the provider's security group instead — restrict it to your app server's IP.

You don't need to micromanage database grants for a single-app install; the installer's `localhost`-scoped user is already right. The thing that actually bites people is forgetting the firewall, which Priority 5 covers in one step.

### 1.4 — Set up automated backups *before* you import a single list

The cheapest backup you'll ever take is the first one, when the database is empty. Six months and 200 campaigns later, the temptation to "skip it just this once" is what kills people. A daily dump plus an off-site copy means a fat-fingered delete or a dead disk costs you a day, not your business.

A local copy alone doesn't survive the server dying, so pair the daily dump with an off-site target (S3, Backblaze, or your provider's snapshots) and test a restore once. The full pattern — schedule, off-site copy, retention, restore drill — is its own guide. **Treat this as non-optional, not "nice to have."**

➡️ [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups)

### 1.5 — Lock down the files that hold secrets

Your `.env` holds the database password, mail credentials, and the `APP_KEY` that encrypts session data. It should be readable by the web user and nobody else, and the rest of the install shouldn't be world-writable — only `storage/` and `bootstrap/cache/` need write access. If PHP is ever compromised, correct file permissions are what stop an attacker rewriting your app files. Your install guide sets these during setup; re-confirm them if you've been copying files around as root.

> **Want full encryption-at-rest** (encrypted volumes, encrypted backups) for regulated data? That's the next level up — see [Data Encryption for Self-Hosted Platforms](https://acellesend.com/kb/articles/data-encryption-for-self-hosted-platforms).

## Priority 2 — Protect your sending reputation

This is what's unique about an *email* app. A normal web app can be sloppy here and only its own uptime suffers. With AcelleMail, getting this wrong means mailbox providers start junking your campaigns — and reputation is slow to earn back.

### 2.1 — Get SPF, DKIM, and DMARC right (non-negotiable)

Gmail, Yahoo, and Microsoft now expect bulk senders to pass all three — and they must *align* to pass DMARC, not merely exist. In plain terms:

- **SPF** lists which servers may send for your domain.
- **DKIM** signs each message so receivers can prove it's really from you and untampered. Use a 2048-bit key.
- **DMARC** tells receivers what to do when a message fails the other two.

AcelleMail generates the DKIM key for you and shows the exact DNS records to publish. Don't hand-craft these — follow the in-app flow:

➡️ [How to Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)

### 2.2 — Roll DMARC enforcement gradually

Don't publish `p=reject` on day one — if your SPF or DKIM alignment is even slightly off, you'll bounce *your own* legitimate mail. Start at `p=none` to collect reports and see exactly what's sending under your domain, then tighten to `p=quarantine`, then `p=reject` once the reports are clean. There's no flag-day deadline — the point is steady, verified progression.

➡️ [DMARC Enforcement Migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)

### 2.3 — Send at a sane rate, and don't IP-hop

Mailbox providers throttle suspicious patterns — sudden volume spikes or a brand-new IP blasting at full tilt — before they ever hard-block. Two habits keep you clear:

- **Pick a sending IP/domain and stick with it.** Constantly rotating IPs or sender names is a classic spammer signature. On a brand-new IP, warm it up gradually before full volume — see [Domain & IP Warmup](https://acellesend.com/kb/articles/domain-warmup).
- **Set rate limits in AcelleMail** to match what your sending server (your own SMTP, Amazon SES, SendGrid, Mailgun) actually allows. Configure these under **Sending servers** rather than letting one campaign fire 100k emails in a burst.

➡️ [Sending Throttling Strategies](https://acellesend.com/kb/articles/sending-throttling-strategies)

### 2.4 — Watch for reputation drops and blacklisting

Even a clean setup can get listed if a few hundred subscribers mark you as spam in a short window. The fix is mostly upstream — list hygiene and authentication — but you want to *know* when it happens, because throttling shows up before a hard block.

➡️ [Sender Reputation Monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring) · if you're already in trouble, the [Deliverability Incident Runbook](https://acellesend.com/kb/articles/deliverability-incident-runbook).

## Priority 3 — Lock down admin and API access

Your admin panel can export every subscriber and send on your behalf. The API can do the same programmatically. These are the two doors worth bolting.

### 3.1 — Strong admin password + two-factor

Multi-factor auth is the single highest-leverage control here — it stops credential-stuffing and brute-force outright, even if a password leaks. AcelleMail ships TOTP-based two-factor; turn it on for every admin account:

Go to **Admin → Account Settings → Two-Factor Authentication → enable**, scan the QR with any authenticator app (Google Authenticator, 1Password, Authy), and **save the recovery codes to your password manager immediately** — there's no override if you lose both the device and the codes.

### 3.2 — Restrict who can reach `/admin`

`/admin/login` is the highest-value brute-force target on the box. Beyond a strong password, two web-server layers help:

- **Rate-limit the login** so password sprayers get throttled — a few attempts per minute per IP is plenty.
- **If your team has fixed IPs, allowlist them.** Restricting `/admin` to known IP ranges makes stolen credentials nearly useless to an outside attacker. Skip this if your admins roam (cafés, mobile) — it'll lock *you* out.

These live in your web-server config (the nginx section of your install guide), not in AcelleMail's settings.

### 3.3 — Treat the API token like a password, and rotate it

Every AcelleMail account has an `api_token`. Anyone holding it can read your lists and send campaigns, so:

- **Store it in a secrets manager or `.env`, never in version control.**
- **Prefer the `Authorization: Bearer` header over the `?api_token=` query parameter** — query strings end up in server logs and browser history.
- **Rotate it** if it might have leaked (committed to a repo, shared in a ticket, an ex-employee had it). Rotating is just resetting the token in the account's API settings; the old one stops working immediately.

➡️ [REST API Authentication & Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints)

## Priority 4 — Keep AcelleMail and PHP patched

A perfectly hardened box still gets owned by one unpatched known vulnerability. This is ongoing, not one-time.

- **AcelleMail updates** ship security fixes alongside features. Apply them when they land — don't sit three versions behind.
- **PHP** is the runtime everything sits on. Stay on a [supported PHP version](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404#step-2--php-83-the-engine) (8.3+ for current AcelleMail) and let your distro's unattended-upgrades apply security patches automatically.
- **Re-audit after any disclosed vulnerability** in PHP, nginx, MySQL/MariaDB, or AcelleMail — and on a quarterly cadence otherwise, the same rhythm you use to re-check your DNS records.

## Priority 5 — Server-OS basics (the short version)

These are general server-hardening steps — not AcelleMail-specific, so we won't reproduce a whole sysadmin course. Two matter most, done once:

**Firewall down to only what AcelleMail needs** — SSH, HTTP, and HTTPS inbound, nothing else. MySQL, Redis, and php-fpm should never be reachable from the internet. On Ubuntu that's a handful of `ufw allow` rules (OpenSSH + "Nginx Full"); on DigitalOcean or AWS, use the provider's Cloud Firewall / Security Group as the primary edge filter and treat the host firewall as defense in depth.

**SSH with keys, not passwords.** Copy your key first, confirm it works in a *second* terminal, then disable password login. Adding `fail2ban` to auto-ban repeat offenders is a nice extra.

That's the 80/20. The full Linux-hardening treatment (non-standard SSH port, disabling root login, kernel sysctl tuning) is a general OS topic — your platform's install guide covers the firewall and SSH basics in context:

➡️ [Install AcelleMail on Ubuntu 24.04](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) (and the Debian / [Rocky](https://acellesend.com/kb/articles/install-acellemail-on-rocky-linux-9) / DigitalOcean / AWS variants)

## Quick verification

Two minutes to confirm the high-value items took:

- **In `.env`:** `APP_ENV=production`, `APP_DEBUG=false`, `FORCE_HTTPS=true`.
- **Database** is bound to `127.0.0.1` (or locked to your app IP in the provider security group).
- **Firewall** is up with only SSH / HTTP / HTTPS open.
- **A backup file actually exists** — and re-check tomorrow that the schedule fired.

Then, in the app: send a test campaign to a [mail-tester.com](https://www.mail-tester.com) address — it scores your SPF/DKIM/DMARC alignment in one click, the fastest way to confirm Priority 2 landed.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Locked out of SSH after disabling passwords | Disabled password auth before the key worked | Use your provider's web console; re-enable password auth, copy the key, confirm, then disable again |
| Stack trace with `.env` values in the browser | `APP_DEBUG=true` still set | Set `APP_DEBUG=false`, clear the config cache |
| Your own campaigns land in spam | SPF/DKIM/DMARC not aligned, or `p=reject` set before alignment was clean | Run a [mail-tester](https://www.mail-tester.com); back DMARC down to `p=none`, fix alignment, re-tighten |
| Admin login locks out your whole office | Rate-limit or IP-allowlist too tight for a shared office IP | Raise the limit, or scope the allowlist to the office's real egress IP |
| Backups silently stopped, disk fills | The scheduled job didn't run | Confirm the backup schedule is active and its retention cleanup is firing |
| API token shows up in access logs | Using the `?api_token=` query parameter | Switch callers to the `Authorization: Bearer` header |

## What to do after

Once Priorities 1–3 are in place you can safely import your first list and publish the admin URL. Keep Priority 4 (patching) on a calendar reminder, and revisit this checklist whenever you add an admin user, change sending servers, or move the install to a new server.

## FAQ

**Do I need all of this before going live?** Priorities 1–3 yes, before you import real subscribers or publish the admin URL. Priority 4 is ongoing; Priority 5 is the OS floor — do the firewall + SSH-keys steps, defer the rest if you must.

**Is Cloudflare / a WAF a substitute for this?** No — it's an extra layer, not a replacement. Cloudflare in front gives you free DDoS protection and hides your origin IP, but it doesn't fix an exposed database, missing backups, or unaligned DMARC. Defense in depth means *both*.

**What about GDPR / CAN-SPAM?** Hardening protects the data; compliance is a separate set of content and process obligations on top. See the [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing).

**Running AcelleMail in Docker?** Same priorities, applied at the container/host boundary — the [Docker Deployment Guide](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) covers the Docker-specific patterns.

## Related articles

- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — the canonical install; run this checklist right after
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — the full backup strategy behind Priority 1.4
- [How to Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) — the heart of Priority 2
- [REST API Authentication & Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — token handling for Priority 3.3
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — for EU-resident sender programs

---

# Sender Reputation Monitoring — What to Watch in AcelleMail

> Three numbers tell you most of what receiving servers think of you: bounce rate, complaint (spam) rate, and how engaged your list is. AcelleMail surfaces them on the campaign Overview tab and its Bounce log + Feedback log. This guide shows where each one lives and when to act.

- Canonical URL: https://acellesend.com/kb/articles/sender-reputation-monitoring
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Sender reputation is the score receiving servers (Gmail, Outlook, Yahoo, and the rest) quietly assign to your sending identity. Nobody publishes it. You read it indirectly, from the few signals you *can* see — and AcelleMail puts the important ones right on the campaign report. This article shows you exactly where each signal lives and what to do when it moves the wrong way.

## The three numbers that matter

You don't need a dashboard full of metrics. Three signals carry most of the story:

| Signal | Where it lives in AcelleMail | Rule of thumb |
|---|---|---|
| **Bounce rate** | Campaign Overview → **Bounced** rate row → **Bounce log** | Comfortable under ~2%. Past ~5% on a single send, stop and clean the list. |
| **Complaint (spam) rate** | Campaign Overview → **Spam reports** rate row → **Feedback log** | Keep it as close to zero as you can. A fraction of a percent is already a warning. |
| **Engagement** | Campaign Overview → opens + clicks | High opens this month tend to predict a healthy reputation next month; flat opens predict the opposite. |

These are rules of thumb, not hard cutoffs — every ISP weighs them differently and none of them publish the exact line. Treat a number trending the wrong way as a prompt to look closer, not as a single pass/fail gate.

## Reading the campaign Overview

Open any sent campaign and you land on the **Overview** tab. The first thing you see is an insight banner: AcelleMail reads your open rate and gives you a one-line verdict — *Strong engagement*, *Solid performance*, or *Room to grow* — and if your bounce rate is elevated it appends a *"Heads up: your bounce rate is elevated — consider cleaning your list"* note right there.

![Campaign Overview with the engagement insight banner](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/01-campaign-insights.png)

Below the headline numbers is a stack of clickable rate rows — **Opened**, **Clicked**, **Unsubscribed**, **Bounced**, and **Spam reports** — each showing the percentage and linking straight into the matching log. That row stack is your reputation cockpit. You don't go hunting; the numbers come to you, and one click drills into the detail.

### Where the bounce signals live

Click the **Bounced** rate row, or open the **Sending logs** dropdown in the campaign nav and pick **Bounce log**. Every failed delivery is listed with the recipient, a bounce-type chip, and the raw reason text the receiving server returned:

![Campaign bounce log — bounce type and the raw DSN reason](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/02-campaign-bounce-log.png)

- **Hard** bounces are permanent — the address doesn't exist. A pile of these on a fresh import means the list is dirty.
- **Soft** bounces are temporary — mailbox full, server busy. A few are normal; a sudden wall of them on one ISP is worth watching.

The reason cell shows the receiving server's verbatim response; click it to read the full message. For decoding the machine codes (the `5.x.x` / `4.x.x` DSN classifications), see [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages).

### The complaint signal — Feedback log

From the same **Sending logs** dropdown, open **Feedback log** (or click the **Spam reports** rate row on Overview). Every complaint an ISP reports back — recipient, type, and reason — lands here:

![Campaign feedback log](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/03-campaign-feedback-log.png)

Complaints are the most expensive signal you have. Receiving servers treat them as direct evidence that people don't want your mail, and the threshold before they react is much lower than for bounces. Read the log row-by-row when complaints climb — the *pattern* tells you the cause:

- **All from one list or segment?** Bad source. Pause sending to it and audit how those people signed up.
- **All on the first send to a freshly imported list?** Permission was probably unclear at signup. Audit before you send again.
- **Spread evenly across the whole list?** It's the content — a subject line that over-promised, or a body that didn't match what people expected.

### Check the sending side too

A reputation problem isn't always your list — sometimes your sending setup itself broke. Two quick checks:

**Sending domain authentication.** Open **Sending → Sending Domains** and click into your domain. Each DNS record (SPF, DKIM, DMARC) shows a green **Verified** chip when it's passing, and the header summarises it as an *N/N DNS verified* badge:

![Sending domain DNS records](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

If a record that used to be verified flips to unverified, a DNS change broke your alignment — receiving servers will start downgrading or rejecting your mail until you fix it. Re-check the record and re-verify.

**A live test send.** When you suspect a problem and want a fast read, open **Sending → Sending Servers**, click into the server, and use **Send test email**. AcelleMail drops a real message through that server to whatever inbox you type in:

![Send test email popup](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/03-sending-server-test.png)

Send one to your own Gmail, Outlook, and Yahoo accounts and open each on the receiving side:

- **All land in the inbox** → transmission and authentication are fine; your original issue is content or list quality.
- **Gmail spam, Outlook inbox** → a Gmail-specific reputation hit. Time to check Google Postmaster Tools (see the collapsible below).
- **All spam everywhere** → a systemic authentication break. Recheck SPF / DKIM / DMARC alignment first.

## Acting on each signal

| What you see | First action |
|---|---|
| Bounce rate jumps to several percent on one campaign | List hygiene. Run [email verification](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) on the list and prune obviously-bad and role addresses before the next send. |
| Spam reports climbing in the Feedback log | Pause the campaign. Audit consent for that list, and send a re-engagement message before you mail it at full volume again. |
| Opens sliding over several consecutive sends | You're likely mailing too often. Cut your cadence for a couple of weeks — engagement usually recovers, and reputation follows it. |
| Test send: Gmail spam but Outlook inbox | A Gmail-specific hit. Check Google Postmaster Tools — that's where Gmail tells you what it sees. |
| A DNS record that was Verified is now unverified | A DNS change broke alignment. Fix the record and re-verify the domain. |

## When to add a second sending server

Once you cross any of these, running on two servers minimum is the sane default:

- You're sending more than tens of thousands of messages a day
- You're mixing marketing and transactional traffic on one identity
- You're in a regulated industry where an outage is genuinely costly
- A single vendor is getting expensive enough that a second, cheaper one pays for itself

See [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers) for the setup walkthrough.

<details>
<summary><strong>Going deeper: the per-ISP picture with external postmaster tools</strong></summary>

AcelleMail's campaign report tells you the aggregate — your bounce, complaint, and engagement numbers across everyone you mailed. What it can't show you is what *one specific ISP* thinks of you, because Gmail and Microsoft only expose that to you directly, through their own free tools. If you send any real volume, set these up — they're where you catch a problem before it shows up as missing emails.

**Google Postmaster Tools** (for gmail.com / googlemail.com recipients):

1. Add your authenticated sending domain at https://postmaster.google.com
2. Verify ownership with the TXT record Google gives you
3. Within about a day you get dashboards for spam rate (real Gmail complaints), IP and domain reputation, authentication pass rates, TLS adoption, and delivery errors

The number to watch daily is the **spam rate**. When Gmail's own complaint figure starts climbing, Gmail begins throttling you quietly — long before you'd notice from your own send. Pause and audit the list the moment it moves.

See [Gmail postmaster tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough) for the full setup.

**Microsoft SNDS** (for outlook.com / hotmail.com / live.com recipients):

1. Sign up at https://sendersupport.olc.protection.outlook.com/snds/
2. Register your sending IPs, one entry per IP
3. You get a per-IP reputation colour (Red / Yellow / Green), filter results, and a complaint rate

See [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough) for the full setup.

**Yahoo / AOL** historically had postmaster tools too, but most have been retired. Your best signal for those domains is the bounce and complaint data already sitting in your AcelleMail Bounce log and Feedback log.

**A simple recovery rhythm after a hit.** When a reputation dip happens, the pattern that works is to shrink your sends back to your most engaged people and rebuild outward:

- Pause bulk sending. Mail only your most recently engaged recipients — the ones most likely to open.
- If those sends stay clean for a week or so, widen the audience a step.
- Keep widening, checking Postmaster Tools / SNDS as you go, until you're back to your normal segments.

Mild dips recover in a week or two. A serious complaint problem can take much longer — and occasionally the honest answer is a fresh sending domain. The cleaner you keep the three numbers at the top of this article, the less often you'll be here at all.

</details>

## Related articles

- [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages)
- [Gmail postmaster tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough)
- [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)
- [Why are my emails going to the spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder)
- [Deliverability incident runbook](https://acellesend.com/kb/articles/deliverability-incident-runbook)

---

# Sending Throttling Strategies — Pace Your Volume in AcelleMail

> Blast 500k emails at once and you'll throttle yourself: receiving servers tar-pit you, your IP reputation drops, complaint rate spikes. AcelleMail's per-server sending limit lets you pace. This guide walks the UI + 4 throttling patterns by scenario.

- Canonical URL: https://acellesend.com/kb/articles/sending-throttling-strategies
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-07-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why throttle

Receiving servers throttle senders that spike. The math:

- **Sustained 5,000/hour for 30 days** → normal traffic; clean reputation
- **50,000 in 10 minutes** → spike alarm; receivers throttle or temp-block

A throttle = AcelleMail enforces a max-rate on its outbound queue. Each receiving server sees you arriving at a steady cadence rather than a flash.

## Configure throttle per-server
### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account with its status chip, sending limit, and last activity:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page surfaces Connection settings (host / credentials), Configuration (server name, default from, sending limit, bounce + FBL handler), and the **Test connection** / **Send test email** buttons in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

### Where the throttle lives

Same sending-server detail → **Configuration** section. The **Sending limit** dropdown caps how many emails AcelleMail will hand to this server per unit of time (per minute / hour / day):

![Sending limit dropdown — 5,000 emails per 1 hour](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

AcelleMail enforces the limit globally — when the rolling-window counter hits the cap, the queue holds back until the window slides. No throttling code in your campaigns; configure once per server.

The dropdown options typically include:

```
100 emails per 1 minute
500 emails per 1 minute
1,000 emails per 1 minute
500 emails per 1 hour
5,000 emails per 1 hour
50,000 emails per 1 hour
5,000 emails per 1 day
50,000 emails per 1 day
Unlimited
```

Pick based on volume target + IP maturity.

## 4 throttling patterns by scenario

### Pattern A: New IP warmup (Day 1)

```
Sending limit: 100 emails per 1 hour
Daily ramp:    +50% each day
Target:        Day 30 = full volume
```

Low rate, steady through the day. Pairs with engaged-segment-first audience selection (see [IP warmup schedule](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)).

### Pattern B: Mature IP at scale (steady-state)

```
Sending limit: 50,000 emails per 1 hour OR Unlimited
Burst:         Spread campaign launches across day (multiple smaller campaigns instead of one mega-blast)
Quota:         Daily cap matches vendor's published quota
```

At >100k/day, you're past warmup. Throttle still acts as a circuit-breaker — if a runaway campaign accidentally enqueues 1M sends, the per-hour cap prevents the burst from happening all at once.

### Pattern C: Multi-vendor split

```
Server A (SES):       30,000 emails per 1 hour (long-running)
Server B (Postmark):  10,000 emails per 1 hour (warming)
Server C (Mailgun):    5,000 emails per 1 hour (warming)
```

AcelleMail's dispatcher rotates across all three, but per-server caps prevent any single one from being overloaded. Combined: 45k/hour aggregate; per-server limits matched to each server's reputation maturity.

### Pattern D: Burst-friendly transactional + steady marketing

```
Server T (transactional):  Unlimited (login emails, password resets must send immediately)
Server M (marketing):       5,000 emails per 1 hour (steady)
```

Two separate Plans, one server each. Transactional bypasses throttle (low volume, high priority). Marketing throttled to keep reputation clean.

## When to step up vs step down

| Signal | Action |
|---|---|
| Day-after bounce rate <2%, sustained 7 days | Step up sending-limit by ~25% |
| Bounce rate creeping 3-4% | HOLD current cap; investigate before stepping up |
| Bounce rate >5% | Step DOWN cap by 50%; investigate root cause |
| Complaint rate <0.05% sustained | Cap acceptable; can step up |
| Complaint rate >0.15% | Step down cap; audit content + consent |
| Daily cap hit by noon every day | Cap too low for traffic; raise OR add second server |
| Daily cap never hit, sends complete at midnight | Cap too high relative to actual traffic; either fine OR reduce for safety |

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Campaigns queue but don't send during the day | Sending limit hit; rest is rate-paced into next window | Expected behavior; raise cap if too restrictive |
| Burst of 5,000 takes 30 minutes to dispatch | Throttle is working as designed | Lower per-minute cap if you want even slower; raise if you want faster |
| Sub-second between sends in tracking log | No throttle configured (Unlimited) | If reputation is concern, set a per-minute or per-hour cap |
| Daily cap shows 5,000 but campaign is queueing more | Multiple campaigns sharing the server pool; aggregate exceeded daily cap | Stagger campaign launches; OR raise daily cap |
| Single subscriber gets duplicate emails | NOT a throttle issue; usually retry-loop or queue bug | Investigate separately — see [Why is my campaign stuck](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status) |

## Throttle interaction with batch sending

AcelleMail's send pipeline batches messages. The throttle applies at the per-message level (queue-handler picks a server, rate-limits its own picks).

For example: throttle = 5,000/hour. Campaign with 10,000 recipients:

- 0:00 — first 5,000 dispatched
- 1:00 — second 5,000 dispatched
- Total time: ~1 hour

For 100,000 recipients at 5,000/hour: ~20 hours. Either raise the cap or add a second server.

## Multi-server throttle aggregation

When you have 3 servers in a pool, each with its own cap:

```
Server A: 5,000/hour
Server B: 5,000/hour
Server C: 5,000/hour
```

Total throughput = 15,000/hour. Round-robin distribution means each server hits its own cap, then queue fills the next batch.

If one server caps out, the dispatcher routes to the others until they also cap; then everyone waits for the next window. AcelleMail handles this automatically — no manual intervention needed.

<details>
<summary><strong>Advanced: per-domain throttling, custom rate limits via API, and time-of-day patterns</strong></summary>

The server-level sending-limit applies to all outbound traffic. For per-recipient-domain throttling (e.g. "no more than 1000/hour to gmail.com specifically"), customize via:

**Per-domain limits in sending-server config:**

```php
// In sending-server admin advanced config (some installs expose this)
'rate_limits_by_domain' => [
    'gmail.com'  => 1000, // per hour
    'outlook.com' => 500,
    'yahoo.com'  => 300,
    'icloud.com' => 200,
    'default'    => 5000,
],
```

When the dispatcher needs to send to a domain that's hit its per-domain cap, it queues those addresses until the window slides.

**Custom rate-limit via API:**

For programmatic adjustment (e.g. when CloudWatch detects a reputation issue):

```bash
curl -X PATCH "https://acellemail.com/api/v1/admin/sending-servers/{server_uid}" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
    "sending_limit": 1000,
    "sending_limit_period": "minute"
  }'
```

Effects immediately on the next batch.

**Time-of-day pattern** — sending more during recipient business hours, less at night:

```bash
# Cron: 09:00 — bump to weekday daytime rate
curl -X PATCH ... -d '{"sending_limit": 20000, "sending_limit_period": "hour"}'

# Cron: 22:00 — cut to overnight rate
curl -X PATCH ... -d '{"sending_limit": 2000, "sending_limit_period": "hour"}'
```

For US-based audience: send during US business hours; cut volume during EU/APAC night-time. Result: highest-engagement windows get full volume.

**Burst-on-launch + steady-after pattern** — for campaign launches, the typical curve:

```
First 5 minutes:  3000/min (push the first thousand recipients immediately)
Next 15 minutes:  1500/min
After that:       100/min (steady drip)
```

Get the engaged subscribers first (they open within minutes), then trickle the rest. Improves attribution + click-through optics in analytics.

**Per-segment throttling** — combine throttle with segment scope:

```
Marketing campaign: 5,000/hour, sent to engaged-30d segment only
Re-engagement campaign: 1,000/hour, sent to inactive-90d segment only
```

Different urgencies, different paces, different audience risk levels. Combined: clean reputation, predictable performance.

**Vendor-aware throttle alignment:**

| Vendor | Default cap | Recommended throttle |
|---|---|---|
| Amazon SES (production, default quota) | 50,000/day, 14/sec | Set to 12/sec to stay under |
| Postmark | 100,000/mo on free; varies on paid | Match to plan limits |
| SendGrid Free | 100/day | Set 50/day to stay safe |
| SendGrid Essentials | 50,000/mo | Match plan |
| Mailgun Flex | 5,000 free / scale paid | Match account quota |

Why under-shoot the vendor cap? Vendor enforces hard limits; AcelleMail's throttle is the soft layer. If vendor's hard limit hits, you get throttle errors from the vendor (delays + retries). If your AcelleMail-level cap hits first, you get clean queue-paced delivery.

</details>

## Related articles

- [Email throttling and rate limits explained](https://acellesend.com/kb/articles/email-throttling-and-rate-limits-explained)
- [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers)
- [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)
- [Multi-server rotation pattern](https://acellesend.com/kb/articles/multi-server-rotation-pattern)

---

# Building Your Email Marketing SaaS

> The technology side of an AcelleMail SaaS is solved. The hard part is the business: who you sell to, how you price, how you reach them, and how you keep them. This guide is the GTM + pricing playbook — opinionated, market-tested patterns for launching an email-marketing SaaS on the AcelleMail stack.

- Canonical URL: https://acellesend.com/kb/articles/building-your-email-marketing-saas
- Author: Martin Keller, SaaS & Business Editor
- Category: SaaS & Multi-tenant — White-label, subscription plans, billing, tenant management, branding
- Content type: guide · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-07-03 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The technology side of an AcelleMail SaaS is solved — the [setup guide](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) walks you through Stripe, plans, tenant isolation. **The hard part is the business**: who you sell to, how you price, how you reach them, and how you keep them.

This guide is the GTM + pricing playbook for launching an email-marketing SaaS on the AcelleMail stack. Opinionated patterns from operators who've shipped one.

## Step 1 — Pick a niche

"Email marketing for everyone" is the wrong positioning — you'll compete head-on with Mailchimp / Klaviyo / ActiveCampaign, who have 10–100× your marketing budget. **Niche down** to a defensible angle:

| Niche axis | Example |
|---|---|
| **Vertical** | Email for SaaS founders. Email for real-estate agents. Email for restaurants. Email for podcasters. |
| **Geography** | Email marketing for Vietnamese SMBs. Email for Mexican e-commerce. Email for Indian B2B. |
| **Language** | Vietnamese-first email marketing. Indonesian-first. Hindi-first. |
| **Use case** | Cold outreach only. Newsletter only. Transactional only. Re-engagement only. |
| **Compliance** | GDPR-first (EU-data-residency, no US providers). HIPAA-compliant email for healthcare. |
| **Channel mix** | Email + WhatsApp combined. Email + Zalo (Vietnam). Email + LINE (Japan/Taiwan). |
| **Price-point disruption** | $5/mo flat-rate vs Mailchimp's tiered pricing for small lists. |

The best niches combine **two or more axes** — e.g. "email marketing for Vietnamese e-commerce stores on Shopee", or "GDPR-compliant email for German B2B agencies". The intersection is what makes you not-a-commodity.

**Test the niche before committing.** Talk to 20 prospects in the niche. If 12 of them say "yes, I'd pay $X/month for that", you have a niche. If they say "interesting" without committing, you don't.

## Step 2 — Decide your pricing model

Email-marketing SaaS pricing comes in three main models:

| Model | Used by | When to pick |
|---|---|---|
| **Per-contact tier** | Mailchimp, ActiveCampaign | Easy for customers to understand; you collect more as their list grows |
| **Per-send tier** | SendGrid, Mailgun, transactional-focused | Customer pays for usage; volatile revenue but aligned with their value |
| **Flat-rate / unlimited** | ConvertKit (creators), some agency tools | Predictable revenue + simple pricing; risky if a customer floods you |
| **Hybrid (base + overage)** | Klaviyo, EmailOctopus | Best of both worlds; harder to communicate |

For an AcelleMail SaaS, **start with per-contact tier** — it's the model AcelleMail's plan structure (max_subscribers + max_emails_per_month options) maps to naturally. Once you have product-market fit, consider hybrid models for power users.

**Three pricing principles that compound:**

1. **Anchor with annual.** Show monthly + annual side-by-side. Annual at ~17% discount (10 months free) converts >30% of paying customers and reduces churn.
2. **Charge for the second tier.** The cheapest plan is a magnet, not your revenue. Most revenue comes from the *second* tier (the one people upgrade to from the magnet). Price that tier to support your CAC.
3. **Don't compete on price.** If you're the cheapest, you're saying your product is the worst. Be 30–50% cheaper than Mailchimp for the equivalent tier (because you can — your infra cost is lower) but no more.

## Step 3 — The three-tier ladder

The standard SaaS pricing ladder, with notes on what makes each tier work for email marketing:

| Tier | Target buyer | Price | Hook | Notes |
|---|---|---|---|---|
| **Starter** | Solo creator, side project | $19–$29/mo | Self-serve credit card; free 14-day trial | Goal: capture leads. Many will churn; that's fine. |
| **Pro (the anchor)** | Small business, in-house marketer | $79–$129/mo | Templates, automations, custom domain | Most revenue lives here. Spend the most product effort. |
| **Agency** | Marketing agency managing multiple clients | $199–$399/mo | Sub-accounts, multi-client dashboard, white-label option | Sales-assisted close; 30-day extended trial; quarterly QBR |

If you can support an **Enterprise tier** ($999+/mo, custom contracts, SLA, dedicated IP, on-prem option) — do it, but only after you've nailed the Pro tier. Enterprise sales is a different motion.

## Step 4 — Customer acquisition math

You need to know two numbers before spending a dollar on marketing:

- **CAC** (customer acquisition cost) — what does it cost you to acquire one paying customer?
- **LTV** (lifetime value) — how much revenue does that customer generate before they churn?

The healthy ratio is **LTV : CAC = 3 : 1 or better**. Below 1:1 you're burning money on every customer. Between 1:1 and 3:1 you're underpriced or undercharging — either tighten your CAC or raise your prices.

**Worked example** (Pro-tier focus, $79/mo):

- Average customer stays 18 months → LTV = $79 × 18 = $1,422
- Acceptable CAC = $1,422 / 3 = **$474 per customer**
- Channels that pay back at <$474 CAC: SEO (your KB!), partner referrals, content marketing, niche-community presence
- Channels that probably don't: paid Google Ads for "email marketing software" (you're outbid 10× by ConvertKit / Klaviyo / Mailchimp), generic Facebook ads

For a niche play, **owned media wins**. Write a 30-article KB targeting your niche's long-tail keywords; publish 2 quality YouTube walkthroughs per month; show up in 3 niche-community Slacks/Discords/forums. This is slow but cheap and compounds.

## Step 5 — Channels that work for email-marketing SaaS

Ranked by typical CAC / payback time for niche email SaaS:

1. **SEO / content marketing.** Multi-month payoff. Cheapest CAC at scale. Target long-tail keywords your competitors ignore. The very KB you're reading is a working example — pick 30 high-intent keywords in your niche, write a 1,500–2,500-word article per keyword, ship one per week.
2. **Partner / affiliate.** Agencies, web developers, marketing consultants who'd recommend you to clients. 20–30% recurring revenue share is standard. Best when you have a clear vertical positioning.
3. **Niche community presence.** Be the go-to expert in 3–5 forums / Slacks / Discord servers for your niche. Answer questions; don't pitch. Pitch only when asked.
4. **Free tools.** A free email-deliverability checker / DMARC analyzer / send-time recommender on your marketing site. Captures top-of-funnel; converts a small % to paid.
5. **Cold outbound.** Effective for Agency tier ($199+). Personalised, niche-targeted; 5–10 sends/day max. Don't blast — your own deliverability matters.
6. **Paid ads.** Last resort. Only competitive against generic "email marketing" if you have a sharp niche angle in the ad copy.

## Step 6 — The founding-customer playbook

Your first 10 paying customers are not random — they're earned. The pattern:

1. **List 50 ideal-customer-profile people in your niche.** Real names, real companies, real LinkedIn URLs.
2. **Talk to 20 of them.** 30-minute calls, no pitch. Ask: "What email tool are you using? What do you hate about it? What would 10× better look like?"
3. **Build / tune AcelleMail to solve the top 3 pains.** This might be UI tweaks ([white-label customization](https://acellesend.com/kb/articles/white-label-customization-guide)), automation templates pre-loaded for the niche, niche-specific KB content, or integrations (Shopify, WooCommerce, the local e-commerce platform).
4. **Re-invite the 20.** "I built the thing we talked about. Want to try it free for 90 days in exchange for honest feedback?"
5. **Convert 5 of those to paying customers** at month 3, with a 50% lifetime discount as a thank-you. They're your case studies, testimonials, and referral source.
6. **Use their pain stories** as your marketing copy. Real customer language beats anything you'd write.

This is slow — 3–6 months to first $1k MRR. It's also the only reliable path. Skipping it for "let's just run Facebook ads" is the #1 reason SaaS startups fail.

## Step 7 — Operations + support reality check

A growing SaaS is also a growing support burden. At 50 paying customers:

- ~5 support tickets/week (~3 hours/week)
- 1 sales call/week for Pro/Agency tier inquiries
- 4 hours/week of marketing content
- 2 hours/week of operations (server monitoring, billing reconciliation)
- = **~14 hours/week of operator time** before code.

If you can't dedicate that, hire a part-time customer-success person at ~50 customers, and a full-timer at ~150. Margin on AcelleMail SaaS is high enough to afford this — but only if you charge enough.

## Realistic 12-month trajectory

| Month | Customers | MRR | Hours/week | Notes |
|---|---|---|---|---|
| 1–3 | 0 | $0 | 20–30 | Customer discovery, MVP polish, content seeding |
| 4 | 5 | $400 | 25 | Founding customers, 50% lifetime discount |
| 6 | 15 | $1,200 | 30 | First public launch; some paid customers |
| 9 | 40 | $3,500 | 35 | SEO starting to compound; first referrals |
| 12 | 80 | $7,500 | 30 | Marketing on autopilot; first hire conversation |

At $7,500 MRR ($90k ARR) you're at the threshold where you can quit the day job (depending on your local cost of living). Hitting this in 12 months is realistic in a clear niche; 18–24 months is more common.

## Common failure modes

| Mistake | Why it fails | Fix |
|---|---|---|
| **No niche / "email marketing for everyone"** | Outspent 100:1 by Mailchimp on every keyword | Pick a vertical + geo + language combo. Defend that intersection. |
| **Undercharging** ($5/mo plans) | Can't afford CAC; can't afford support; race to the bottom | Anchor pricing at $79 Pro tier; never compete on price alone |
| **No customer discovery before building** | Building the wrong features for the wrong people | 20 calls before line 1 of code (or line 1 of marketing copy) |
| **Paying for ads before SEO works** | Burning runway on $5–$10 CPCs for "email marketing software" | SEO + content first; ads only after CAC math works |
| **Letting spammers in via free trial** | Sending IP gets blocklisted; legitimate customers churn | Credit card on free trial; manual review of suspicious signups |
| **Ignoring annual plans** | Volatile MRR, high churn, no cash flow buffer | Push annual at 17% discount from day 1; >30% conversion is typical |
| **Trying to compete with Mailchimp's feature set** | Endless feature treadmill; never differentiated | Differentiate on niche-specific workflow, not feature parity |

## FAQ

**Should I require credit card upfront, or offer a free tier?** Free tier attracts spammers. Free trial with card-on-file converts better long-term. Recommended pattern: 14-day free trial, card required, downgrade to a generous-but-limited free tier after trial ends (catch-and-release model). The generous free tier is your marketing channel; the paid tier is your revenue.

**What about a "lifetime deal" launch on AppSumo / etc.?** Lifetime deals trade short-term cash for long-term margin — typical 1,000 LTD customers at $79 lifetime = $79k cash but ~$1.5M of foregone MRR over 3 years. Use only if you need the cash desperately AND your hosting cost is low enough to support them. Mostly a bad trade.

**Should I build a marketplace of templates / integrations?** Not initially. Maintaining a marketplace is expensive (review, support, payouts). After 200+ customers, when natural template requests outpace what you can produce, consider it. Until then, ship 20 great pre-loaded templates yourself.

**How do I price internationally?** PPP-adjusted pricing (purchasing-power parity — cheaper in lower-GDP regions) increases LTV in those regions but creates a small arbitrage risk (a US customer can sign up via VPN). Worth it for big regional markets (India, Brazil, Vietnam, Indonesia); skip for small ones. AcelleMail supports per-plan currencies so this is doable.

**Should I open-source my customizations?** Selective open-sourcing of UI plugins / templates is great marketing — your customers + competitors learn from you, you build reputation. Keep core business logic (your pricing, your customer-acquisition tools, your differentiated automations) closed. The Mailchimp/HubSpot SaaS lock-in story is brand + integrations + workflow, not source code.

**How do I exit / sell the business?** Email-marketing SaaS at $20k–$100k MRR typically transacts at 3–5× ARR (≈$700k–$5M valuation). Buyers care most about: (1) low operator hours / week, (2) low churn (< 3% monthly), (3) diversified customer base (no single customer > 5% of MRR), (4) clean books. Build for these from day 1.

## Related articles

- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — the technical setup
- [White-Label Customization Guide](https://acellesend.com/kb/articles/white-label-customization-guide) — make it look like *your* brand
- [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) — server tiers for SaaS scale (Medium tier minimum)
- [Building a Welcome Email Series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) — trial-conversion automation
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — required compliance reading
- [CAN-SPAM Compliance Checklist](https://acellesend.com/kb/articles/can-spam-compliance-checklist) — US compliance
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — sending stack for SaaS
- [IP Warmup Schedule for New Sending Servers](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) — required when launching shared IPs

---

# Extending AcelleMail Source Code

> When the REST API + webhooks aren't enough, AcelleMail's open Laravel codebase lets you extend it directly — add custom fields, integrate a custom sending provider, listen to internal events, override Blade views. This guide walks the upgrade-safe extension patterns, the project structure as it actually is (not what older docs say), and the discipline that keeps your customisations alive through Acelle upgrades.

- Canonical URL: https://acellesend.com/kb/articles/extending-acellemail-source-code
- Author: Tom Becker, Developer Editor
- Category: Developer Guide — REST API, webhooks, customization, extending source code, plugins
- Content type: guide · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-06-28 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

When the [REST API](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) and [webhooks](https://acellesend.com/kb/articles/webhook-events-reference) aren't enough, AcelleMail's open Laravel codebase lets you **extend it directly** — add custom fields, integrate a custom sending provider, listen to internal events, override Blade views.

This guide walks the **upgrade-safe** extension patterns. The discipline matters: AcelleMail ships patches frequently, and direct edits to core files get wiped on upgrade.

**Prerequisite:** comfortable with Laravel (~mid-level PHP). If you've never touched a Laravel service provider, start with [laravel.com/docs/providers](https://laravel.com/docs/providers).

## Project structure (as it actually is in 2026)

Some older docs reference `app/Models/` (plural) — that's wrong for Acelle. The real layout:

```
/var/www/acellemail/
├── app/
│   ├── Model/                         ← Eloquent models (SINGULAR)
│   │   ├── Customer.php
│   │   ├── Subscriber.php
│   │   ├── Campaign.php
│   │   ├── MailList.php
│   │   ├── Plan.php / Subscription.php
│   │   └── ...
│   ├── Models/                        ← (Sometimes present for newer Laravel-style models)
│   ├── Http/
│   │   ├── Controllers/
│   │   │   ├── Api/                   ← REST API controllers
│   │   │   ├── Admin/                 ← Admin UI controllers
│   │   │   └── Refactor/              ← Newer-architecture controllers
│   │   └── Middleware/
│   ├── Jobs/                          ← Queue jobs (SendEmail, ProcessImport, ...)
│   ├── Services/                      ← Business logic services
│   ├── Providers/                     ← Laravel service providers (where you'll register listeners)
│   ├── Console/
│   │   └── Commands/                  ← Artisan commands
│   ├── SendingServers/                ← Sending-provider drivers (SES, Mailgun, SendGrid, ...)
│   └── Cashier/                       ← SaaS billing layer
├── config/
│   ├── webhook_events.php             ← Webhook event catalog
│   ├── mail_drivers.php / sending_drivers.php
│   └── ...
├── routes/
│   ├── api.php                        ← REST API routes
│   ├── web.php                        ← Web routes (legacy)
│   ├── refactor.php                   ← Newer-architecture web routes
│   └── console.php                    ← Scheduled-command definitions
├── resources/
│   ├── views/                         ← Blade templates
│   └── lang/                          ← Localised strings
└── database/
    ├── migrations/
    └── seeders/
```

**Watch out:** the `app/Http/Controllers/` tree has TWO controller styles — `Admin/` (older) and `Refactor/Admin/` (newer). When extending, **check both** before editing — your route might be handled in either, depending on Acelle version.

## Pattern 1 — Custom service provider (the foundation)

Every extension starts with a custom service provider. This is your **anchor** — all listeners, view overrides, and bindings live here so they survive upgrades.

```bash
cd /var/www/acellemail
sudo -u www-data php artisan make:provider AcmeExtensionServiceProvider
```

Acelle's `composer.json` auto-discovers service providers in `app/Providers/` — your new provider is registered automatically.

In `app/Providers/AcmeExtensionServiceProvider.php`:

```php
<?php

namespace Acelle\Landing\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;

class AcmeExtensionServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Register bindings, custom config — runs early in app lifecycle
    }

    public function boot(): void
    {
        // Register listeners + view paths — runs after all providers registered

        // Example: register a listener for an Acelle event
        Event::listen(\App\Events\CampaignSent::class, function ($event) {
            // Your custom logic
            \Log::info('Custom hook: campaign sent', [
                'campaign_id' => $event->campaign->id,
                'recipients'  => $event->campaign->recipients_count,
            ]);
        });

        // Example: add a view override path (see Pattern 2)
        $this->loadViewsFrom(
            resource_path('views/vendor/overrides'),
            'overrides'
        );
    }
}
```

**Why a separate provider, not editing AppServiceProvider?** AppServiceProvider is part of Acelle core and gets overwritten by upgrades. Your provider in `app/Providers/AcmeExtensionServiceProvider.php` (custom namespace) won't be touched.

## Pattern 2 — Blade view overrides

Already covered in detail in the [white-label customization guide](https://acellesend.com/kb/articles/white-label-customization-guide#step-5--upgrade-safe-blade-overrides). Summary:

```bash
# Copy the view you want to override
mkdir -p resources/views/vendor/overrides
cp resources/views/auth/login.blade.php \
   resources/views/vendor/overrides/auth-login.blade.php
# Edit the copy
```

Then in your service provider, point the affected route at the override:

```php
Route::get('/login', fn () => view('overrides::auth-login'));
```

Document every override in an `OVERRIDES.md` at the project root.

## Pattern 3 — Custom sending-provider driver

If you have your own SMTP gateway / API and want it as an option in **Admin → Sending Servers**, implement the driver interface.

Acelle's sending-server drivers live in `app/SendingServers/Drivers/`. Each driver implements the contract that defines `send($email)`, `verify()` (auth check), `getQuota()` (remaining sends).

```php
<?php
// app/SendingServers/Drivers/AcmeMailerDriver.php

namespace Acelle\Landing\SendingServers\Drivers;

use App\SendingServers\Drivers\AbstractDriver;
use App\SendingServers\Contracts\Drivable;

class AcmeMailerDriver extends AbstractDriver implements Drivable
{
    public function send($email): array
    {
        // Use Guzzle / your SDK to POST to your gateway
        $response = $this->client->post('https://api.acme-mailer.com/send', [
            'json' => [
                'from'    => $email->from,
                'to'      => $email->to,
                'subject' => $email->subject,
                'html'    => $email->html,
            ],
        ]);

        return [
            'status'     => 'sent',
            'message_id' => json_decode($response->getBody(), true)['id'],
        ];
    }

    public function verify(): bool
    {
        // Check credentials by hitting an auth-protected endpoint
        return $this->client->get('https://api.acme-mailer.com/me')->getStatusCode() === 200;
    }

    public function getQuota(): array
    {
        // Return remaining sends if your gateway exposes it
        return ['max_24h' => 50000, 'sent_24h' => 12000];
    }
}
```

Register in your service provider:

```php
public function register(): void
{
    $this->app->extend('sending-server-driver-registry', function ($registry) {
        $registry->register('acme-mailer', AcmeMailerDriver::class);
        return $registry;
    });
}
```

The driver now appears as an option when creating a new sending server in admin. Configuration fields (API key, region, etc.) are declared in a sibling `AcmeMailerConfigSchema.php`.

> Exact registry method names may differ slightly across Acelle versions — check `app/SendingServers/` for the current factory pattern. Look at the SES driver (`SesDriver`) as your reference.

## Pattern 4 — Listening to events

Acelle dispatches Laravel events for major operations. Hook them via `Event::listen()` in your service provider. Some commonly-used events:

| Event | Fires when |
|---|---|
| `App\Events\CampaignSent` | A campaign finishes sending |
| `App\Events\CampaignStarted` | A campaign starts sending |
| `App\Events\SubscriberSubscribed` | A new subscriber confirms |
| `App\Events\SubscriberUnsubscribed` | A subscriber unsubscribes |
| `App\Events\BounceReceived` | A bounce is processed |
| `App\Events\ComplaintReceived` | A spam complaint is processed |

(Exact event class names vary across Acelle versions — `grep -r 'event(new\|broadcast(new\|Event::dispatch' app/` to discover the full list in your install.)

Example: post to Slack on every campaign send:

```php
Event::listen(\App\Events\CampaignSent::class, function ($event) {
    $campaign = $event->campaign;
    \Http::post(config('services.slack.webhook'), [
        'text' => "Campaign sent: *{$campaign->name}* to {$campaign->recipients_count} recipients",
    ]);
});
```

**Async listeners** for heavy work — implement `ShouldQueue`:

```php
class SlackNotifyOnCampaignSent implements \Illuminate\Contracts\Queue\ShouldQueue
{
    public function handle(\App\Events\CampaignSent $event): void
    {
        // Heavy work — runs on the queue, not blocking the send
    }
}

Event::listen(\App\Events\CampaignSent::class, SlackNotifyOnCampaignSent::class);
```

## Pattern 5 — Adding custom fields to models

If you need to add a column (e.g. `external_crm_id` on `customers`):

1. Create a migration:
   ```bash
   sudo -u www-data php artisan make:migration add_external_crm_id_to_customers \
     --table=customers
   ```
2. Add the column in the new migration file (`up()` adds, `down()` drops)
3. Add to the model's `$fillable` — but **don't edit `app/Model/Customer.php` directly** (gets overwritten on upgrade). Instead, *extend* the model and have your service provider rebind it. This is fragile across upgrades; alternative is to put migrations in a separate provider and use `Customer::saveQuietly()` from a listener to set the field.

For simple cases (no rebinding needed), the migration + direct read via `Customer::find($id)->external_crm_id` works without model changes — Eloquent dynamically exposes any column.

> **Pragmatic alternative:** add a `meta` JSON column once, then store any custom field as a key under it. Single migration; never needs another schema change.

## Pattern 6 — Custom REST API routes

Add your own API endpoints by defining routes in your service provider's `boot()`:

```php
public function boot(): void
{
    \Route::middleware(['auth:api'])->prefix('api/v1/acme')->group(function () {
        \Route::get('hello', function (\Illuminate\Http\Request $request) {
            return ['greeting' => 'Hello ' . $request->user()->email];
        });

        \Route::post('crm-sync/{customer_uid}', \App\Acme\CrmSyncController::class);
    });
}
```

Now `GET /api/v1/acme/hello?api_token=...` works. Route registration is upgrade-safe because it lives in your provider, not core route files.

## Upgrade-safe checklist

Before any extension, verify:

- [ ] Custom logic lives in **your** service provider, not `App\Providers\AppServiceProvider`
- [ ] Custom Blade views live in `resources/views/vendor/overrides/`, not in `resources/views/auth/`
- [ ] Custom routes live in your provider's `boot()`, not `routes/web.php` or `routes/api.php`
- [ ] Custom config lives in your own files (e.g. `config/acme.php`), not core configs
- [ ] Custom migrations live in `database/migrations/` (they're additive; never modify Acelle's)
- [ ] You have an `OVERRIDES.md` at project root listing every customisation + the Acelle version it was tested against
- [ ] Customisations are tracked in git (commit your `app/Providers/AcmeExtensionServiceProvider.php`)
- [ ] You re-test after every Acelle patch upgrade (`patch-x.x.x.bin`)

## After an Acelle upgrade

```bash
# 1. Before applying the upgrade, ensure you have a fresh backup
sudo /usr/local/bin/acellemail-db-backup.sh

# 2. Apply the upgrade per the standard flow
# (see your install guide's patch-upgrade section)

# 3. After upgrade, verify your extensions still work
cd /var/www/acellemail
sudo -u www-data php artisan tinker --execute='
    echo "Provider loaded: " . (class_exists(\App\Providers\AcmeExtensionServiceProvider::class) ? "yes" : "no") . PHP_EOL;
    echo "Custom route present: " . (\Route::has("api.acme.hello") ? "yes" : "no") . PHP_EOL;
'

# 4. Test each customisation manually against the new Acelle version
# 5. Update OVERRIDES.md with the new Acelle version you tested against
```

If something broke:

- Check Acelle's release notes for breaking changes
- Diff the affected core file (e.g. `app/Model/Customer.php`) between versions
- Adjust your extension; commit; document in OVERRIDES.md

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Service provider not loading | composer.json auto-discover skipped it | `composer dump-autoload`; verify the file is in `app/Providers/` |
| Event listener not firing | Wrong event class name | Verify exact class with `Event::listen(\App\Events\CampaignSent::class, fn ($e) => \Log::debug("fired"))` |
| Blade override not used | View namespace not registered | `php artisan view:cache` after registering `loadViewsFrom` |
| Custom route 404s | Provider's `boot()` runs too late, or route conflicts | Verify route order; use `Route::has('name')` to confirm registration |
| Sending-server driver doesn't appear in admin | Registry not extended correctly | Check the exact factory method name in your Acelle version; look at SES driver as reference |
| `php artisan` errors after upgrade | Cached config / views referencing removed classes | `php artisan optimize:clear` |
| Listener fires twice | Event re-emitted in newer Acelle version, or you registered listener twice | Use `Event::hasListeners()` to debug; deduplicate registrations |
| Custom field on Customer model not saving | Model has `$guarded = []` or `$fillable` doesn't include your field | Use `Customer::find($id)->forceFill(['your_field' => $val])->save()` if model rules block it |

## FAQ

**Should I fork AcelleMail to git?** For non-trivial customisations, yes. Maintain a private fork with your extensions on a branch (e.g. `acme-extensions`). Merge upstream upgrades into your branch; resolve conflicts each release.

**Can I write a plugin without forking?** Yes — service providers + Blade overrides + migrations don't require forking. The `app/Providers/AcmeExtensionServiceProvider.php` + `resources/views/vendor/overrides/` + your custom config files can all sit alongside the stock install and survive upgrades.

**What about Composer packages?** You can publish your extensions as a Composer package and `composer require` them. Standard Laravel package layout (`src/`, `config/`, `migrations/`, `views/`). Plus side: clean isolation. Minus: requires running composer on the production server.

**How do I add a custom admin menu item?** Currently no first-class plugin API for menu items — you'd need to override the admin layout Blade. Acelle's roadmap mentions a plugin-API revamp; check [acellemail-updates](https://acellesend.com/kb/category/acellemail-updates) for current state.

**Can I add a new model + table?** Yes — `php artisan make:model AcmeCustomerNote -m`, define the migration + relationships, register in your provider. Use a namespace prefix to avoid conflicts (`App\Acme\Models\CustomerNote`).

**Is there a Slack/Discord for AcelleMail developers?** Check the [Community Spotlight](https://acellesend.com/kb/articles/community-spotlight-business-case-studies) article for current community links.

**How does plugin installation via `/api/v1/plugins/install` relate?** That's for AcelleMail's first-class plugin format (a packaged plugin zip with a `manifest.json`). For custom in-place extensions of *your* install, you don't need the plugin system — the service provider pattern is simpler.

## Related articles

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — use the API first; extend only when you can't
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — outbound events you can subscribe to (alternative to listeners)
- [White-Label Customization Guide](https://acellesend.com/kb/articles/white-label-customization-guide) — Blade overrides for branding
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — common SaaS extensions
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — queued listeners share this infra
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — apply after each upgrade
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — the SES driver is the reference for Pattern 3
- [Contributing to AcelleMail Development](https://acellesend.com/kb/articles/contributing-to-acellemail-development) — upstream contributions

---

# How to verify your email list before you send

> Dead addresses bounce and bounces cost you reputation. Connect a verification service, clean a real list, and see exactly which contacts to keep, remove, or think twice about.

- Canonical URL: https://acellesend.com/kb/articles/how-to-verify-your-email-list-before-you-send
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: beginner
- Tags: Bounce Handling, Sender Reputation, Inbox Placement, List Cleaning, Email Verification
- Published: 2026-06-22 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Every list rots. People change jobs, abandon inboxes, or mistype their address at signup — and each dead address you mail is a bounce that chips away at your sender reputation. Email verification checks each address *before* you hit send, so the bounces never happen.

This guide walks you through connecting a verification service and cleaning a real list, start to finish. You'll need about ten minutes.

## Before you start

- **A verification provider account and its API key.** The platform ships drivers for 16 services — ZeroBounce, Reoon, Emailable, EmailListVerify, myEmailVerifier, Bouncify, NeverBounce, Kickbox, Debounce and more. Most hand out free trial credits, which is plenty to test with.
- **A plan that includes email verification.** If you hit *"email verification servers are not included on your current plan"* when saving, that's a plan quota, not a mistake on your side — ask your administrator to enable it.
- **A list worth cleaning.** Anything you imported from elsewhere, or haven't mailed in a few months, is a good candidate.
- **A queue worker covering the right queues.** Verification runs as a background job, and *which* queue it lands on depends on your provider (see Step 4). A worker started with `--queue=high,batch,default` covers every case.

## Step 1 — Open the Email verification screen

In the left sidebar, under **Sending**, click **Email verification**. This is where every verification service you connect will live.

![The Email verification screen under Sending, listing your connected verification services](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-verify-your-email-list-before-you-send/step-1.png)

Click **Add server** (top right) to connect your first one.

## Step 2 — Pick your verification service

Fill in a **Server name** you'll recognise later, then choose your provider from the **Verification service** dropdown.

![The Add Verification Server form with the Verification service dropdown open](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-verify-your-email-list-before-you-send/step-2.png)

Server name, Verification service and Rate Limits are all there from the start. The one block that stays hidden is **API Configuration** — each provider asks for different credentials, so those fields only appear once you've picked a service.

## Step 3 — Paste your API key and set a rate limit

Choosing a service reveals the **API Configuration** block. Paste the API key from your provider's dashboard, then fill in **Rate Limits**.

![The completed form: server name, API key, and rate limits of 100 verifications per 1 hour](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-verify-your-email-list-before-you-send/step-3.png)

All three rate-limit fields are required, and they're the ones people miss — leave any of them blank and the form quietly refuses to save:

- **Max verifications** — how many addresses this server may check per period (100 is a sensible start).
- **Per time base** and **Time unit** — the period itself, e.g. `1` + `hour`.

Rate limits exist to protect your provider credits. If your provider gives you 20 checks a day, a runaway job on a 50,000-address list would burn through them in seconds.

> **The key isn't checked when you save.** The form accepts any string. A wrong or empty key only surfaces when a run actually calls the provider — see Troubleshooting.

Click **Add server**. You'll land back on the list of verification servers with your new one on it.

## Step 4 — Open your list and start the run

Go to **Audience → Lists**, click the list you want to clean, then open its **Email verification** tab. Under **Verification controls**, pick the server you just created from **Select verification server** and click **Start verification**.

![A list's Email verification tab before a run: 10 total, 0 verified, 10 unverified, with the server selected and Start verification ready](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-verify-your-email-list-before-you-send/step-4.png)

The counters at the top tell you where you stand before you spend a credit: how many subscribers the list holds, how many have been checked before, and how many are still unverified. A run only checks the unverified ones.

The run happens in the background — you'll see a progress bar climb rather than a frozen page, and you can leave and come back.

**Which queue it uses depends on your provider.** Services that support bulk verification — ZeroBounce, Reoon, Emailable, EmailListVerify, myEmailVerifier, Bouncify and NeverBounce — hand the whole list over in one API call and run on the `high` queue. The rest, Kickbox and Debounce among them, check addresses one at a time on the `batch` queue. That's why the worker in *Before you start* covers both.

## Step 5 — Read the results

When the bar reaches 100%, **Verification results** breaks your list into four buckets:

![Verification results: 4 deliverable, 3 undeliverable, 3 risky, 0 unknown](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-verify-your-email-list-before-you-send/step-5.png)

- **Deliverable** — the mailbox exists and accepts mail. Keep these.
- **Undeliverable** — the domain or the mailbox doesn't exist. These are guaranteed bounces. Remove them.
- **Risky** — the address resolves, but something's off: a catch-all domain that accepts everything, a disposable address, or a likely typo. Judgement call.
- **Unknown** — the provider couldn't get a definitive answer, usually a server that wouldn't respond. Re-check these later.

In the run above, ten addresses split 40% deliverable, 30% undeliverable, 30% risky. That undeliverable 30% is exactly what you *didn't* just mail.

Need to run the whole list again — after a re-import, or to re-check a batch of Unknowns? **Reset verification data** at the bottom of Verification controls clears every verdict, so the next run treats the list as unverified again. It asks *"Reset all verification data? This cannot be undone."* first, and it does mean spending the credits a second time.

## Step 6 — Act on what you found

Open the list's **Subscribers** tab and you'll see the verdict on each address:

![The subscribers list showing a verification verdict against each address](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-verify-your-email-list-before-you-send/step-6.png)

To act on a whole bucket at once: set the **verification filter** in the toolbar to **Undeliverable**, tick the checkbox in the table header to select every matching subscriber, then choose an action from the **Select** menu.

> **Delete is permanent.** The confirmation says so plainly — *"This will permanently remove the selected subscribers and all their data. This cannot be undone."* If you'd rather keep the record, **Blacklist** stops them being mailed and blocks the address across all your lists.

A sane policy for most senders:

- **Undeliverable** — remove them. There's no upside to keeping an address that cannot receive mail.
- **Risky** — usually worth holding back from your main sends. Disposable addresses will never engage; a catch-all might be fine. If a risky address is an obvious typo like `user@gmial.com`, the person meant `gmail.com` — worth a manual fix.
- **Deliverable** — mail with confidence.

Re-verify every few months, and always after importing a list from somewhere else. For the wider routine this fits into, see [Email list hygiene: clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability).

## Troubleshooting

**"Email verification servers are not included on your current plan."** A plan quota, not a form error. Your plan needs email verification enabled before you can add a server.

**"Reoon: missing api_key in server options."** Every driver throws this same shape — your provider's name, then the message. It appears when a run starts, not when you save the server, because the key is only read at call time. Reopen the server and re-paste the key, checking for a trailing space.

**The run finishes and everything is Unknown.** A key that's present but wrong looks exactly like this: the provider rejects the call and the driver has no verdict to record. Confirm the key in your provider's dashboard, and check you haven't run out of credits, before re-running.

**The progress bar sits at 0% and nothing happens.** The job is queued but no worker is consuming that queue. Check which queue your provider uses (Step 4), then start a worker that covers it — `--queue=high,batch,default` covers both paths.

**The run slows to a crawl on a big list.** Your rate limit is a run-time throttle, not just a form field. A limit of 100 per hour on a 5,000-address list means the run paces itself over days rather than failing — that's working as intended. Raise the limit only as far as your provider plan actually allows.

## Recap

You connected a verification service, cleaned a real list, and turned a pile of unknown addresses into a clear keep / remove / decide split — before a single bounce hit your reputation.

The habit that keeps it working: verify after every import, re-verify quiet lists a few times a year, and remove undeliverables the same day you find them. Next, it's worth understanding [what different bounce types actually mean](https://acellesend.com/kb/articles/understanding-email-bounce-types) and [how to monitor your sender reputation](https://acellesend.com/kb/articles/sender-reputation-monitoring) over time.

---

# How to read your email campaign report

> A plain-language walkthrough of the campaign report screen — the four headline metrics, how open and click rates are really calculated, and how to drill down to individual recipients.

- Canonical URL: https://acellesend.com/kb/articles/how-to-read-your-email-campaign-report
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: tutorial · Difficulty: beginner
- Tags: Performance, Open Rates, Click Rates, Analytics
- Published: 2026-06-21 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Sending a campaign is only half the job — the report is where you find out what actually happened: how many people received it, opened it, clicked it, and where to aim your next send. This guide walks the campaign report screen from top to bottom so every number makes sense and you know which ones to act on. It's written for anyone who has sent at least one campaign; no analytics background needed.

By the end you'll be able to open any campaign's report, read its headline numbers correctly, judge whether they're healthy, and drill down to the individual people who engaged.

## Before you start

- **A campaign you've already sent.** The report only fills in once a campaign has gone out. If you haven't sent one yet, follow [create and send your first email campaign](https://acellesend.com/kb/articles/create-your-first-email-campaign) first.
- **Open and click tracking left on.** These are on by default, and the report's open and click numbers depend on them. Once the report is open you can confirm it in the **Campaign details** panel, where *Tracking* reads "Opens, Clicks".
- **A little patience for opens.** Opens and clicks keep arriving for hours — sometimes days — after a send, so a report checked five minutes after sending will look quieter than the same report tomorrow.

> **Why the report matters:** the numbers tell you what to change next. A weak open rate points at your subject line and sender reputation; a healthy open rate but few clicks points at the email's content and calls-to-action. Reading the report is how one campaign makes the next one better.

## Step 1 — Open the report

From the left menu, open **Email** under *Campaigns*, then click the name of a campaign you've already sent. It opens straight onto the **Overview** tab — the summary report.

Along the top is a row of tabs: **Overview**, **Insights**, **Links**, **Map**, **Sending logs**, and **Email review**. Overview is where you'll spend most of your time; the others drill into specific angles.

At the top of Overview you'll often see a plain-language **insight banner** — for this campaign it reads *"Strong engagement — your open rate of 48% is above average — 1,893 recipients engaged with your campaign."* It's an automatic, one-line read of the headline numbers: a quick gut-check before you dig into the detail.

![The campaign report open on the Overview tab](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-read-your-email-campaign-report/step-1.png)
*Click a sent campaign to land on its Overview report; the tab row lets you drill into links, locations, and recipients.*

Just under the title you'll see when the campaign was sent and a **Last updated** time with a refresh icon — more on that in Troubleshooting.

## Step 2 — Read the four headline numbers

The four cards near the top summarize the whole send. Read them left to right:

1. **Recipients** — the size of the audience the campaign was sent to (here, 7,914).
2. **Delivered** — how many messages the receiving mail servers accepted, with the delivery rate beneath (3,935, 49.72%). Your open and click rates are measured against *this* number, not Recipients. A healthy campaign delivers **95% or more**, so a figure this low is worth investigating (see Troubleshooting).
3. **Opened** — how many *unique* people opened the email, with the open rate (1,893, 48.11%).
4. **Clicked** — how many unique people clicked a tracked link, with the click rate (154, 3.91%).

![The four headline metric cards: Recipients, Delivered, Opened, and Clicked](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-read-your-email-campaign-report/step-2.png)
*Recipients → Delivered → Opened → Clicked, each with its rate underneath.*

> **Is that good?** Rough benchmarks for most lists: **delivery 95%+**, **open rate 20–40%** (higher right after someone subscribes), and **click rate 2–5%**. They vary a lot by industry, so your own trend over time matters more than any single number — but they tell you at a glance that 48% opens here is strong while 3.91% clicks is about average.

Picture the four as a shrinking staircase: of everyone the campaign went to, some were delivered; of those, some opened, and some clicked. (A **bounce** — a message the receiving server refused — is one that never reaches Delivered.)

## Step 3 — Understand how the rates are calculated

Scroll down to the **Open rate** and **Click rate** panels. This is where most people misread a report, so it's worth a moment.

Both rates share the **same base: Delivered.**

- **Open rate** = unique opens ÷ delivered = 1,893 ÷ 3,935 = **48.11%** (not 1,893 ÷ 7,914). The panel spells it out with **Successful deliveries**, **Unique opens**, and **Not opened**.
- **Click rate** = unique clicks ÷ delivered = 154 ÷ 3,935 = **3.91%** — the same denominator as opens, so the two are directly comparable.

![The Open rate and Click rate panels showing successful deliveries, unique opens, and totals](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-read-your-email-campaign-report/step-3.png)
*Both rates divide by Delivered. "Unique" counts people, so one subscriber opening six times still counts once.*

"Unique" is the key word. The Click rate panel shows both **Total clicks** (every click — one person clicking twice counts twice) and **Unique clicks** (people). They happen to be equal here (154 and 154), but the moment someone clicks a link more than once, Total climbs above Unique. Use unique clicks to judge how many humans engaged, and total clicks to see raw link activity. The **Abuse reports** figure on the same panel counts recipients who marked the email as spam — you want it at or near zero.

## Step 4 — Drill down to individual recipients

Summary rates tell you *how many*; sometimes you need *who*. Two places drill in:

- **Open log** and **Click log** — the buttons on the Open rate and Click rate panels — list the exact people who opened or clicked. This is your "who engaged" view.
- **Sending logs → Delivery log** — every recipient with a **Status** (it reads **Sent** once the message has left for the receiving server) and a date, searchable by email. This is your "who received it" view — handy when someone says they never got the email.

![The per-recipient delivery log listing each recipient, status, and date](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-read-your-email-campaign-report/step-4.png)
*The delivery log lists every recipient, their status, and when the message was handled — search by email to check one contact.*

Lower down on Overview, **Top links clicked** shows which URLs earned the clicks, and **Top open countries** and **Most active subscribers** show where and who your engagement came from — useful for planning [a re-engagement campaign for the people who didn't open](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers). The **Rate summary** panel also lists your **Unsubscribed** and spam-complaint figures; keep both at or near zero — a spike there means your content or frequency is pushing people away.

> **Tip:** To attribute clicks to specific buttons or track them in your website analytics, add UTM tags to your links — see [setting up UTM parameters for campaign tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking).

## Troubleshooting

**My open rate looks strangely high or jumps around.** Apple's Mail Privacy Protection pre-loads tracking pixels, which registers an "open" even when nobody read the email. Treat open rate as a directional signal; clicks are far more reliable, though automated security scanners can still inflate them a little. There's a fuller explanation in [email open rates in the age of Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp).

**The numbers look stale or lower than I expected.** The report is cached for speed. Use the refresh icon next to **Last updated** to recompute the latest figures, and remember opens and clicks keep trickling in for a day or two after a send — check back tomorrow before drawing conclusions.

**Delivered is much lower than Recipients.** A small gap is normal — a few addresses **bounce** (the receiving server refuses them) or are skipped as invalid at send time. A *large* gap, like the ~50% in this example, is a red flag. Open the **Statistics breakdown** panel, which itemizes **Failed**, **Bounced**, and **Reported as spam**, to see where the messages went. If the campaign still shows as sending, give it time and refresh; if it shows as finished, a delivery rate that low usually points to list quality or sender-reputation problems worth chasing down — start with [cleaning your list](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability).

## Recap

You can now open a campaign's report, read the Recipients → Delivered → Opened → Clicked funnel, calculate open and click rates against the right base (Delivered), judge them against rough benchmarks, and drill down to individual recipients and links. For the concepts behind the numbers — deeper benchmarks and what counts as "good" — read [understanding email analytics and key metrics](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics). When you're ready to act on what you found, [analyze your click map to optimize the layout](https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout).

---

# Campaign Scheduling and Timezone Optimization in AcelleMail

> When you send a campaign at "9am" — whose 9am? Yours, your subscribers'? AcelleMail's scheduler defaults to your account timezone. This guide walks the UI + timezone-aware patterns + send-time optimization.

- Canonical URL: https://acellesend.com/kb/articles/campaign-scheduling-and-timezone-optimization
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-06-12 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The scheduling decision

When you launch a campaign, you pick one of:

| Option | Use when |
|---|---|
| **Send now** | Immediate transactional (welcome, password reset); first test campaigns |
| **Schedule for later** | All marketing campaigns; coordinate with business events |
| **Send by subscriber timezone** | Global audience; want everyone to receive at the same LOCAL time |

AcelleMail's scheduler defaults to the account-wide timezone. Subscriber-local timezones require per-subscriber timezone tagging.

## The scheduling UI

### Step 1: Open the schedule step

In the campaign builder, after completing setup + template, you reach the **Schedule** step:

![Schedule step — send-now toggle](https://acellesend.com/plugins/acelle/landing/images/help/kb/scheduling/01-schedule-send-now.png)

By default, "Send now" is selected. Toggle to "Schedule for later" to set a specific date+time.

### Step 2: Pick the schedule time

![Schedule later — date + time picker](https://acellesend.com/plugins/acelle/landing/images/help/kb/scheduling/02-schedule-later.png)

Pick the date + time. The picker uses your account timezone (visible in the field label).

### Step 3: Confirm the timezone

Settings → Account → **Timezone**:

![Account timezone setting](https://acellesend.com/plugins/acelle/landing/images/help/kb/scheduling/03-account-timezone.png)

This is what AcelleMail uses for all schedule calculations + display. Change it once when your account is created; rarely after.

## Best practices for send time

### Default rule: weekday mid-morning, recipient timezone

For most B2C and B2B audiences, the consistent winner is:

- **Weekday**: Tuesday / Wednesday / Thursday (avoid Monday + Friday)
- **Time**: 9:30am - 11:00am in recipient's timezone
- **Avoid**: weekends, early morning (<7am), late afternoon (>4pm), midnight

For a US-East-Coast-dominant audience using account timezone of America/New_York:

```
Tuesday 09:30 ET
Tuesday 10:00 ET
Wednesday 10:00 ET
Thursday 09:30 ET
```

These windows hit recipients when they're starting their work day — open rates peak.

### When to break the rule

| Audience | Better time |
|---|---|
| News junkies (financial, sports) | Earlier — 6:30am ET catches morning commute |
| B2B SaaS / tech | Tuesday 10am OR Friday 2pm (weekly wrap-up) |
| E-commerce promotions | Saturday 9am for weekend shoppers |
| College students | Sunday evening (homework time) |
| International (no clear majority) | Multiple send waves per timezone band |

## Multi-timezone audience

If your audience is split US + EU + APAC, three patterns:

### Pattern A: Send at "your" timezone (simplest)

```
Tuesday 10:00 ET
  → US East gets it at 10am local
  → US West gets it at 7am local (early)
  → London gets it at 3pm local (afternoon — different but workable)
  → Tokyo gets it at 11pm local (night — engagement drops)
```

Sub-optimal for non-US recipients but easy to ship.

### Pattern B: Multiple send waves

```
Tuesday 09:30 PT  (West Coast US)
Tuesday 09:30 ET  (East Coast US)
Tuesday 09:30 GMT (London)
Tuesday 09:30 JST (Tokyo)
```

Each subscriber's timezone tag determines which wave they receive in. AcelleMail handles this via per-segment campaign launches OR via per-subscriber send-time-optimization (advanced).

### Pattern C: Subscriber-local timezone

If your install supports per-subscriber timezone:

```
Tuesday 09:30 in subscriber's local timezone
```

Each subscriber receives at 9:30am THEIR time. AcelleMail's scheduler does the math.

Requires:
- Per-subscriber `timezone` field on the list
- Send-time optimization feature enabled (varies per install)

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Schedule rejects "tomorrow 9am" | Date is in past relative to account timezone | Verify account timezone matches what you expect |
| Send happens 1 hour off expected time | Daylight saving transition | AcelleMail handles DST automatically; if off, confirm timezone setting (not "UTC offset") |
| Different recipients receive hours apart | Send queue throttling spreading over time | Expected for large sends; tune sending limit if too slow |
| Schedule shows in UTC despite account TZ set | Browser session timezone different from account | Reset by re-saving the schedule via the explicit picker |
| Campaign sends in the wrong week | Time-of-day in account TZ vs subscriber TZ confusion | Use ISO format dates to remove ambiguity |
| Subscriber-local timezone shows everyone at midnight UTC | Per-subscriber timezone field empty | Back-fill timezone column or fall back to account TZ |

## Send-time optimization

AcelleMail's send-time optimization (varies per install) uses each subscriber's past-open-time pattern to predict the best send time for them:

```
Subscriber A: opens at 9:30am ET routinely → schedule for 9:30am ET
Subscriber B: opens at 6:30pm PT (evening browse) → schedule for 6:30pm PT
Subscriber C: no clear pattern → use default 10:00 account TZ
```

If your install has this feature, enable in **Settings → Sending → Send-time optimization**.

## How to pick the right time when you don't know your audience

1. Test 3 windows over 6 weeks (Tuesday 10am, Wednesday 2pm, Sunday 9am)
2. Compare open rates across the 3 cohorts
3. Continue with the winner
4. Re-test annually as audience behavior shifts

<details>
<summary><strong>Advanced: per-segment scheduling, automated optimal-time selection, and avoiding holiday clashes</strong></summary>

**Per-segment scheduling:**

Split your audience by behavior + schedule each segment optimally:

```
Engaged segment (open <7d ago):  Tuesday 9:30 ET (proven optimal)
At-risk segment (open >30d ago): Saturday 10am ET (different cadence)
New segment (signup <14d ago):   Welcome series — automation-triggered
```

Each segment gets a separate campaign with its own optimal schedule. Improves overall engagement vs. one-size-fits-all blast.

**Automated optimal-time selection:**

```bash
#!/bin/bash
# Daily-run script: identify optimal time per segment based on last 90 days
ACELLE_TOKEN="..."
LIST_UID="..."

for segment in engaged at-risk new; do
  # Query open events for this segment over last 90 days, group by hour-of-week
  best_time=$(curl -sH "Authorization: Bearer $ACELLE_TOKEN" \
    "https://acellemail.com/api/v1/lists/$LIST_UID/opens-by-hour?segment=$segment&days=90" \
    | jq -r '.peak_hour')

  echo "Segment $segment: peak engagement at $best_time"
  # Set campaign schedule via API or store for next manual launch
done
```

**Avoiding holiday clashes:**

| Date | Likely conflict |
|---|---|
| Thanksgiving week (US) | Inbox flooded; low engagement |
| Black Friday / Cyber Monday | Promotional inbox overflow |
| Christmas week | Most B2B audiences ignore email |
| Local national holidays | Per-region; check audience demographics |
| Religious holidays | Per-region; check audience demographics |

Skip these dates entirely OR send at unusual times to stand out (Christmas afternoon, when normal senders pause).

**Programmatic timezone-aware sending:**

```bash
# Get subscriber timezones from your data + bucket per region
sub_tzs=$(curl -sH "Authorization: Bearer $TOKEN" \
  "https://acellemail.com/api/v1/lists/$LIST_UID/subscribers/timezones" | jq -r '.')

# Schedule per-bucket campaigns
for tz in $sub_tzs; do
  schedule_time=$(calculate_optimal_time $tz 'Tuesday' '09:30')
  schedule_campaign $LIST_UID $tz $schedule_time
done
```

For ~50 timezones globally, the bucket count gets unwieldy. Most senders use 3-4 macro-regions (Americas, EMEA, APAC, India).

**Avoiding "send drift":**

If your campaign cadence is "every Tuesday 10am ET" and you stop tweaking:

1. Holiday weeks (Thanksgiving, Christmas) skip
2. DST shifts confuse the schedule by 1 hour
3. After 18 months, you'll have weeks where you sent twice and weeks where you sent zero

Audit quarterly: confirm your weekly Tuesday actually happens every Tuesday. Catch drift via the campaigns index timestamp pattern.

</details>

## Related articles

- [Your first email campaign in AcelleMail](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Birthday and anniversary email automations](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations)

---

# Email Queue Architecture in AcelleMail (Why Sends Are Steady, Not Sudden)

> AcelleMail uses a job queue to pace out large sends — instead of trying to push 50,000 emails at once, workers pick recipients off a queue one at a time. This explainer covers why that matters for deliverability and how to read the campaign status flow.

- Canonical URL: https://acellesend.com/kb/articles/email-queue-architecture-in-acellemail
- Author: Lena Hart, Email Deliverability Editor
- Category: Email Infrastructure — Mail servers, MTA configuration, relay setup, IP management, monitoring
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-06-06 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

If AcelleMail tried to push every email in a 50,000-recipient campaign at the same instant, three things would break:

1. **Your sender reputation** — receiving servers treat a 50k spike as a spam pattern
2. **Your SMTP provider** — every provider rate-limits sends per second / per minute; you'd be throttled or banned
3. **Your campaign itself** — a single transient network blip would lose thousands of messages with no retry

So AcelleMail does the opposite: it converts the campaign into a queue of small jobs and lets background workers drain that queue at a sustainable pace. This guide explains the model, the four states a campaign passes through, and what to watch.

## The campaign lifecycle, four statuses

Every campaign you send moves through these statuses on the campaign **Overview** page. They tell you exactly where the campaign is.

| Status badge | What's happening | Typical duration |
|---|---|---|
| **Draft** | You're still editing it — no jobs exist yet | Until you click Send |
| **Queuing** | AcelleMail is building the recipient queue — one job per subscriber | Seconds (small list) to a minute or two (large list) |
| **Queued** | All jobs exist, none have been picked up yet by a worker | Brief — workers should pick up immediately |
| **Sending** | Workers are draining the queue. Each completed job marks one recipient as delivered, soft-bounced, or hard-bounced | Minutes to hours depending on list size + throttle rate |
| **Done** | Every job has finished | Final state — campaign report is now stable |

If your campaign sits on *Queuing* or *Queued* for more than a few minutes, your queue worker process isn't running — see the Common issues table below.

## Why one job per recipient (not one big job)

Splitting the work into per-recipient jobs is what gives AcelleMail two operational properties you want:

**1. Retry is per-recipient.** If recipient #4,237 fails because their mail server hiccupped, AcelleMail retries *just that one job* — it doesn't restart the whole campaign. The other 49,999 jobs are unaffected.

**2. Pacing is automatic.** You can run more or fewer workers, and the campaign automatically sends faster or slower without any UI change. Running 5 workers on a small VPS sends slowly and gently; running 50 workers on a tuned server sends faster — same code, same campaign, just more drains pulling from the same pool.

This is the same pattern Gmail itself uses internally. Big senders never blast — they pace.

## The five job types you'll hear about

You don't need to think about these — they're internals. But if you ever talk to an operator about your AcelleMail server's health, these are the names that come up:

| Job | What it does |
|---|---|
| **Campaign job** | Iterates the recipient list and dispatches a per-email job for each subscriber |
| **Send-email job** | Sends one email to one recipient over SMTP |
| **Bounce-processor job** | Reads incoming bounce notifications and updates subscriber statuses |
| **Open / click tracker** | Records the events from the tracking pixel and link redirector |
| **Webhook delivery job** | Pushes events to your configured webhook URLs |

Each runs independently. A backed-up open-tracking queue doesn't stop sends; a backed-up send queue doesn't stop tracking. They isolate failures.

## What happens when a job fails

Failed jobs are retried automatically — usually 3 attempts with a back-off (a delay between attempts that grows on each retry). A "soft" failure (recipient server busy, network timeout) almost always succeeds on retry 2 or 3. A "hard" failure (recipient address doesn't exist, domain has no MX record) is recorded as a bounce immediately and not retried.

After all retries are exhausted, the job lands in a **failed jobs** table. Your operator can re-dispatch it manually if needed, or inspect it to understand why a class of failure is recurring. From the marketer's side: any hard bounces become subscriber-status changes you can see on the list; failed jobs that didn't cause a bounce are an operator concern.

## How to read send progress

Open the campaign **Overview** while it's in *Sending*. The progress bar fills as the worker queue drains. The recipients counter (`Delivered: 12,300 / 50,000`) increments as each send-email job finishes.

The campaign report tabs (Opens, Clicks, Bounces, Unsubscribes) start populating as soon as the first events come in — even before the campaign moves to *Done*. So you can see early engagement signal while a long campaign is still finishing the send.

## Common issues

| What you see | What to do |
|---|---|
| Campaign stuck on **Queuing** | Your queue worker is not running. The operator command is `supervisorctl restart acellemail-worker:*` on most setups. See [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs). |
| Campaign on **Sending** for hours but delivery count barely moves | Either: (a) your SMTP provider is rate-limiting you (check the provider dashboard), or (b) your queue worker count is too low. Talk to the operator. |
| Send completes but bounce count is stuck at 0 even though some addresses are invalid | The bounce-processor job is not running — check the bounce-handler config under **Bounce handlers** in the admin sidebar. |
| Some recipients got the email twice | Almost never the queue — usually the campaign was duplicated or a list contains the same address twice. Check **Lists → [list] → Subscribers** for duplicates. |

## What to do after

1. Watch one of your next live campaigns through the status flow — *Queuing → Queued → Sending → Done* — to see the model in action.
2. If your campaigns sit on *Queuing* longer than ~30 seconds, ask your operator whether queue workers + the cron scheduler are running.
3. Pair this with [How Email Delivery Actually Works](https://acellesend.com/kb/articles/how-email-delivery-actually-works) for the receiving side.

## Related articles

- [How Email Delivery Actually Works](https://acellesend.com/kb/articles/how-email-delivery-actually-works)
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs)
- [Email Throttling and Rate Limits Explained](https://acellesend.com/kb/articles/email-throttling-and-rate-limits-explained)
- [Bounce Types Deep Dive](https://acellesend.com/kb/articles/understanding-email-bounce-types)

---

# Email Campaign ROI: Math + AcelleMail Data Points

> Email ROI = (Revenue − Cost) / Cost. This guide shows the math, which AcelleMail data points feed each variable, the costs people forget to count, and a working revenue-attribution pattern using UTMs + your store.

- Canonical URL: https://acellesend.com/kb/articles/email-roi-calculation-measuring-campaign-profitability
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-06-06 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Email ROI is the metric that justifies the channel. *"Open rates are up"* is interesting; *"every dollar spent on email returns $42"* is the conversation you have with your finance team. This guide shows the actual math, the AcelleMail data that feeds it, and the costs people forget to count.

## The formula

```
ROI = (Revenue from email − Cost of email) / Cost of email × 100
```

Multiply by 100 to express as a percentage. **300% ROI** means every $1 spent returned $3 in profit. **−50% ROI** means every $1 spent lost 50¢.

## Revenue — pull from your store, not from AcelleMail

AcelleMail tells you how many clicks happened. **Your e-commerce store / CRM tells you how many of those clicks converted into purchases.** The reliable path:

1. **Tag every email link with UTMs** ([UTM Parameters article](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking))
2. **Your store records `utm_campaign` on every order** (Shopify, WooCommerce, Stripe, etc. all support this natively or via plugin)
3. **Filter orders by the campaign's UTM** to get revenue attributable to that campaign

Without UTMs, you cannot reliably attribute revenue to specific campaigns — *"email referrals"* in Google Analytics is too coarse. Set up UTMs first.

### Attribution windows — pick one and stick

Same campaign drives revenue over time. You need a rule for when to "stop" counting.

| Window | What it captures | Best for |
|---|---|---|
| **Click → 1 hour** | Direct, immediate purchases | Flash sales |
| **Click → 24 hours** | Same-day conversions including comparison shopping | Most consumer campaigns |
| **Click → 7 days** | Considered purchases, multi-touch | B2B / high-ticket |
| **Click → 30 days** | Full LTV influence | Long-cycle products |

For most consumer emails, **24-hour attribution** is the right default. For B2B / SaaS, **7-day**. Pick a window per industry and apply it consistently — comparing 24-hour ROI on one campaign to 30-day ROI on another is misleading.

## Cost — the components people forget

Email feels "free" because there's no per-email cost (unlike paid ads). But there's a real cost stack:

| Cost component | Typical range | Why people miss it |
|---|---|---|
| **AcelleMail subscription / hosting** | $20-200/month | Fixed cost, but real |
| **Sending server fees** (SES, SendGrid, etc.) | $0.10-1.00 per 1,000 emails | Variable, ignored if low |
| **Design time** (template + email creation) | 2-8 hours per campaign × hourly rate | "Internal" so feels free |
| **Copy / strategy time** | 1-4 hours per campaign | Same |
| **List management** (segmentation, cleaning) | 2-5 hours/month | Spread across all campaigns |
| **Image / asset purchase** | $0-100 per campaign | Often ignored if reusing stock |
| **Tooling** (verification service, deliverability monitoring) | $0-500/month | Subscription that "just runs" |

A campaign's *fully-loaded cost* — not just the send fee — is what you divide revenue by.

### A worked example

| Item | Cost |
|---|---|
| Pro-rated AcelleMail subscription (1 campaign out of 4 monthly) | $25 |
| Sending fees: 10,000 emails × $0.30/1k | $3 |
| Designer's time: 4 hours × $50 | $200 |
| Copywriter's time: 2 hours × $60 | $120 |
| **Total cost** | **$348** |

Campaign drove **$2,800 revenue** (40 orders × $70 avg, attributed via UTM `utm_campaign=spring_sale_2026` filter in store).

```
ROI = ($2,800 − $348) / $348 × 100
    = $2,452 / $348 × 100
    = 705%
```

Each $1 returned $7.05. Strong campaign.

## Reading AcelleMail's numbers in context

AcelleMail's campaign report doesn't show revenue — that's intentional (it doesn't know what each subscriber did at your store). But it gives you the **clicks** that feed the calculation:

![Campaign Overview report stat cards showing RECIPIENTS 14,794, DELIVERED 3,838, OPENED 931, CLICKED 146](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/01-campaign-overview.png)

(Screenshot from [Read the Campaign Report](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics).)

The flow:

1. **AcelleMail** says: 146 unique clicks across X UTM-tagged URLs
2. **Your store** says: 12 orders had `utm_campaign=spring_sale_2026` in the last 24 hours, totalling $840
3. **You compute**: ROI = ($840 − $52 cost) / $52 × 100 = **1,515%**

Cross-checking: 146 clicks → 12 orders = **8.2% click-to-order conversion rate**. That's healthy (typical 3-15% for warmed audiences). If you see <1%, something's off — bad landing page, mismatch between subject promise and offer, etc.

## Per-campaign vs per-subscriber-per-year

Two ways to look at email ROI:

| View | When to use | Calculation |
|---|---|---|
| **Per-campaign ROI** | Evaluate a single send | Revenue from this campaign / Cost of this campaign |
| **Per-subscriber-per-year (RPY)** | Evaluate the whole program | Total annual revenue / Subscribers on list |

A subscriber whose lifetime value (LTV) is $500/year contributes — across all the campaigns they receive — that $500. If your list is 10,000 subscribers and your annual email revenue is $1,200,000, your **RPY = $120/subscriber/year**.

| RPY | Diagnosis |
|---|---|
| **$50-100** | Solid B2C program |
| **$100-300** | Strong; doing many things right |
| **$300-1,000+** | Best-in-class (high-LTV products, well-targeted lists) |
| **<$25** | Either low-LTV product OR list is mostly inactive — run [re-engagement](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers) |

Per-campaign ROI tells you which campaigns to repeat. RPY tells you whether the entire email program is worth investing in.

## Hidden non-revenue value

ROI is one number, but email drives outcomes that don't show up as direct revenue:

| Hidden value | How to estimate |
|---|---|
| **Brand recall / re-purchases** | RPY captures some of this |
| **Customer support deflection** ("How do I…?" emails answered preemptively) | Survey CSAT post-campaign |
| **Referrals** generated by satisfied subscribers | Track refer-a-friend codes |
| **Reduced churn** (subscribers who stayed because the email reminded them you exist) | A/B test sending vs not sending for a quarter |

Include these qualitatively when defending the email budget — *"ROI is 700% direct, plus reduced churn by 12%"*.

## Improving ROI

Three buckets of improvement, ranked by leverage:

| Bucket | Impact | Effort |
|---|---|---|
| **Reduce send cost** | Low — already cheap | Switch to cheaper sending server only if usage is heavy |
| **Increase conversion rate** | High — better targeting, copy, offer | Focused A/B testing |
| **Increase list quality** | Highest — better subscribers → higher RPY | List hygiene + reduced churn |

**Don't optimise cost first.** Email is one of the cheapest marketing channels by far. The leverage is in conversion + list quality.

## Common issues

| What you see | What to do |
|---|---|
| Reported revenue doesn't match what store thinks | Attribution window mismatch. Confirm both sides use the same window. |
| ROI looks great but business isn't growing | You're cannibalising organic / direct traffic (same customers would have bought without email). Run a holdout test: don't email 10% of the audience, compare conversion rates. |
| AcelleMail says 200 clicks, store says only 50 referrals | Most likely UTMs got stripped somewhere (browser tab restore, social-share rewrites). Audit step-by-step using [UTM Parameters article](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking) §5. |
| ROI calculation says 4,000% (suspiciously high) | You probably under-counted costs. Add design + copy hours; recompute. |
| ROI looks negative — should we stop emailing? | NO — at least not based on one campaign. Look at trailing 3-month avg + RPY before pulling the plug. |

## A weekly / monthly review template

For a sustainable email program, run this review on a Friday afternoon:

| Question | Where to look |
|---|---|
| What was last week's per-campaign ROI? | Store's UTM-filtered revenue / cost |
| What's our 3-month average ROI? | Same, rolling |
| What's our current RPY? | Annual revenue / subscriber count |
| Which campaign type drove the highest ROI? | Cross-reference with category tags |
| Which subscribers have we never converted? | [Re-engagement article](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers) candidates |

## Related articles

- [Read the Campaign Report: AcelleMail Analytics Tour](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics)
- [Setting Up UTM Parameters for Campaign Tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking)
- [Open Rates in the Age of Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp)
- [Re-engagement Campaigns: Win Back Inactive Subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# How to add custom fields to capture more about your subscribers

> Custom fields let you collect more than name and email, then personalize and segment on it. Add one step by step: pick a type, name it and its merge tag, set options, and use it in emails and segments.

- Canonical URL: https://acellesend.com/kb/articles/how-to-add-custom-fields-to-your-list
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: beginner
- Tags: Personalization, Custom Fields
- Published: 2026-06-03 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Every subscriber is more than an email address. Where they signed up, which plan they're eyeing, their birthday, their company size — that's the data that lets you send *"here's the Growth plan you looked at"* instead of *"Dear customer."* **Custom fields** are how you capture it. A custom field adds a column to your list, gives you a merge tag to drop that value into any email, and becomes something you can filter on when you build a segment.

This guide adds a custom field from scratch, end to end, so by the finish you can collect a new piece of data, personalize with it, and target by it.

**Who this is for:** anyone managing a mailing list who wants to collect more than name and email. You'll work on the customer side, inside one of your lists — no admin access needed.

## Before you start

- **A list to add the field to** — custom fields belong to a single list. Go to **Audience → Lists** and open the one you want to extend.
- **Know what you're collecting** — a piece of data with a clear use: "plan interest" to tailor offers, "signup source" to see what's working, "birthday" to send a date-triggered email. If you can't name how you'll use it, don't collect it.

## Step 1 — Open Manage list fields

Open your list, then click the **Manage list fields** tab. This screen lists every field on the list — the built-in ones (Email, First Name, Company, Date of Birth…) plus any custom fields you add. Each row shows the field's **name**, its **type**, and — the important part — its **merge tag**, like `{{ SUBSCRIBER_FIRST_NAME }}`. That tag is how you reference the value later.

One rule to know up front: the **Email** field is required and can't be removed. Everything else is yours to add, reorder, hide, or delete.

![The Manage list fields screen](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-add-custom-fields-to-your-list/step-1-fields.png)
*The Manage list fields tab lists every field with its type and its merge tag (`{{ SUBSCRIBER_… }}`). Email is required and can't be removed; everything else is yours to manage.*

## Step 2 — Add and configure a field

Scroll to the **Add field** row at the bottom and click the **type** that fits your data. Grouped by what they do:

- **Text** — a short free-text answer (job title, city).
- **Number** — a numeric value you can compare (team size, order count).
- **Dropdown / Radio** — one choice from a fixed list you define.
- **Multi-select / Checkbox** — one or more choices from a fixed list.
- **Date / Date & time** — a calendar value (birthday, renewal date).
- **Text area** — a longer free-text answer.
- **Phone** — a phone number.

A new field row appears, expanded, ready to configure:

- **Label** — what you (and your signup form) call the field, e.g. *Plan interest*.
- **Tag** — the merge tag's name. It's auto-prefixed `SUBSCRIBER_`, so typing `PLAN_INTEREST` gives you `{{ SUBSCRIBER_PLAN_INTEREST }}`. This is the token you'll use in emails and segments.
- **Default value** — an optional value used when a subscriber leaves it blank.
- **Required** — force a value at signup.
- **Visible** — show the field on your signup forms (turn it off for data you set yourself, like a score or tag).
- **Options** — for a Dropdown, Multi-select, Checkbox, or Radio, add each choice as a **label — value** pair (e.g. *Starter — starter*). Click **Add option** for more.

Fill it in, then click **Save fields** at the bottom.

![Configuring a Dropdown custom field](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-add-custom-fields-to-your-list/step-2-configure.png)
*A Dropdown field being configured: a Label, its auto-generated merge tag, and the choices as label — value pairs. Number, Date, Text and the rest share the same Label / Tag / Required / Visible controls.*

> **Tip:** Keep tags short and stable. The merge tag is baked into every email and segment that uses the field, so renaming it later means updating those references. Pick `PLAN_INTEREST` once and leave it.

## Step 3 — Use your new field

A custom field earns its keep in two places, both powered by that merge tag:

- **Personalize emails.** Drop `{{ SUBSCRIBER_PLAN_INTEREST }}` into a subject line or body and each recipient sees their own value — *"Ready to move up to Growth?"* See [advanced merge tags](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags) for fallbacks and formatting.
- **Target with segments.** The moment you save the field, it shows up as a **condition** when you build a segment — so you can send only to *Plan interest equals growth*. That's the payoff below.

![The custom field as a segment condition](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-add-custom-fields-to-your-list/step-3-use-in-segment.png)
*Right after saving, "Plan interest" is selectable as a segment condition — filter your list by the data you just started collecting. See the full segment walkthrough for how to stack conditions.*

And when you [import contacts from a CSV](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping), your new field appears in the field-mapping step, so you can map a spreadsheet column straight into it.

## Field types — which to pick

- Filtering or math later (counts, sizes) → **Number**.
- A fixed set of answers you'll segment on → **Dropdown** (one) or **Multi-select** (several) — cleaner than free text, which is easy to mistype.
- A date you'll trigger emails from → **Date**.
- Anything freeform → **Text** (short) or **Text area** (long).

Choosing a structured type (Dropdown/Date/Number) over plain Text pays off at segment time — you filter on clean values instead of guessing at typos.

## Troubleshooting

- **The field doesn't show on my signup form** → turn on **Visible** for it. Fields with Visible off exist on the list but aren't shown to subscribers (useful for internal values you set yourself).
- **The merge tag prints literally in an email** → check the tag spelling matches exactly (case included). `{{ SUBSCRIBER_PLAN_INTEREST }}` only resolves if that's the field's real tag; a typo renders as plain text.
- **I can't find the field as a segment condition** → save the field first (**Save fields**), then reopen the segment builder — conditions read the list's saved fields.
- **I need to remove a field** → use **Delete field** on its row. Note this drops that column's data for every subscriber, so export first if you might want it back. The Email field has no delete — it's required.

## Recap

You've added a custom field: opened Manage list fields, picked a type, named it and its merge tag, set its options and visibility, and saved. Now you can collect that data, personalize emails with `{{ SUBSCRIBER_… }}`, and target it in a segment.

Next, turn that field into an audience in [Build a subscriber segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment), and put the merge tag to work in [Personalization beyond first name](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags).

---

# How to create and send your first email campaign

> The blank “new campaign” screen looks intimidating — it isn’t. Follow the five-step builder end to end: choose your recipients, set the subject and sender, design the email, and send your first campaign with confidence.

- Canonical URL: https://acellesend.com/kb/articles/create-your-first-email-campaign
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Email Templates, Subject Lines, Email Preview, Open Rates, Click Rates
- Published: 2026-06-02 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Sending your first campaign is the moment all the list-building pays off — but the blank "new campaign" screen can feel intimidating if you've never done it. It shouldn't. The campaign builder walks you through five clear steps, and this guide follows every one of them: choose who gets the email, set it up, design it, and send. It's written for first-timers — by the end you'll have a real campaign ready to go out, and you'll know exactly what each step does.

## Before you start

Two things need to be in place:

- **A mailing list with subscribers.** The campaign sends to a list, so you need at least one with contacts in it. (Just getting started? See [importing contacts from a CSV](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping).)
- **A verified sending domain + sender address.** Your "from" address must be a *verified sender* or the send will fail. If you haven't set this up, work through [SPF, DKIM, and DMARC](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) first — it's also what keeps you out of the spam folder.

Open **Email** in the left sidebar and click **Create** (a regular campaign). You'll land on a five-step wizard — **Recipients → Setup → Template → Schedule → Confirm** — shown across the top so you always know where you are.

## Step 1 — Choose your recipients

The first step, **"Choose your recipients,"** asks *who* gets this email.

Under **Mailing lists & segments**, open the **Mailing list** dropdown and pick the list you want to email. That's the only required choice here.

![Choosing the mailing list on the Recipients step](https://acellesend.com/plugins/acelle/landing/images/kb-bot/create-your-first-email-campaign/step-1-recipients.png)
*Pick the list that should receive this campaign — the one required choice on this step.*

Two optional refinements:

- **Segment** — leave it on *All subscribers* to email everyone on the list, or point it at a [saved segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment) to target just part of the list (say, only engaged openers).
- **Exclude lists & segments** — anyone on a list/segment you add here is removed from the send. Handy for suppressing people who already bought, for example.

You can even **Add another list** to email several at once. When you're set, continue to Setup.

## Step 2 — Set up the campaign

Step 2, **"Campaign setup,"** is where you name the campaign and tell recipients who it's from.

Under **Basic information**:

- **Campaign name** — internal only; recipients never see it. Something like `Product update — July` keeps your list tidy.
- **Email subject** — the line that lands in the inbox. This is the single biggest lever on whether the email gets opened, so make it count (these [subject-line formulas](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work) are a good shortcut). Tip: type `{{` to drop in a merge tag like the subscriber's first name.
- **From name** and **From email** — who it appears to be from. The from address **must be a verified sender** — an unverified one will fail at send time.

![The campaign setup step with the subject line filled in](https://acellesend.com/plugins/acelle/landing/images/kb-bot/create-your-first-email-campaign/step-2-setup.png)
*Name the campaign, write your subject, and set the sender. Keep tracking on under Advanced Settings.*

On the right, **Advanced Settings** are sensible by default — leave **Track opens**, **Track clicks**, and **Enable DKIM signing** on. Opens and clicks are how you'll measure the campaign; DKIM signing helps it reach the inbox. There's also optional UTM tagging if you push traffic to a site you track.

## Step 3 — Design your email

Step 3, **"Design your email,"** is where the message takes shape.

Click **Template gallery** and pick a starting design — **Blank** for a clean slate, or a ready-made layout like **Minimal**, **Simple Text**, or **Featured Product**. Choosing one opens the drag-and-drop email builder, where you edit text, swap images, and add or remove blocks until it looks right. (For layout and styling that converts, see [email template design best practices](https://acellesend.com/kb/articles/email-template-design-best-practices).)

![The template gallery on the Design step](https://acellesend.com/plugins/acelle/landing/images/kb-bot/create-your-first-email-campaign/step-3-template.png)
*Choose a starter template, then customize it in the visual builder. You can also paste custom HTML or let AI draft one.*

Two shortcuts sit alongside the gallery: **Make template with AI** (describe your goal and it drafts a ready-to-edit design) and **Your Custom HTML** (paste your own code). For a first campaign, a gallery template is the fastest path.

> **Tip:** Always send yourself a test first. Before the real send, mail the campaign to your own address and read it on both desktop and phone — catch a broken image or an awkward subject *before* thousands of people do.

## Step 4 — Schedule

Step 4, **Schedule**, is where you decide *when* it goes out: **send immediately**, or **schedule** it for a specific date and time (useful for hitting a timezone or a Tuesday-morning slot). Pick whichever fits and continue.

## Step 5 — Confirm & send

The final step, **Confirm**, is a last review — your recipients, subject, sender, and design in one place. Check it over, and when everything looks right, send (or lock in the schedule). That's it — your first campaign is on its way.

## Troubleshooting

- **"Unverified sender" / the send won't start.** Your **From email** isn't a verified sender. Verify the sending domain/address first — walk through [SPF, DKIM, and DMARC](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).
- **"No recipients."** The list you chose is empty, or an exclude rule removed everyone. Confirm the list has active subscribers and re-check Step 1.
- **It landed in spam in your test.** Almost always sender authentication. Make sure DKIM signing is on (Step 2) and your domain's SPF/DKIM/DMARC are set.
- **Can't reach Step 4/5.** The wizard needs each earlier step complete — a missing subject or an unverified from-address will hold you on Setup until it's fixed.

## Recap

You just built a campaign end to end: chose the audience, set the subject and sender, designed the email, and scheduled the send. The five-step wizard means you never have to guess what comes next.

Once it's out, don't stop at "sent" — open the campaign's report to see opens, clicks, and who engaged, and use a [segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment) of your most active readers to make the next one even stronger.

---

# Return-Path vs Envelope-Sender — What Each Does in AcelleMail

> The "From" address recipients see isn't the address that handles bounces. That's the envelope-sender (also called the return-path or MAIL FROM). This guide separates the three sender concepts and shows where AcelleMail configures each.

- Canonical URL: https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-06-01 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The 3 sender concepts (different things)

Email has 3 "sender" addresses, often confused:

| Concept | Where it lives | Used by |
|---|---|---|
| **From: header** | Email body header | Displayed to recipient — what they see in the inbox |
| **Envelope-sender** (MAIL FROM, Return-Path) | SMTP envelope (before the message body) | Receiving servers — where bounces go back to |
| **Reply-To: header** | Email body header | Recipients clicking "Reply" — where their reply goes |

These can all be different addresses. Sometimes useful, sometimes confusing. Let's separate them.

## From: header (what recipients see)

`From: Brand Marketing <hello@brand.com>` in the email body. Recipients see "Brand Marketing" in the sender column. Inbox previews display this.

In AcelleMail: campaign settings → **From email** + **From name**. Per-campaign override of the sending server's default.

## Envelope-sender (where bounces go)

Set during SMTP transmission via `MAIL FROM:` command. NOT visible in the email body. Receivers use it for:

- Sending bounce messages back to (so AcelleMail can process them)
- SPF check (the sender domain's SPF record must authorize the sending IP)
- Reputation tracking (one of the inputs to per-domain reputation)

In AcelleMail: handled automatically by the sending-server config. Typically `bounce@yourdomain.com` or a vendor-rotated address like `bounce-12345@amazonses.com`.

Verify the envelope-sender by opening "Show original" in Gmail:

```
Return-Path: <bounce@yourdomain.com>
X-Original-Sender: bounce@yourdomain.com
```

This is the envelope-sender, NOT the From: header.

## Reply-To: header (where replies go)

`Reply-To: support@yourdomain.com`. When a recipient clicks "Reply," their reply goes to this address (not the From: address).

Useful for:

- Sending from `noreply@brand.com` but accepting replies at `support@brand.com`
- Routing replies to a different team/inbox per campaign

In AcelleMail: campaign settings → **Reply-to email**.
### Open the sending-domain detail

In AcelleMail's sidebar, **Sending → Sending domains**. The list shows every domain you've registered with status chips (Verified / Pending / Failed) and per-auth indicators:

![Sending domains list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into your domain row. The detail page surfaces exactly which DNS records to publish (TXT for SPF, CNAMEs for DKIM, TXT for DMARC) with copy-paste-ready values + current verification state per check:

![Sending-domain detail — DNS records + auth status](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

The sending-domain detail page shows the active envelope-sender pattern + verification status — confirming that the bounces will route correctly back to AcelleMail's processor.

## Why all 3 matter for deliverability

Receiving servers check alignment between these addresses + your DNS records:

| Check | Aligned when |
|---|---|
| **SPF** | Envelope-sender's domain has the sending IP in its SPF record |
| **DKIM** | The DKIM signature's `d=` parameter matches the From: domain |
| **DMARC alignment** | SPF and/or DKIM domain matches the From: domain |

Strict DMARC alignment = the bounce/envelope domain MUST match the From: domain (no subdomain leeway). Relaxed = subdomain leeway allowed.

If you send `From: <hello@brand.com>` but the envelope-sender is `bounce-xyz@amazonses.com`, DMARC may fail (envelope-sender domain `amazonses.com` doesn't align with From: domain `brand.com`).

The fix: use a custom Return-Path domain that aligns. AcelleMail's sending-server config → **MAIL FROM (Return-Path)** field — set to `bounce.brand.com` so the envelope-sender becomes `bounce@bounce.brand.com`, aligned with `brand.com`.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Bounces never come back to AcelleMail | Envelope-sender misconfigured (pointing elsewhere) | Sending server detail → MAIL FROM field |
| DMARC fails despite SPF/DKIM green | Envelope-sender domain doesn't align with From: | Set up custom return-path subdomain |
| Recipients reply but I never see it | Reply-To: pointing to dead address | Campaign settings → Reply-to email |
| From: shows weird formatting | Display name not set or quotes broken | Campaign settings → From name |
| Spam folder placement on B2B receivers | Strict DMARC alignment expected by some B2B filters | Set up aligned return-path domain |

## Common configurations

| Setup | Best for |
|---|---|
| From: = Envelope = Reply-To, all on `brand.com` | Simplest; strict DMARC alignment works automatically |
| From: `brand.com`, Envelope: `bounce.brand.com`, Reply-To: `support@brand.com` | Most common for marketing — aligned envelope subdomain handles bounces; replies go to support |
| From: `notifications@brand.com`, Reply-To: empty (suppress replies) | Transactional emails; "do not reply" pattern |
| From: `brand.com`, Envelope: `amazonses.com` | Default SES setup — DMARC alignment may need relaxed mode |

<details>
<summary><strong>Advanced: per-vendor envelope-sender mechanics + DMARC alignment math + multi-tenant return-paths</strong></summary>

**Per-vendor envelope-sender:**

| Vendor | Default envelope-sender | Customizable? |
|---|---|---|
| Amazon SES (API) | `bounces+xxx@amazonses.com` | Yes — set "MAIL FROM domain" in SES verified-identity config |
| SendGrid SMTP | `bounce+xxx@em.sendgrid.net` | Yes — Whitelabel feature (paid) |
| Mailgun | `bounce@mg.brand.com` | Yes — included in standard config |
| Postmark | `xxx@pm-bounces.brand.com` | Yes — included in standard config |
| Self-hosted Postfix | Whatever you configure in postfix's `myorigin` | Fully customizable |

For DMARC strict alignment, the envelope-sender domain must match (or be a subdomain of) the From: domain. Each vendor has a different way to set this:

- **SES**: Verify identities → custom MAIL FROM domain → publish MX record at `mail.brand.com`
- **SendGrid**: Whitelabel feature → reverse DNS + CNAME setup
- **Mailgun**: Auto-handled per their domain setup

**DMARC alignment math:**

```
DMARC pass requires AT LEAST ONE of:
  - SPF pass + From: domain matches MAIL FROM domain (or aligned subdomain)
  - DKIM pass + From: domain matches DKIM d= (or aligned subdomain)

Strict alignment = exact domain match (e.g. brand.com = brand.com)
Relaxed alignment = subdomain leeway (e.g. mail.brand.com aligns with brand.com)
```

Default DMARC is relaxed alignment. Some receivers (financial-services-side filters) enforce strict.

**Multi-tenant return-paths** (for SaaS operators):

Each customer needs their own envelope-sender subdomain:

```
Customer A: From: hello@customerA.com   Envelope: bounce@bounce.customerA.com
Customer B: From: hello@customerB.com   Envelope: bounce@bounce.customerB.com
```

Each customer adds the `bounce` CNAME at their DNS host pointing to your AcelleMail processor. AcelleMail's per-customer sending server config holds the right envelope-sender setting.

**Vendor-rotated envelope-sender** (default SES pattern):

```
bounces+abc123@amazonses.com
bounces+abc124@amazonses.com
bounces+abc125@amazonses.com  ...
```

The `+abcXXX` is a unique ID per outgoing message. SES uses it to route bounces to your account.

**SRS (Sender Rewriting Scheme):**

For forwarding scenarios — when you receive a message and forward it elsewhere. SRS rewrites the envelope-sender to maintain SPF authentication through the forward.

```
Original sender:  bob@external.com → SPF check passes for external.com
After SRS rewrite: srs0=abc=brand.com@external.com → SPF check passes for brand.com
```

Most modern SMTP relays handle SRS automatically. Verify on your sending server if you do forwarding.

**Reply-To: pitfalls:**

```
From: noreply@brand.com
Reply-To: noreply@brand.com  ← BAD! Recipients reply, mail bounces to noreply (likely no inbox)
```

Always set Reply-To: to a real-inbox address that someone reads.

```
From: noreply@brand.com
Reply-To: <empty>           ← OK — recipients can't reply via standard reply; explicit communication channel mentioned in body
```

</details>

## Related articles

- [Return-path and envelope-sender (quick setup)](https://acellesend.com/kb/articles/return-path-and-envelope-sender)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)

---

# How to Set Up Amazon SES with AcelleMail

> Create an IAM user and access key in AWS, connect Amazon SES to AcelleMail, share your verified domains with users, and let them verify their own.

- Canonical URL: https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Amazon SES, Bounce Handling, Acellemail
- Published: 2026-06-01 · Last updated: 2026-09-17
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

AcelleMail is a self-hosted email marketing application written in PHP and Laravel. It manages contacts, campaigns, automation, sending queues, and reporting, while the actual email delivery is handled by a separate sending service.

Amazon SES is one of the services that can be used for email delivery. AcelleMail supports both the Amazon SES API and SMTP. This guide focuses on the SES API because it also allows AcelleMail to manage sending identities and configure bounce and complaint notifications.

The setup has three main parts:

1. Create an AWS IAM user and access key.
2. Add Amazon SES as a sending server in AcelleMail.
3. Configure the sending domains and SES settings.

Once the sending server is connected, AcelleMail can also configure the SES resources it needs for bounce and complaint handling.

## 1. Create an AWS IAM user

AcelleMail needs AWS credentials to connect to Amazon SES.

It is better to create a dedicated IAM user for AcelleMail instead of using the AWS root account credentials. A dedicated user can be disabled or have its access keys rotated without affecting other AWS services.

In the AWS console, open **IAM**.

![Searching for IAM in the AWS console](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/01-open-iam.png)

Go to **IAM → Users** and click **Create user**.

You can use a name such as:

```text
acellemail-ses
```

The user does not need console access because AcelleMail will use its access key to access AWS.

![The IAM users list with the Create user button](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/02-iam-users-create-user.png)

### Attach the required permissions

On the permissions step, choose **Attach policies directly**.

Attach these three AWS managed policies:

| Policy | Used by AcelleMail for |
|---|---|
| `AmazonSESFullAccess` | Sending email and managing SES identities |
| `AmazonSNSFullAccess` | Creating and configuring the SNS resources used for bounce and complaint notifications |
| `IAMReadOnlyAccess` | Checking the AWS credentials and permissions when you use **Test Connection** |

![IAM user with AmazonSESFullAccess, AmazonSNSFullAccess and IAMReadOnlyAccess attached](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/03-user-permissions.png)

These policies provide broader permissions than a custom least-privilege policy. If the AWS account is dedicated to AcelleMail, this can be a practical setup. If the account also contains other production resources, consider creating a more restricted IAM policy instead.

### Create an access key

Open the IAM user's **Access keys** section and click **Create access key**.

![The Access keys section of the IAM user with the Create access key button](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/04-create-access-key.png)

When AWS asks what the key will be used for, choose **Third-party service**.

![Choosing Third-party service as the access key use case](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/05-third-party-service.png)

AWS will then show the **Access key** and **Secret access key**.

Save both values. AWS only shows the secret access key when it is created. If you lose it later, create a new key and deactivate the old one.

![The Retrieve access keys screen](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/06-access-key-created.png)

AcelleMail stores the AWS credentials in its database because it needs to access SES without user interaction. The AWS secret access key is encrypted at rest.

You should still use a dedicated IAM user, limit its permissions where practical, and rotate its access keys periodically.

## 2. Add Amazon SES to AcelleMail

Once you have the AWS access key, add it to AcelleMail.

Log in to AcelleMail as an administrator and open:

**Sending → Sending Servers**

![The admin dashboard with Sending Servers in the left menu](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/07-admin-sending-servers.png)

Click **Add Server** and select:

**Amazon Web Service API**

![The Choose Server Type window with Amazon Web Service API selected](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/08-choose-amazon-ses-api.png)

AcelleMail also supports **Amazon Web Service SMTP**. The SMTP option requires SES SMTP credentials, while the API driver uses the AWS access key directly.

For this setup, we recommend the **Amazon Web Service API** driver.

Under **Connection details**, enter:

- AWS Access Key ID
- AWS Secret Key
- AWS Region

The region is important because Amazon SES identities and quotas are regional.

![The New server form with AWS Access Key ID, AWS Secret Key and AWS region](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/09-new-server-form.png)

Click **Save Changes**.

### Test the connection

After saving the server, AcelleMail provides two useful tests.

**Test Connection** checks the AWS credentials and the permissions required by AcelleMail.

**Send Test Email** sends an actual email through Amazon SES.

Use **Test Connection** first. If it reports a permission problem, fix the IAM permissions before testing email delivery.

If the SES account is still in the sandbox, the test recipient must be a verified email address.

![The saved sending server showing Test Connection and Send Test Email](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/10-connection-saved.png)

If you are running AcelleMail with multiple users or plans, also make sure the sending server is assigned to the appropriate plan. A correctly configured sending server will not send anything if no plan or user is allowed to use it.

## 3. Bounce and complaint notifications

AcelleMail uses Amazon SNS to receive bounce and complaint notifications from SES.

When the SES sending server is connected, AcelleMail creates the required SNS topic and subscription automatically.

This allows AcelleMail to receive notifications from SES and associate them with the corresponding contacts. Contacts affected by hard bounces or complaints can then be excluded from later campaigns.

There is one important requirement: **your AcelleMail installation must be publicly accessible**.

SNS needs to send notifications to an endpoint on your AcelleMail installation. A localhost address, private IP address, or VPN-only hostname cannot receive these notifications from AWS.

Make sure `APP_URL` points to the public URL of your AcelleMail installation before enabling the SES server.

If this is not configured correctly, email sending may still work, but bounce and complaint notifications will not be processed.

## 4. Share verified sender identities

Every email sent through SES must use a verified sender identity.

If you have already verified domains in Amazon SES, you can manage them from the AcelleMail sending server:

**Advanced → Sender Identity**

The list shows the SES identities available in the selected region.

![Sender Identity rows with the Available for all checkbox and the Owner dropdown](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/11-sender-identity-assign.png)

You can make a verified domain available in two ways.

### Available for all

Enable **Available for all** if all users on the AcelleMail installation should be able to send from that domain.

### Assign to a user

Alternatively, set an **Owner** for the identity. The selected customer will then be able to use that domain.

Only identities with an **Active** status can be used for sending.

If an identity is marked **Failed**, the problem needs to be fixed in Amazon SES or DNS before it can be used.

Once a domain is available to a user, it appears in the campaign's **From email** field under **Verified senders**.

![The From email dropdown in a campaign showing a shared domain under Verified senders](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/12-from-email-in-campaign.png)

## 5. Add customer sending domains

In a multi-user AcelleMail installation, customers will usually want to send from their own domains.

A customer can add a domain from:

**Sending domains → Add domain**

![The Sending Domains page with the Add domain button](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/13-customer-add-domain.png)

When the customer's account uses the Amazon SES sending server, AcelleMail registers the domain with SES and displays the DNS records that need to be added.

The domain page shows a **Synced · Amazon SES** status and the required records.

![The DNS records AcelleMail shows for a domain synced with Amazon SES](https://acellesend.com/plugins/acelle/landing/images/help/kb/configuring-amazon-ses-with-acellemail/14-customer-domain-dns-records.png)

The records normally include:

- **Domain identity** — a TXT record at `_amazonses.theirdomain.com`
- **DKIM** — three CNAME records ending in `.dkim.amazonses.com`
- **SPF** — a TXT record containing `v=spf1 include:amazonses.com ~all`

After publishing the records, click **Verify DNS records** in AcelleMail.

DNS changes can take some time to propagate, so the verification can be run again later.

### Two common DNS problems

#### Do not create multiple SPF records

A domain should have one SPF record.

If the domain already has an SPF record for Google Workspace or another email service, add the Amazon SES include to the existing record instead of creating a second SPF record.

For example:

```text
v=spf1 include:google.com include:amazonses.com ~all
```

Multiple SPF records can result in a `permerror` and cause SPF validation to fail.

#### Check how your DNS provider handles record names

Some DNS providers automatically append the domain name to the host field.

For example, entering:

```text
_amazonses.theirdomain.com
```

may result in:

```text
_amazonses.theirdomain.com.theirdomain.com
```

If the SES identity remains in `Pending`, check the actual DNS record rather than relying only on what is shown in the DNS control panel.

You can check the TXT record with:

```bash
dig +short TXT _amazonses.theirdomain.com
```

And a DKIM record with:

```bash
dig +short CNAME <selector>._domainkey.theirdomain.com
```

## 6. SES sandbox, quotas and regions

There are three Amazon SES settings you should check before sending a real campaign.

### SES sandbox

New SES accounts start in the sandbox, and the restriction applies per region.

In the sandbox:

- recipients generally need to be verified
- the sending limit is 200 messages per 24 hours
- the sending rate is 1 message per second

To request production access, open the SES Account dashboard and choose **Request production access**.

The review is asynchronous, so it is worth requesting production access while you are configuring your domains and AcelleMail.

### Sending quotas

Production access does not mean that every AWS account has the same sending quota.

SES sets sending quotas per account and region. Check the current quota in the AWS Account dashboard and use that value when configuring sending limits in AcelleMail.

Do not assume that a commonly quoted quota such as 50,000 emails per day applies to your account.

### AWS regions

SES identities and sending quotas are regional.

For example, if you verified a domain in:

```text
us-east-1
```

but configured AcelleMail to use:

```text
eu-west-1
```

the verified identity will not appear in the AcelleMail server's Sender Identity list.

Make sure the region used by AcelleMail is the same region where your SES identities are configured.

## 7. Common problems

| What you see | What it usually means |
|---|---|
| `Action ses:... is not allowed` / `sns:...` | The IAM user is missing a required permission |
| `The security token included in the request is invalid` | The access key is incorrect, truncated, or deactivated |
| `Email address is not verified` | The SES account is still in the sandbox for that region |
| `AWS SNS will not be able to reach it…` | `APP_URL` is not publicly accessible |
| Sender Identity list is empty | The AcelleMail server region does not match the SES region |
| Customer domain remains `Pending` | The DNS record may have been entered incorrectly or DNS has not propagated |
| `This domain is already registered on the system sending server…` | The domain was already registered in SES and needs to be assigned through Sender Identity |

## Summary

To connect AcelleMail to Amazon SES:

1. Create a dedicated AWS IAM user.
2. Give it the permissions required by AcelleMail.
3. Create an AWS access key.
4. Add the credentials to an **Amazon Web Service API** sending server in AcelleMail.
5. Use **Test Connection** before sending a campaign.
6. Make sure `APP_URL` is publicly accessible so SNS can deliver bounce and complaint notifications.
7. Verify your sending domains and publish the required DNS records.
8. Keep the SES region consistent between AWS and AcelleMail.
9. Check your actual SES sending quota before configuring sending limits.

---

# Community Spotlight: Business Case Studies

> Three composite case studies from the AcelleMail operator community — a digital agency that replaced Mailchimp for 40 clients, an e-commerce store that built a high-converting abandoned-cart sequence, and a B2B SaaS that wired AcelleMail to product-usage data for retention emails. Anonymized + plausible-pattern, with the architecture decisions and the operational realities behind the numbers.

- Canonical URL: https://acellesend.com/kb/articles/community-spotlight-business-case-studies
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: AcelleMail Updates — Release notes, changelogs, new features, roadmap, community news
- Content type: case-study · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-05-29 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Three **composite case studies** from the AcelleMail operator community. Each is built from patterns we've seen repeatedly across real operator implementations — anonymised + reshaped for clarity. The numbers are realistic for the described scenarios, not specific to any one operator.

Use these as **architecture templates** when you're designing something similar. Each section ends with what worked + what we'd do differently.

## Story 1 — Digital agency replaces Mailchimp for 40 clients

**Context:** A 12-person digital marketing agency in Southeast Asia was managing email marketing for 40 SMB clients on individual Mailchimp accounts. Total Mailchimp spend across all client accounts: ~$2,000/month. Each client had its own dashboard login, its own templates, its own sender domain.

**Problem:** The agency wanted to:

1. Consolidate operational tools (one dashboard instead of 40 logins)
2. Stop paying Mailchimp's per-contact pricing as client lists grew
3. White-label the platform under the agency's brand so clients see "AgencyMail" not "Mailchimp"

**Solution:** A single AcelleMail install on a 4 vCPU / 8 GB DigitalOcean droplet, configured per the [SaaS setup guide](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform):

- Each client = one AcelleMail customer (tenant)
- Each client gets a sub-domain (e.g. `client-acme.agency-mail.com`) via [white-label custom domain](https://acellesend.com/kb/articles/white-label-customization-guide#step-3--custom-domain-per-tenant-or-app-wide)
- All clients share an Amazon SES sending account (free in-region egress)
- Agency staff log into the central admin to manage everyone
- Clients login at their own sub-domain to a fully-branded interface

**Architecture:**

```
                    [agency-mail.com Admin]
                            ↓
                  [AcelleMail single install]
                  ┌─────────┼─────────┐
                  ↓         ↓         ↓
       [client1.acme]  [client2.xyz] [...] 40 customer tenants
                      ↓
                    [Shared Amazon SES]
```

**Numbers:**

| Line item | Before (Mailchimp) | After (AcelleMail) |
|---|---|---|
| Software/SaaS | $2,000/mo | $0 (one-time $199 Extended License) |
| Hosting | (included) | $50/mo (DO 4 vCPU / 8 GB) |
| Sending (~3M total/mo across all clients) | (included) | $300/mo (SES at $0.10/1k) |
| Operations overhead | ~5 hrs/wk (managing 40 logins) | ~8 hrs/wk (managing one install + 40 clients) |
| **Total monthly cost** | **$2,000** | **$350** |

Annual savings: ~$20,000. After 1 year the agency was reselling email marketing as a productised service (charging clients flat $50/mo for it instead of cost-plus), creating a new revenue line.

**What worked:**

- Single SES account with per-customer config — simpler than per-client SES accounts
- Custom domain per client made the white-labelling feel premium
- Centralised template library — agency built 10 templates once, every client benefited

**What we'd do differently:**

- Should have set up per-client suppression list isolation earlier — one client's bad list contaminated shared reputation
- Should have implemented per-customer sending throttles from day 1 (added later via plan-level rate limits)
- Should have invested in customer-facing docs earlier — the agency's support load was high in months 1-3

**Reference articles:** [Setting Up as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) · [White-Label Customization](https://acellesend.com/kb/articles/white-label-customization-guide) · [Configuring Amazon SES](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail)

## Story 2 — E-commerce store builds high-converting abandoned-cart automation

**Context:** A mid-size WooCommerce store (45,000 newsletter subscribers, ~600 orders/week) needed a better abandoned-cart recovery flow. They were using WooCommerce's default "abandoned cart email" plugin which sent one email at 24 hours. Recovery rate: ~5%.

**Problem:** The 24-hour single-email sequence was leaving money on the table. Industry research suggested 2-3 emails with smart timing + escalating offer could lift recovery rate to 15-20%.

**Solution:** Wired AcelleMail to WooCommerce via the [WC integration pattern](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce), built a 3-email sequence per the [post-purchase emails framework](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails) (adapted for pre-purchase).

**The sequence:**

| Email | Timing | Offer | Goal |
|---|---|---|---|
| 1. Reminder | 1 hour after cart abandoned | None | "Hey, you left this in your cart — finish whenever" |
| 2. Social proof | 24 hours | None | Customer review of the abandoned product + use case |
| 3. Discount | 48 hours | 10% off code | Last-chance incentive; expires in 48 hours |

**Trigger logic** (in WooCommerce, custom mu-plugin):

- WooCommerce cart contents persist for logged-in users (1 hour idle = abandonment)
- For guests, cart is captured at checkout-email-entry (`woocommerce_after_checkout_form` hook)
- Plugin sends abandonment event to AcelleMail with cart product details, total, and discount code (pre-generated per-customer to enable single-use)

**Results after 90 days:**

- **Cart recovery rate: 18%** (vs 5% baseline)
- **At 45k subscribers and ~25% cart abandonment rate** = ~7,200 abandoned carts/quarter
- **18% × 7,200 carts × ~$30 avg order** = ~$39,000 recovered revenue/quarter
- **Cost:** $50/mo AcelleMail hosting + ~10 hours setup time
- **Payback: < 1 week**

**What worked:**

- Real-time webhook trigger (not batch poll) — keeps the 1-hour timing precise
- Single-use discount codes — prevents customers from manually re-applying after expiry
- Plain-text-styled email 1 — felt personal, not promotional; outperformed pretty design

**What we'd do differently:**

- Should have segmented by cart value earlier — $50 carts and $500 carts deserve different sequences
- Should have A/B tested email 3's offer (10% vs 15% vs free shipping) — the 10% was a guess that worked but might not be optimal
- Built more granular suppression — customers who completed checkout AFTER email 1 still got emails 2+3 occasionally

**Reference articles:** [Creating an Abandoned Cart Sequence](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence) · [Integrating with WordPress + WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) · [WooCommerce Post-Purchase Emails](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails)

## Story 3 — B2B SaaS builds onboarding sequence triggered by product usage

**Context:** A B2B SaaS (project-management tool, ~3,000 active customers, $99/mo average) had ~40% of trial users converting to paid. Once paid, retention at 90 days was ~80% — but at 12 months had dropped to 45%. The team suspected the 90-day → 12-month dropoff was driven by users who never reached "habit-forming" feature use.

**Problem:** Generic email onboarding (welcome → setup → feature tour) wasn't differentiating between users who needed which next-step. The team wanted to send the *right* email at the *right* time based on what each user had actually done in-product.

**Solution:** Wired the product's event stream (Segment.io was already in place) to AcelleMail via [API triggers](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows). Each meaningful in-product event ("first project created", "first task assigned", "first comment", "first integration connected") fired an AcelleMail API call to advance the customer in a personalised onboarding flow.

**The 14-email sequence** (high-level — emails branch based on what happens or doesn't):

| Stage | Trigger | Email content |
|---|---|---|
| Day 0 | Signup | Welcome + setup link |
| Day 0+15min | (If logged in) | Dashboard tour |
| Day 1 | First project created | "Great start — here's how to invite teammates" |
| Day 1 | NO first project after 24h | "Need help getting started?" + Calendly link |
| Day 3 | First task assigned | "How to use comments + @mentions" |
| Day 3 | NO task yet | "Common first-week patterns" (lower-friction examples) |
| Day 7 | First integration connected | "Power-user tip: workflow X" |
| Day 7 | NO integration yet | "5 integrations our customers love most" |
| ... | (10 more conditional steps) | ... |

**Architecture:** Segment.io → Zapier (Catch Hook on Segment event) → POST to `/api/v1/automations/{uid}/api/call` per [Zapier integration guide](https://acellesend.com/kb/articles/zapier-integration-guide).

**Results after 6 months:**

- **Users who completed full sequence: 3.4× higher 12-month retention** than control (37% baseline → 51% for sequence completers)
- **Total cohort lift: 8 percentage points** in 12-month retention across all paid users
- **Revenue impact:** at $99/mo × 3000 customers × 8% retention lift × 12 months ≈ **$285k incremental annual revenue**
- **Cost:** $50/mo AcelleMail + $40/mo Zapier (paid tier needed for multi-step Zaps) + ~30 hours setup

**What worked:**

- Branching on "did the event happen yet?" was high-leverage — users who hadn't done X got materially different content from users who had
- Plain-text emails outperformed HTML for the highest-friction step (Day 1 "did you get stuck?")
- Sending from the actual founder's email address (`alice@company.com`) tripled response rate on emails asking for replies

**What we'd do differently:**

- Should have invested in BIG-picture customer-journey mapping before writing emails — wrote some emails that turned out to be redundant
- Should have measured "feature adoption" not just "retention" — adoption was the leading indicator
- Should have built the unsubscribe / pause logic into the automation from day 1 — angry trial-ending customers were getting onboarding emails

**Reference articles:** [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) · [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) · [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) · [Lead Scoring with Email Automation](https://acellesend.com/kb/articles/lead-scoring-with-email-automation)

## Common patterns across all three

Looking at these three cases, the operational similarities:

- **Each replaced an inferior tool** (Mailchimp's pricing tier, WC's default abandoned-cart, generic onboarding) rather than building net-new
- **Each had clear metric movement** in the first 3 months — not "we're learning"
- **Each invested 10-30 hours of setup time** for a payback measured in months
- **None used AI / fancy ML features** — the wins came from sending the RIGHT email at the RIGHT moment, not from clever content

If you're considering a similar build: start with the metric you want to move. Pick the automation that pushes that metric directly. Resist the urge to build many at once.

## What we'd love to feature next

Operating an AcelleMail instance and willing to share what you built? Reach out via CodeCanyon support — the team gathers operator stories for future case-study articles. Stories that get featured:

- Specific metric movements (not just "it was nice")
- Architecture decisions that surprised even you
- Failures + lessons learned alongside wins

Anonymity respected on request.

## Related articles

- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — for Story 1's architecture
- [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas) — pricing + GTM for the SaaS model
- [Creating an Abandoned Cart Email Sequence](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence) — for Story 2's automation template
- [Building a Welcome Email Series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) — onboarding sequence template
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — for Story 3's event-driven flow
- [Integrating AcelleMail with WordPress and WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) — Story 2's foundation
- [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) — Story 3's bridge from Segment
- [Lead Scoring with Email Automation](https://acellesend.com/kb/articles/lead-scoring-with-email-automation) — concept used by Story 3's branching
- [Re-engagement Campaigns: Win Back Inactive Subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers) — complementary to onboarding

---

# How to create a subscription plan for your customers

> Package what your customers get and pay for. Create a subscription plan in the admin panel — set its price, credits, quotas, and features — and make it available to customers at signup.

- Canonical URL: https://acellesend.com/kb/articles/creating-subscription-plans-for-your-customers
- Author: Martin Keller, SaaS & Business Editor
- Category: SaaS & Multi-tenant — White-label, subscription plans, billing, tenant management, branding
- Content type: tutorial · Difficulty: intermediate
- Tags: Multi Tenant
- Published: 2026-05-26 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

If you run the platform as a SaaS — selling email marketing to your own customers — **subscription plans** are how you package what a customer gets and what they pay. A plan sets the price on the outside and the sending limits on the inside, so a new signup is provisioned automatically the moment they choose it.

This guide walks you through creating a plan from scratch in the admin panel, end to end, so a customer can sign up and start sending in minutes.

**Who this is for:** platform administrators running a multi-tenant SaaS (you sell accounts to customers). You'll work entirely in the **Admin** panel.

## Before you start

- **Admin access** — you'll use the admin sidebar under **Plans & Billing → Plans**.
- **A currency enabled** — Admin → **Currencies**. For a paid plan, the price is charged in one of your enabled currencies.
- **A payment gateway** (paid plans only) — Admin → **Payment Gateways**, so customers can actually be charged.
- **A sending server** — Admin → **Sending Servers**, so the plan has delivery infrastructure to assign.

## Step 1 — Open the Plans screen

In the admin sidebar, go to **Plans & Billing → Plans**. This screen lists every plan you offer with its price, its sending-credit allowance, and an active toggle. Click **Create Plan** in the top-right corner.

![The Plans screen](https://acellesend.com/plugins/acelle/landing/images/kb-bot/creating-subscription-plans-for-your-customers/step-1-plans-list.png)
*The Plans screen lists every plan with its price, sending-credit allowance, and an active toggle. **Create Plan** is in the top-right corner.*

## Step 2 — Set the plan basics

The **Create New Plan** window asks for the outward-facing details — the things a customer sees when they pick a plan:

- **Plan Name** — what customers see (e.g. *Starter*, *Growth*, *Agency*).
- **Description** — one line on who the plan is for.
- **Price + Currency** — what they pay per cycle. Enter **0** for a free plan.
- **Billing Cycle** — *Monthly* or *Yearly*.
- **Enable free trial period** — give new customers a number of days before the first charge.
- **No payment method required when free** — skip card entry at checkout when the price is $0.

Fill in the name, price, and currency (the rest can stay at their defaults for now), then click **Create Plan**.

![The Create New Plan window](https://acellesend.com/plugins/acelle/landing/images/kb-bot/creating-subscription-plans-for-your-customers/step-2-create-modal.png)
*The plan basics — name, description, price and currency, billing cycle, and an optional free trial. Everything else is configured after you create the plan.*

> **Tip:** You can change any of these later. Creating the plan first just gives you something to configure — which is exactly what the next step does.

## Step 3 — Configure what the plan includes

After you create it, you land on the plan's settings, organised into tabs down the left. This is where a plan stops being a price tag and becomes a real product tier:

- **General** — the name, price, and billing cycle you just set.
- **Pricing Options** — a base price plus optional paid add-ons (for example, a premium-support tier).
- **Credits** — the per-cycle sending (and verification) **credits** — the headline *"20,000 emails / month"* number a customer sees.
- **Quotas** — hard resource caps: how many **lists, campaigns, and subscribers** this plan allows.
- **Entitlements** — on/off feature flags: may this customer use their **own sending server**, **remove your branding**, and so on.
- **Rate Limits** — a throttle (for example, emails per minute) that protects your shared infrastructure from any one customer.
- **Sending Servers** — which delivery infrastructure this plan's mail is sent through.

Set each tab to match the tier you're selling, then click **Save Changes**.

![The plan settings tabs](https://acellesend.com/plugins/acelle/landing/images/kb-bot/creating-subscription-plans-for-your-customers/step-3-configure.png)
*Credits, Quotas, Entitlements, Rate Limits, and Sending Servers each control part of what the plan includes. Set them to match the tier, then **Save Changes**.*

## Step 4 — Make the plan available

Back on the **Plans** list, the toggle next to each plan controls whether it's **active and visible** to customers at signup. Flip it on. From now on, a customer who chooses this plan is automatically given exactly the credits, quotas, and features you set — no manual provisioning.

## Troubleshooting

- **The plan doesn't appear at signup** → check the active/visible toggle on the Plans list. A plan that's toggled off is hidden from customers.
- **A customer can't be charged** → confirm a payment gateway is set up (Admin → Payment Gateways) and that the plan's currency is one that gateway supports.
- **A free plan still asks for a card** → turn on **No payment method required when free** in the plan basics (Step 2).
- **A customer hits limits too soon** → revisit the plan's **Credits**, **Quotas**, and **Rate Limits** tabs — one of those caps is lower than the tier should allow.

## Recap

You've created a subscription plan, set its price and billing cycle, configured its credits, quotas, features, and delivery, and made it available to customers. Every new signup on this plan is now provisioned automatically.

Next, decide how to price your tiers in [Subscription plan design strategy](https://acellesend.com/kb/articles/subscription-plan-design-strategy), and assign delivery infrastructure per plan in [Choosing a sending server for a plan](https://acellesend.com/kb/articles/sending-server-for-plan).

---

# White-Label Customization Guide for AcelleMail

> AcelleMail's Extended License allows full re-branding — every "AcelleMail" reference can be replaced with your brand name, every screen recoloured, every customer email sent from your domain. This guide walks every customisation point: branding settings, custom CSS, custom domain + TLS, transactional email templates, and the upgrade-safe pattern for layout/Blade overrides.

- Canonical URL: https://acellesend.com/kb/articles/white-label-customization-guide
- Author: Martin Keller, SaaS & Business Editor
- Category: SaaS & Multi-tenant — White-label, subscription plans, billing, tenant management, branding
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-25 · Last updated: 2026-08-15
- Reading time: 11 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail's [Extended License](https://codecanyon.net/item/acellemail-self-hosted-email-marketing-application/10149682) allows **full re-branding** — every "AcelleMail" reference can be replaced with your brand name, every screen recoloured, every customer email sent from your domain. The Regular License doesn't allow this.

This guide walks every customisation point in the order you'd typically apply them, ending with the upgrade-safe pattern for deeper Blade/CSS overrides.

**Before you start:** make a clone of your install (rsync to a staging server) and run customisations against the clone first. Some changes are subtle enough that you'll want to compare side-by-side with the original to confirm nothing broke.

**Prerequisite:** the [SaaS setup guide](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) is the typical context for white-labelling. You can also white-label a single-tenant install if you want a custom internal tool.

## Step 1 — Branding settings (covers ~80% of the work)

These four settings cascade to every screen + every system email:

In **Admin → Settings → General → Branding**:

| Field | Format | Where it shows |
|---|---|---|
| **App Name** | Plain text, ≤ 60 chars | Browser tab, navbar logo (if no logo image), email subject prefixes, "Powered by..." in system emails |
| **Logo** | PNG or SVG, recommended ≤ 200×60px transparent | Top-left of every authenticated page; in email headers (if email template references it) |
| **Logo Light (for dark backgrounds)** | PNG or SVG | Email header on dark themes; login page if dark mode |
| **Favicon** | 32×32 ICO or PNG | Browser tab icon |
| **Primary Color** | Hex (e.g. `#0066CC`) | Button backgrounds, link color, active-state highlights, email CTA button color |
| **Secondary Color** | Hex | Sub-actions, secondary buttons |
| **Email Footer Text** | Plain text | Bottom of every customer-facing system email (welcome, password reset, billing receipt, campaign-status notifications) |

Save → reload the admin to confirm. Then log in as a test customer to verify the customer-facing screens picked up the changes (some are cached for 5 min).

**Logo tips:**

- Use SVG when possible — scales perfectly at every resolution.
- For PNG, export at 2× the displayed size (e.g. 400×120 for a 200×60 slot) to look crisp on retina displays.
- Light variant: needed if your primary logo is a dark color and your email templates use a dark header.
- Test the favicon at small size — letterforms that look fine at 200px can be illegible at 32×32.

## Step 2 — System emails (transactional)

The system-email templates control what your customers see when AcelleMail sends them welcome / password-reset / verification / billing emails. **These are seen by every paying customer in their first 5 minutes** — they're the highest-leverage white-label work.

In **Admin → System Emails**:

| Template | When sent | Customisation priority |
|---|---|---|
| Welcome | After successful signup + email verification | **High** — first impression |
| Email Verification | Right after signup | **High** — must look legit, not phishy |
| Password Reset | On "forgot password" | Medium |
| Subscription Created | After Stripe checkout success | **High** — confirms billing worked |
| Subscription Cancelled | On cancellation | Medium — opportunity for re-engagement |
| Payment Failed | After failed renewal attempt | **High** — dunning |
| Plan Upgrade / Downgrade | On plan change | Medium |
| Campaign Status (Sent / Paused / etc.) | When customer's campaign changes status | Low |

For each template:

- Subject line: write in your brand voice; mention your app name explicitly
- Body: use the WYSIWYG to set header logo + footer
- **Test by sending to yourself.** Most issues are visible only in the rendered email, not the editor preview.

The system emails support these **merge tags** (insert at appropriate spots):

```
{NAME}              — customer's name
{EMAIL}             — customer's email
{APP_NAME}          — your branded app name (from Step 1)
{ADMIN_EMAIL}       — your admin email
{LOGIN_URL}         — link to login page
{VERIFICATION_URL}  — for the verification email
{RESET_URL}         — for the password-reset email
{PLAN_NAME}         — current plan name
{CANCEL_URL}        — link to cancel subscription
```

## Step 3 — Custom domain (per-tenant or app-wide)

There are **two distinct custom-domain features** in AcelleMail. Don't mix them up:

### 3a. Your white-label app domain (you operate)

Your SaaS app should live on **your** domain, not `app.acellemail.com`. If you've followed the [Ubuntu install guide](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404), your domain is already configured (the `server_name` in the nginx vhost). Verify it shows correctly in every email — Acelle uses `APP_URL` from `.env` for absolute links:

```bash
grep -E '^APP_URL' /var/www/acellemail/.env
# Should show: APP_URL=https://your-brand.com
```

If wrong, edit it and run `php artisan config:clear`.

### 3b. Per-customer custom domain (their customers)

Each paying customer can set up their own custom **tracking domain** (e.g. `click.theircompany.com`) — so tracking links in their campaigns show their brand, not yours. Enabled per plan via the `custom_domain_enabled` option (see [SaaS setup Step 3](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform#step-3--design-plans)).

The customer-facing flow:

1. Customer enters their desired subdomain in **Settings → Custom Tracking Domain**
2. AcelleMail tells them: "Add a CNAME from `click.theircompany.com` to `track.your-brand.com`"
3. Customer does it at their DNS provider; clicks Verify
4. AcelleMail attempts to provision a Let's Encrypt TLS cert (auto, via certbot)
5. On success, their campaign tracking links use `click.theircompany.com`

**Operational caveats:**

- Let's Encrypt rate-limits — at scale (100s of new custom domains/day) you may need a DNS-challenge setup or a wildcard. For Pro tier, single-domain HTTP-challenge is usually fine.
- If certbot fails (DNS not propagated, port 80 blocked), the customer sees a "Verifying..." spinner indefinitely. Add a status page or admin notification for this.

## Step 4 — Custom CSS (visual polish beyond Branding)

The Branding settings cover most visual customisation, but Acelle's Blade templates also support a **custom CSS file** for finer control.

Drop a `custom.css` file in `/var/www/acellemail/public/css/custom.css`. AcelleMail automatically loads it on every page (search `app.blade.php` for the `<link>` include). Target Acelle's existing classes:

```css
/* Tighter navbar */
.navbar-main { padding: 8px 16px; }

/* Custom button shape */
.btn-primary { border-radius: 24px; }

/* Hide "Powered by AcelleMail" text in footer (Extended License only) */
.app-footer .powered-by { display: none; }

/* Email preview header — match your brand */
.email-preview-header { background: linear-gradient(135deg, #0066CC, #6633CC); }
```

After editing, hard-refresh the admin (Ctrl+Shift+R / Cmd+Shift+R) to bypass browser cache.

**For email-template CSS** (different from app CSS), edit the system-email templates directly in **Admin → System Emails** — inline styles per the email-rendering rules in [Understanding Email Client Rendering Differences](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences).

## Step 5 — Upgrade-safe Blade overrides

When the Branding settings + custom.css + system-email templates aren't enough, you can override Acelle's Blade views. The trick is doing it in a way that survives `patch-x.x.x.bin` upgrades.

**Wrong way** (will get overwritten by every upgrade):

```bash
# Don't do this:
vi /var/www/acellemail/resources/views/auth/login.blade.php
```

**Right way** — publish to a vendor-specific override directory:

```bash
mkdir -p /var/www/acellemail/resources/views/vendor/overrides
# Copy the file you want to override
cp /var/www/acellemail/resources/views/auth/login.blade.php \
   /var/www/acellemail/resources/views/vendor/overrides/auth-login.blade.php
# Edit the copy
vi /var/www/acellemail/resources/views/vendor/overrides/auth-login.blade.php
```

Then in your custom `AppServiceProvider` (or by patching the views finder), tell Laravel to look at the overrides directory first:

```php
// In a custom service provider's boot()
$this->loadViewsFrom(
    resource_path('views/vendor/overrides'),
    'overrides'
);

// And then in routes/web.php (or a router override), point the login route at:
Route::get('/login', fn () => view('overrides::auth-login'));
```

**Document every override** in a `OVERRIDES.md` file at the repo root:

```markdown
- /resources/views/vendor/overrides/auth-login.blade.php
  - Replaces /resources/views/auth/login.blade.php
  - Changes: removed "AcelleMail" wordmark, added custom social-proof block
  - Last reviewed against Acelle vN.N.N: 2026-05-16
```

When AcelleMail releases a new version, diff `auth/login.blade.php` against your override and re-apply any structural changes. Painful but unavoidable for deep customisations.

## Step 6 — Removing "Powered by AcelleMail" mentions

Under the Extended License, you can remove every reference. The places to check:

- ✅ App footer — Branding setting (Step 1)
- ✅ Email footer — Branding setting (Step 1) + System Email templates (Step 2)
- ✅ Page titles — Branding's App Name
- ⚠️ HTML `<meta name="generator">` — search `resources/views/layouts/app.blade.php`
- ⚠️ Email `X-Mailer` header — search `config/mail.php` and `app/Library/Mailer/`
- ⚠️ `.htaccess` / `robots.txt` comments — search for "AcelleMail"
- ⚠️ Any inline help links pointing to `acellemail.com/docs` — replace with your own KB

The `⚠️` items aren't user-visible to your customers but show up in source-view / email-headers / WHOIS-style checks. Audit twice if you're competing with AcelleMail-flavoured competitors.

```bash
# Quick audit grep:
sudo grep -rEi "acelle[a-z]*\\.com|powered.by.acelle" /var/www/acellemail/{resources,config,public,app} \
  | grep -v '/vendor/' | head -40
```

## Step 7 — Browser tests

After all of the above, run through this checklist:

- Open an incognito window → does the login page show your brand only?
- Sign up as a test customer → does the welcome email look like it came from your company?
- Verify the email → does the verification email look right?
- Log in → does the dashboard navbar / footer / colors all match?
- Trigger a password reset → does the reset email look right?
- Cancel the test subscription → does the cancellation email look right?
- View page source on dashboard → search for "acelle" — nothing should appear in user-visible text
- View email headers — `X-Mailer` should not say AcelleMail

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| New logo not appearing | Browser cache | Hard-refresh (Ctrl+Shift+R); also clear AcelleMail cache: `php artisan cache:clear` |
| Email logo broken / shows alt text | Logo URL is relative; emails need absolute URL | Ensure `APP_URL` in `.env` is set to `https://your-brand.com`; re-clear config cache |
| Custom domain TLS provisioning hangs | Customer's DNS not propagated, or port 80 blocked | Check DNS: `dig +short click.theircompany.com`; check certbot logs at `/var/log/letsencrypt/letsencrypt.log` |
| Blade override not taking effect | `loadViewsFrom` not registered, or wrong namespace | Verify by `php artisan view:cache` and re-test; check `resource_path('views/vendor/overrides')` exists |
| Brand color not applied to buttons | Primary Color setting cached | Clear cache + hard-refresh; check the rendered `<button>` for the inline style |
| Email subject still shows "AcelleMail" | System email template not edited | Admin → System Emails → edit each template's subject line |
| `php artisan` after override changes fails | Custom service provider has a syntax error | Run `php -l` on the provider file; fix and re-run |

## FAQ

**Can I sell AcelleMail-derived code (not the SaaS) under my own license?** No. The Extended License lets you operate AcelleMail as a SaaS under your brand — it does not grant redistribution rights for the source code. If a customer wants on-prem, they buy their own AcelleMail license.

**Can I remove the AcelleMail footer entirely?** Yes — per the Extended License terms. The Regular License requires a "Powered by AcelleMail" link in the footer.

**What about the Acelle logo in marketing emails sent via my SaaS?** Customer-facing marketing emails (campaigns your customers send to *their* subscribers) don't have any AcelleMail branding by default — they're rendered from the customer's chosen template. No action needed unless the customer's template references AcelleMail.

**How do I theme dark mode?** AcelleMail's admin has a dark-mode toggle. Custom CSS can target `.theme-dark .your-class` for dark-mode-only rules. The Logo Light setting (Step 1) is for the dark theme.

**Can I use a Tailwind-style design system?** Yes via the `custom.css` route — but be aware that AcelleMail's existing Blade markup uses Bootstrap-flavoured class names. You'll either layer Tailwind alongside (cohabitation is fine) or replace existing classes via Blade overrides (Step 5). For most SaaS operators, the cohabitation route is enough.

**What about right-to-left (RTL) language support?** AcelleMail's UI has RTL support built-in. Set the language in Admin → Languages, and the layout flips. Custom CSS may need `[dir="rtl"]` selectors for any layout tweaks you've added.

**How often does the underlying AcelleMail UI change?** Minor (point) releases rarely touch user-visible HTML; major releases (e.g. v4 → v5) may. Plan to re-test your white-label after every major upgrade — budget 2–4 hours.

**Multi-brand from a single install?** Acelle doesn't natively support per-tenant theming (only per-customer custom domain). For multi-brand SaaS (you run two separate brands off one install), you'll need either separate AcelleMail installs (cleaner) or a Blade override that switches branding based on the requesting hostname (hacky, harder to upgrade).

## Related articles

- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — the technical SaaS setup (white-labelling sits inside this)
- [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas) — GTM / pricing context
- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — your `APP_URL` is set here
- [Understanding Email Client Rendering Differences](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences) — how the system-email templates will render across clients
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — for system-email sending
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — applies to white-labelled installs too
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — required reading
- [Email Preheader Text — The Hidden Conversion Booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster) — for the customer-facing system emails

---

# Lead Scoring with AcelleMail Automation — Score, Tag, and Route

> Add points when a subscriber opens a campaign, clicks a key link, or fills a form. Route high-score subscribers to a sales-handoff tag automatically. This guide walks the AcelleMail automation builder + custom-field scoring, with API-driven scoring patterns for ecommerce + CRM integrations in the collapsible section.

- Canonical URL: https://acellesend.com/kb/articles/lead-scoring-with-email-automation
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-25 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What lead scoring is (and isn't)

Lead scoring is a number on each subscriber that tracks how engaged they are with you. It starts at 0; you bump it up when they do something positive (opened, clicked a buy-link, submitted a form), down when they go quiet (no opens for 60 days, unsubscribed from a list).

When the score crosses a threshold, you route them — to a sales tag for a CSM to call, into a higher-frequency cadence for prospect nurture, into a re-engagement automation for a "we miss you" win-back.

The model is simple. The setup in AcelleMail is straightforward. The discipline part — picking the right point values and the right thresholds — that's the marketing call.

## Setup in three pieces

### Piece 1: Add a `lead_score` field to your list

**Audience → [your list] → Fields → New field**. Configure:

- **Tag (key)**: `lead_score`
- **Type**: Number
- **Default value**: `0`
- **Required at signup**: No

This adds a `{{ subscriber.lead_score }}` merge tag and a per-subscriber numeric value AcelleMail will track + update via automation.

### Piece 2: Build automations that bump the score

### Open the visual automation builder

In AcelleMail's left sidebar, click **Automation** → **Automations**. The index lists every automation in this account with its current state:

![Automations index](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/01-automations-index.png)

Click **New automation** in the top-right toolbar. The trigger picker opens:

![Trigger picker — pick what starts the automation](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)


This first automation tracks "opened a campaign":

1. **Trigger**: pick **Email opened** from the trigger picker
2. **Condition** (optional): scope to a specific campaign or "any campaign"
3. **Action**: **Update subscriber field** → `lead_score = lead_score + 5`

Save. The flow now runs every time an open is recorded:

![Automation flow — open → +5 score](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/03-flow-builder.png)

Repeat the pattern for higher-value events. Common point values:

| Event | Point bump | Trigger to use |
|---|---|---|
| Email opened | +5 | Email opened |
| Clicked any link | +10 | Link clicked (no specific link) |
| Clicked a buy/upgrade link | +25 | Link clicked with URL filter on `/pricing` or `/upgrade` |
| Submitted a form | +30 | Subscribed to list (form's target list) |
| Visited pricing page (via tracker pixel) | +15 | Custom event from your site |
| Opened from sequential campaigns 3+ times in 7 days | +10 bonus | Compound conditional (see Advanced) |

### Piece 3: Build the routing automation

Once score is being tracked, add a *routing* automation that watches for threshold crossings.

1. **Trigger**: **Field value change** on `lead_score`
2. **Condition**: `lead_score >= 100`
3. **Action**: **Add tag** → `sales-ready`

When you add `sales-ready` to someone, you can either:

- Email your sales team via a separate automation triggered on tag addition
- Webhook the lead into your CRM via the **Send webhook** action
- Add to a high-touch nurture list

### Verify in the automation index

The Automations index lists all running flows + their last-run timestamp + total runs:

![Automation list — see all flows](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/01-automations-index.png)

Click any automation to see its run history + per-subscriber path:

![Per-flow run details](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/02-automation-flow.png)

The settings tab shows the configuration + per-run debugging:

![Automation settings](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/03-automation-settings.png)

## Score values that actually mean something

Don't over-engineer the point values. Start coarse:

| Tier | Score | Action |
|---|---|---|
| Cold | 0-25 | Default cadence, low frequency |
| Warming | 26-75 | Bumped to standard cadence + nurture sequence |
| Hot | 76-150 | Sales-ready tag + CRM handoff |
| Champion | 150+ | High-touch / CSM outreach |

Adjust quarterly based on what actually correlates with conversion in your funnel. The most useful question to ask each quarter: "of last quarter's converted customers, what was their score 30 days before they converted?" That's your real threshold; ignore the round numbers.

## Common pitfalls + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Scores all stuck at 0 | The "field update" automation step uses `=` not `lead_score + 5` | Open the automation step → use "Increment field" mode, not "Set field" |
| Same subscriber scored 50 times for same open | Open-tracking + automation re-fires per pixel-load (some clients fetch the pixel repeatedly) | Add a per-day rate limit in the automation conditions: "only run if last_score_update > 24h ago" |
| Score keeps growing forever even for unengaged | No decay logic | Add a daily-run scheduled automation that subtracts 1 from everyone's score |
| `sales-ready` tag never gets added despite high scores | The routing automation uses an old threshold (`>= 50`) | Edit the routing automation's condition, re-save |
| Customer-success team complains about wrong-stage leads | Threshold mis-calibrated | Audit converted customers' pre-conversion scores; adjust threshold |

<details>
<summary><strong>Advanced: API-driven scoring from ecommerce, CRM, and product analytics</strong></summary>

The AcelleMail automation builder handles the email-side events (open, click, form submit) natively. For non-email events (purchases, app logins, support tickets), you push score updates via the API.

**Increment a subscriber's score via API:**

```bash
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

# Get current score
current=$(curl -s -H "Authorization: Bearer $ACELLE_TOKEN" \
  "https://acellemail.com/api/v1/subscribers?email=user@example.com&list_uid=$ACELLE_LIST_UID" \
  | jq -r '.data[0].fields.lead_score')

new=$((current + 25))

# Update
curl -X PATCH "https://acellemail.com/api/v1/subscribers/{subscriber_uid}" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"fields\": {\"lead_score\": $new}}"
```

A simpler pattern: use AcelleMail's **Custom event** trigger + an automation step "Increment field":

```bash
# Fire a custom event with the score delta in payload
curl -X POST "https://acellemail.com/api/v1/events" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -d '{
    "subscriber_email": "user@example.com",
    "event": "purchase_completed",
    "value": 25
  }'
```

Then build an automation triggered by `event = purchase_completed` → step "Increment lead_score by event.value". One API call, score bumped, all logic in the automation flow.

**Shopify integration pattern:**

Shopify → Webhook → your bridge service → AcelleMail API. Common point values from Shopify webhooks:

```
cart_started:       +15
cart_abandoned:     +5  (after timeout — still engaged)
checkout_completed: +50
order_refunded:     -25
```

The bridge service can be a Cloudflare Worker, an AWS Lambda, a Vercel Edge function — anywhere that maps Shopify's webhook signature to AcelleMail's auth.

**Salesforce integration pattern:**

Use Salesforce Flow to call AcelleMail's API on Lead status changes:

```
Lead status → Marketing Qualified: AcelleMail tag "mql" + score +30
Lead status → Sales Qualified: AcelleMail tag "sql" + score +60
Lead converted to Opportunity: AcelleMail tag "opportunity" + score +100
```

When AcelleMail sees the `sales-ready` threshold crossing, it can webhook back to Salesforce to create a Task. Two-way sync, both systems agree on the score and the stage.

**HubSpot integration pattern:**

HubSpot's "contact property" sync via webhook to AcelleMail mirror — when HubSpot's native scoring changes, push the new value to AcelleMail's `lead_score` field. Single source of truth (HubSpot), AcelleMail just reads the value.

**Decay automation** (daily-run cron):

```bash
# Run via php artisan scheduler or external cron
php artisan tinker --execute='
  $subscribers = \App\Model\Subscriber::where("fields->lead_score", ">", 0)->get();
  foreach ($subscribers as $s) {
    $current = (int) ($s->fields["lead_score"] ?? 0);
    $newValue = max(0, $current - 1);
    $s->update(["fields" => array_merge($s->fields ?? [], ["lead_score" => $newValue])]);
  }
'
```

OR use AcelleMail's built-in scheduled automation: trigger "Date relative" = "every day at 03:00", action "Decrement field by 1".

**Multi-signal scoring** — score from multiple sources stays meaningful only if you can debug it. Log every score change with the reason. Store it in a custom field `lead_score_log` (JSON-encoded array) so a sales rep looking at a hot lead can see *why* they're hot:

```
{
  "lead_score": 85,
  "lead_score_log": [
    {"delta": 25, "reason": "shopify.cart_started", "at": "2026-05-10"},
    {"delta": 50, "reason": "shopify.checkout_completed", "at": "2026-05-12"},
    {"delta": 10, "reason": "email.click", "at": "2026-05-15"}
  ]
}
```

This becomes invaluable when investigating "why did this lead score 85?" — without the log, you're guessing.

</details>

## Related articles

- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Using API triggers for custom automation workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows)
- [Re-engagement campaigns — win back inactive subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# How to build a subscriber segment to target the right people

> Segments let you group subscribers by shared traits and behavior, then message just that audience. Build one step by step: name it, add conditions with All or Any match logic, and target it in a campaign.

- Canonical URL: https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: beginner
- Published: 2026-05-25 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

Sending the same email to your whole list is the fastest way to bore the people who love you and annoy the people who don't. **Segments** fix that. A segment is a saved, always-up-to-date group of subscribers who share something — what they're tagged with, when they were added, whether they open your emails — so you can send the right message to the right people instead of blasting everyone.

This guide builds a segment from scratch, end to end, so by the finish you can target a campaign at exactly the audience you want.

**Who this is for:** anyone managing a mailing list who wants to send more relevant campaigns. You'll work entirely on the customer side, inside one of your lists — no admin access needed.

## Before you start

- **A list with subscribers** — segments live inside a single list, so you need one with contacts in it. Go to **Audience → Lists** and open the list you want to segment.
- **Something to segment on** — segments filter on subscriber fields (email, name, company, tags, the Created date) and behavior (when they last opened or clicked). The more complete your subscriber data, the sharper your segments. If your list is messy, clean it first with [list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability).
- **A goal in mind** — "people who opened in the last 30 days", "Gmail subscribers", "tagged as VIP but never clicked". Knowing the audience makes the conditions obvious.

## Step 1 — Open the Segments tab

Open your list, then click the **Segments** tab. This screen is the home for every segment on the list: a count of your total segments, how big each one is, and a table of the segments you already have with their match rules and live subscriber counts.

To start a new one, click **New segment** in the top-right corner.

![The Segments screen for a list](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-build-a-subscriber-segment/step-1-segments-list.png)
*The Segments tab shows total segments, the size of each, and a table of existing segments with their match rules. **New segment** is in the top-right corner.*

## Step 2 — Name it and choose the match logic

The create screen asks for two things up front:

- **Segment name** — name it for the audience, not the rule. *Engaged Gmail subscribers* tells you more at a glance than *Segment 1*. This is just a label for you; subscribers never see it.
- **Match subscribers who meet** — this is the important one. **All** means a subscriber has to meet *every* condition to be included (this is AND — narrower, more precise). **Any** means meeting *just one* condition is enough (this is OR — broader). Most targeted segments use **All**.

Give it a clear name and leave the match set to **All** for now — you'll see why in the next step.

![The create-segment form](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-build-a-subscriber-segment/step-2-create-form.png)
*Name the segment for its audience, then pick how conditions combine: **All** (meet every rule) or **Any** (meet at least one). Then add your first condition.*

## Step 3 — Add your first condition

Click **Add condition**. A condition row appears with three parts, left to right — this is the whole engine of a segment:

1. **Field** — *what* you're filtering on. Open the dropdown and you'll see every field on the list: Email, First Name, Company, Tag, Created date, Last opened email, Last link click, plus any custom fields you've added.
2. **Operator** — *how* you're comparing it: *equal*, *not equal*, *contains*, *not contains*, *starts with*, *ends with*. For dates and numbers you also get *greater than* and *less than*, plus *blank* / *not blank* to catch empty fields. The choices suit the field type.
3. **Value** — *what to match against*. For a text field you type it in; for a date you pick one.

Read the row left to right and it says a sentence. The example below reads "**Email contains @gmail.com**" — a quick way to isolate your Gmail subscribers.

![A condition set to Email contains @gmail.com](https://acellesend.com/plugins/acelle/landing/images/kb-bot/how-to-build-a-subscriber-segment/step-3-condition.png)
*Each condition is three parts — field, operator, value — that read as a sentence. This one matches every subscriber whose email contains "@gmail.com".*

> **Tip:** Not sure which field to pick? Start broad with one condition, save, and check the subscriber count. If it's too big, come back and narrow it — segments are always safe to edit.

## Step 4 — Stack conditions to get specific

One condition is a filter. Two or more is a *targeted* segment. Click **Add condition** again for each extra rule, and remember the **All / Any** choice from Step 2 decides how they combine:

- **All (AND)** — every condition must be true. *Email contains @gmail.com* **and** *Last opened email greater than 2026-05-01* **and** *Tag equals vip* gives you recently-active Gmail VIPs — a tight, specific audience.
- **Any (OR)** — one condition is enough. *Tag equals vip* **or** *Tag equals early-adopter* pulls everyone in either group into one segment — a broad audience.

A good rule of thumb: use **All** when you're narrowing to a precise group, **Any** when you're gathering several groups into one. To drop a condition, click the **×** on its row.

## Step 5 — Save and use it

Click **Save segment**. You land back on the Segments tab, and your new segment shows up in the table with a **live subscriber count** — the number of contacts who match right now. That count isn't frozen: because a segment is a set of rules (not a fixed list), it updates itself as subscribers join, leave, or change. A "recent Gmail openers" segment automatically picks up tomorrow's new matching subscribers with no extra work.

Now put it to work. When you build a campaign and pick this list, you can target **the segment** instead of the whole list — so only the people who match get the email. Same for automations. That's the whole payoff: one relevant send beats ten generic ones.

## Troubleshooting

- **The segment count is 0** → your conditions are too strict, or a value doesn't match your data. Loosen one condition (or switch **All** to **Any**) and watch the count move. A common cause is a typo in the value, or filtering on a field most subscribers leave empty.
- **Way too many subscribers match** → you're on **Any** when you meant **All**, or you only have one broad condition. Switch to **All** and add a second condition to narrow it.
- **A field you expected isn't in the dropdown** → it's a custom field that doesn't exist on this list yet. Add it under **Manage list fields**, make sure your subscribers have values for it, then it'll appear as a condition option.
- **The count looks stale** → it isn't — segment counts are computed live from current subscribers. If a number surprises you, open the segment and re-read the conditions; the rules are doing exactly what they say.

## Recap

You've built a segment: opened the Segments tab, named it, chosen **All** or **Any** match logic, added conditions (field, operator, value), stacked them to get specific, and saved a self-updating audience you can target in any campaign.

Next, learn *which* segments actually move the needle in [Segments: slice your list into audiences that convert](https://acellesend.com/kb/articles/advanced-segmentation-strategies), and turn a "went quiet" segment into a comeback with [re-engagement automation](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers).

---

# Hard Bounce vs Soft Bounce — Reading the Bounce Log

> A hard bounce is permanent; a soft bounce is temporary. AcelleMail records them in the campaign bounce log and treats them differently — hard bounces blacklist the address, soft bounces are left alone for the receiving server to retry. This guide walks the bounce log, the DSN codes, and what each type means for your list.

- Canonical URL: https://acellesend.com/kb/articles/understanding-email-bounce-types
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-05-24 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Some of the emails you send come back. The receiving mail server rejects them and sends a little notice — a *bounce* — explaining why. AcelleMail collects those notices in a **Bounce log** on every campaign report.

This article shows you how to read that log, tells the difference between a **hard bounce** (permanent — the address is dead) and a **soft bounce** (temporary — try again later), and explains exactly what AcelleMail does on its own when each one comes back.

## The two categories of bounce

Every bounce carries a status code from the receiving server. The first digit is all you need to tell the two categories apart:

**Hard bounce — codes that start with 5 (5.x.x)**
A permanent failure. The address doesn't exist, the domain doesn't exist, or the message was rejected outright. Sending again won't help. AcelleMail **blacklists** the address (more on that below) so you stop wasting sends on it.

**Soft bounce — codes that start with 4 (4.x.x)**
A temporary failure. The mailbox is full, the server is busy, or you've been greylisted. The receiving server usually accepts the message on a later attempt. AcelleMail records the soft bounce but **takes no action on the address** — it leaves the retrying to the sending server, which is where retries belong.

That single rule — first digit 5 = permanent, first digit 4 = temporary — is how AcelleMail itself classifies every bounce it processes. If a bounce notice has no readable status code, AcelleMail files it as **unknown**.

## Open the bounce log

From a campaign report, open the **Sending logs** menu in the report nav and choose **Bounce log**.

![Campaign bounce log showing recipient, bounce type, reason and date columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/campaign-delivery/02-campaign-bounce-log.png)

At the top you'll see a one-line reminder: *"Emails that bounced back — hard or soft bounces from recipient servers."* Each row has four columns:

- **Recipient** — the subscriber that bounced. Click the email to jump straight to that subscriber's detail page.
- **Bounce type** — a small badge reading **hard**, **soft**, or **unknown** (the value AcelleMail classified the bounce as). All three use the same badge style — the text is what matters, not the colour.
- **Reason** — the receiving server's response. When the bounce notice carries a clean status line, you'll see the code right up front (e.g. `550 5.1.1 The email account that you tried to reach does not exist`). Click the text to read the full message if it's truncated.
- **Date** — how long ago the bounce was recorded.

There's a search box above the table — type a recipient address to filter the log down. Most bounces explain themselves: the reason text tells you what happened in plain language right after the code.

## Common DSN codes and what they mean

The codes in the Reason column are **DSN** (Delivery Status Notification) codes. Here are the ones you'll see most often.

### Hard bounces (5.x.x — permanent)

| Code | What it means | What it tells you |
|---|---|---|
| **5.1.1** | The mailbox doesn't exist | Typo at signup or a deleted account. The single most common hard bounce. |
| **5.1.2** | The domain has no DNS / no MX records | The whole domain is dead — usually a mistyped domain at signup. |
| **5.2.1** | Mailbox disabled or unavailable | The mailbox was turned off or suspended. |
| **5.7.1** | Message rejected (often by a spam/content filter or as unauthenticated) | Frequently a *your-side* problem — check your authentication, not the recipient. |
| **5.7.x** | Other security/authorization failures | Usually points at SPF/DKIM/DMARC or an IP block. Investigate before bulk-deleting addresses. |

### Soft bounces (4.x.x — temporary)

| Code | What it means | What it tells you |
|---|---|---|
| **4.2.2** | Mailbox over quota / full | The recipient's inbox is full right now. May clear up, may not. |
| **4.3.x** | Receiving mail system problem | The receiver is having trouble — out of your hands. |
| **4.4.1** | Connection timeout | The receiving server was slow or overloaded at that moment. |
| **4.7.0 / 4.7.1** | Greylisted / rate-limited / "try again later" | A common anti-spam tactic — the receiver defers your first attempt on purpose and accepts the retry. Usually clears on its own. |

A code starting with **5** that mentions authentication or spam filtering (the 5.7.x family) is the one to slow down on. Those bounces are very often about *your* setup — broken DKIM, an SPF mismatch, or a damaged sending reputation — not a bad recipient address. If you see 5.7.x across many recipients at once, that's a deliverability problem to fix, not a list to clean.

## Reading real rows

Look at the rows in the screenshot above and you can read each one like a sentence — the **type** badge tells you permanent vs temporary, the **reason** tells you the specific cause:

- `moorevanessa@navarro.com` — **hard**, `550 5.1.1 The email account that you tried to reach does not exist`. A dead mailbox. AcelleMail has already blacklisted it.
- `clayton61@molina.info` — **soft**, `552 5.2.2 The email account that you tried to reach is over quota`. The inbox is full right now. AcelleMail recorded it and left it alone; the receiving server can deliver on a retry.
- `conleyricky@day.com` — **hard**, `550 5.7.1 Message rejected as spam by Content Filtering`. This one's worth investigating on your side before assuming the address is bad.
- `mindy49@hodges.com` — **soft**, `451 4.7.0 Recipient server is rate-limiting your IP. Retry recommended`. The receiver is deferring you on purpose — normal, no action needed.
- `damon34@taylor.com` — **hard**, `550 5.1.2 The domain has no DNS / no MX records`. A dead domain — likely a typo when the contact signed up.
- `dustin40@reeves.biz` — **hard**, `550 5.7.7 Message failed DMARC alignment for the From: domain`. An authentication problem on your side, not a bad address.

The pattern is always the same: the badge gives you the category, the reason gives you the cause. Read both.

## What AcelleMail does automatically

| What bounced | What AcelleMail does |
|---|---|
| **Hard bounce (5.x.x)** | Records a bounce log row, sets the subscriber's status to **Blacklisted**, and adds the email to your **Blacklist** so future campaigns skip it everywhere. |
| **Soft bounce (4.x.x)** | Records a bounce log row only. **No change to the subscriber** — the receiving/sending server handles any retry. |
| **Bounce with no readable code** | Recorded as type **unknown**. No subscriber change. |
| **Spam complaint** (a separate signal, not a bounce) | Sets the subscriber's status to **Spam-reported** so future campaigns skip them. This comes from a feedback loop, not the bounce log — see the Feedback log and the article linked below. |

There is **no "bounced" status** in AcelleMail and no built-in soft-bounce retry queue. A hard bounce flips the subscriber to **Blacklisted** — that's the status you'll see on the subscriber and in your blacklisted count. Soft bounces are recorded for your visibility and nothing more.

> **Why blacklist instead of just unsubscribe?** A hard bounce means the address is dead globally, not just unhappy with one list. Blacklisting suppresses it across all your lists at once, which is what protects your sending reputation.

## Common issues and what to do

| What you see | What to do |
|---|---|
| **5.1.1 spikes on the first send to a freshly imported list** | The import source is bad (often a purchased or very old list). Stop sending, run **Email verification** on the list, and only keep verified addresses. |
| **5.7.1 across most of a campaign** | This is your side. Check your sending domain's DKIM/SPF — open **Sending domains** and confirm the DNS records verify. |
| **5.7.x to one specific recipient domain only** | That one receiver is blocking you. Investigate that domain specifically rather than cleaning the whole list. |
| **4.7.0 / 4.7.1 (greylisted / rate-limited) on a chunk of sends** | Normal. The receivers are deferring you on purpose; the retry generally succeeds. No action needed. |
| **4.2.2 (over quota) creeping up over time** | Stale list with abandoned mailboxes. Clean it periodically — see the list-hygiene article below. |
| **A hard bounce on someone who normally opens your emails** | Probably a temporary glitch (DNS hiccup, locked account that recovered). If they re-engage, you can remove them from the Blacklist — see below. |

## When an address was blacklisted by mistake

Because a hard bounce blacklists globally, the way to undo it is to remove the address from your **Blacklist**, not to "un-bounce" it:

1. Open **Blacklist** from the sidebar (under the Sending section).
2. Find the address and remove it.
3. Only do this when you have a genuine reason to believe the mailbox is live again (the person re-confirmed, or replied asking to keep getting your emails).

Don't bulk-remove blacklisted addresses without that evidence. Re-sending to addresses that already bounced hard is exactly the behavior that damages your reputation with receiving servers — the damage compounds with each campaign.

## DSN codes, decoded

If you want to read codes you don't recognize, every DSN code has the same three-part shape:

```
X . Y . Z
│   │   │
│   │   └─ Detail — the specific status (e.g. 1 = mailbox doesn't exist)
│   └───── Subject — the status class (e.g. 7 = security/policy)
└───────── Class — 5 = permanent, 4 = temporary, 2 = success
```

The **Subject** digit (the middle one) groups the cause:

| Middle digit | Status class |
|---|---|
| 1 | Addressing — bad mailbox or address |
| 2 | Mailbox status — exists but can't accept right now |
| 3 | Mail system — the receiving server itself |
| 4 | Network/connection |
| 5 | Mail protocol (SMTP-level) |
| 6 | Message content (body/MIME) |
| 7 | Security — authentication or policy block |

So `5.1.1` reads as *permanent · addressing · bad mailbox* — a dead address. `4.7.0` reads as *temporary · security · policy* — typically greylisting or rate-limiting. The code tells you the category; the reason text next to it tells you the specifics. Read both.

## What to do after

- If hard bounces are high on a list, run [Email list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability) and verify the list before your next campaign.
- If you're seeing 5.7.x or authentication-flavored bounces, work through the [delivery diagnostic checklist](https://acellesend.com/kb/articles/debugging-common-email-delivery-issues).
- Want the full code-by-code reference? See [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages).

## Related articles

- [Decoding SMTP error codes and bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages)
- [Email delivery issues — diagnostic checklist](https://acellesend.com/kb/articles/debugging-common-email-delivery-issues)
- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Email list hygiene — clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Feedback loops (FBL) setup](https://acellesend.com/kb/articles/feedback-loops-fbl-setup)

---

# Scaling AcelleMail for 100K+ Emails Per Day

> At 100K sends/day you cross the threshold where the default AcelleMail config starts to drag — queue depths climb, MySQL slow queries appear, occasional PHP-FPM 502s. This guide walks the bottleneck math, the configs to tune (queue pool size, MySQL InnoDB, PHP-FPM, nginx, Redis), when to split components onto separate hosts, and the cost/benefit at each step.

- Canonical URL: https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day
- Author: Daniel Reyes, Technical Editor
- Category: Server Management — Cron jobs, queues, Supervisor, Redis, Nginx, performance tuning
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-05-18 · Last updated: 2026-08-15
- Reading time: 12 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

At 100K sends/day, the default AcelleMail config starts to drag. You'll notice:

- Queue depths that don't drain (1k+ jobs queued even when "nothing is happening")
- MySQL slow query log filling up
- Occasional 502 Bad Gateway when admin pages load slowly
- Workers getting OOM-killed during list imports
- Customers complaining about "sending taking forever"

None of these are individually catastrophic, but each one compounds. **100K/day is the threshold where one well-tuned box starts to struggle — and where 30 minutes of targeted tuning gets you another 5× headroom.** This guide walks the math, the configs, and the architectural decisions, in the order you should hit them.

## Step 0 — Do the bottleneck math

100K sends/day = ~70 sends/minute *if perfectly even*. In reality, you get bursts — a 9 AM Tuesday campaign blast might be 100k sends in the first 20 minutes = **5,000 sends/minute peak**.

Each "send" is one job pulled from Redis → one HTTP call to the sending provider (SES/Mailgun/SendGrid) → one row inserted into `email_log`. A healthy worker handles ~20-50 sends/minute (limited by sending-provider API latency, ~50-200ms per call).

So to handle 5,000 sends/minute peak, you need:

```
5,000 sends/min ÷ 30 sends/min/worker = ~170 worker-slots needed at peak
```

That's significantly above the default 15-worker pool. **Either accept that bursts will queue up and drain over 5-10 minutes (often fine), or scale the worker pool**.

The right answer depends on your customer expectations. For most use cases, a 10-minute queue drain on a 100k-send burst is acceptable — and the default 15 + auto-scale-to-20 via `queue:adjust` handles it. For latency-sensitive transactional sends (welcome emails, password resets), bump the pool higher.

## Step 1 — Queue worker pool sizing

Per the [supervisor setup guide](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs#step-3--sizing-the-pools-for-your-workload), the two-tier pool default is 2 master + 15 worker = 17 total at Medium tier (4 vCPU / 8 GB).

For 100k+/day, the right starting point is **Large tier (8 vCPU / 16 GB) with 4 + 30 workers**:

```bash
# /etc/supervisor/conf.d/acellemail-master.conf
numprocs=4   # was 2

# /etc/supervisor/conf.d/acellemail-worker.conf
numprocs=30  # was 15
```

```bash
sudo supervisorctl reread && sudo supervisorctl update
sudo supervisorctl status
# Should now show 4 + 30 = 34 RUNNING processes
```

**Memory math:** each worker peaks ~256-512 MB. 30 × 384 MB ≈ 11.5 GB just for workers — fits comfortably in 16 GB after MySQL (3 GB), Redis (1 GB), PHP-FPM web (1 GB), nginx (negligible), OS (1 GB) = ~17 GB worst case. If you're tight, scale to 24 GB.

Verify queue drain rate is acceptable:

```bash
# Trigger a 10k send-test campaign, then watch the drain:
watch -n 2 'redis-cli llen queues:batch; redis-cli llen queues:high'
```

You want both to be back to near-zero within 5 minutes.

## Step 2 — MySQL tuning

Edit `/etc/mysql/mysql.conf.d/mysqld.cnf`:

```ini
[mysqld]
# Cache hot indexes + data in RAM. Set to 50-75% of system RAM if MySQL is alone.
# For a co-located AcelleMail + MySQL on 16 GB, 4-6 GB is a good baseline.
innodb_buffer_pool_size = 4G

# Larger redo logs = fewer flushes under heavy write load (campaign blast)
innodb_log_file_size = 512M

# Allow more concurrent connections (30 workers + web + admin + cron)
max_connections = 300

# Disable query cache (deprecated in MySQL 8; removed in MySQL 9; can hurt perf at scale)
query_cache_type = 0
query_cache_size = 0

# Trade durability for throughput — durable enough for queues; tolerates ~1s of lost commits on crash
innodb_flush_log_at_trx_commit = 2

# Optimize for SSD (default is OK for most installs; tune if you see I/O bottleneck in slow log)
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

# Per-table tablespaces (newer default, but verify)
innodb_file_per_table = 1
```

Restart MySQL:

```bash
sudo systemctl restart mysql
```

Verify the buffer pool is actually using the new size:

```bash
sudo mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
# Expect: 4294967296 (4 GB in bytes)
```

> **`innodb_flush_log_at_trx_commit = 2` trade-off:** the default `1` flushes the redo log to disk on every commit (full durability). Setting `2` flushes only once per second (~1s of writes lost on power failure). For email-sending workloads this is acceptable — at-most-1-second of `email_log` writes might be lost, but the sending itself completed (recipient got the email). The throughput gain is significant (often 3-5×). If you need full durability, leave it at 1 and accept slower writes.

### Watch the slow query log

```bash
# Enable in mysqld.cnf:
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0

sudo systemctl restart mysql

# Watch:
sudo tail -f /var/log/mysql/slow.log
```

Common AcelleMail slow queries and their fixes:

- `SELECT ... FROM subscribers WHERE list_id = X AND status = 'subscribed'` slow → ensure compound index on `(list_id, status)` exists (run `SHOW INDEX FROM subscribers` to verify)
- `SELECT ... FROM email_log WHERE campaign_id = X` slow → add index on `campaign_id`
- `SELECT ... FROM jobs ORDER BY id LIMIT 1` slow → switch queue driver to Redis (it eliminates `jobs` table polling)

## Step 3 — PHP-FPM tuning

Edit `/etc/php/8.3/fpm/pool.d/www.conf`:

```ini
pm = dynamic
pm.max_children = 50      # default 5
pm.start_servers = 10     # default 2
pm.min_spare_servers = 5  # default 1
pm.max_spare_servers = 20 # default 3
pm.max_requests = 500     # recycle workers periodically — prevents memory creep

; Important: limit how long a single FPM worker can hang before being killed
; — protects against a slow outbound HTTP call blocking the pool
request_terminate_timeout = 60s
```

Restart PHP-FPM:

```bash
sudo systemctl restart php8.3-fpm
```

**Memory math:** 50 workers × ~80 MB peak = ~4 GB worst case. Confirms our 16 GB sizing.

> **`request_terminate_timeout = 60s` is a hard lesson from production.** Without it, a single web request that hangs (slow remote API call, DNS timeout, blocked syscall) holds a worker indefinitely. 5 such requests = entire FPM pool blocked = site appears down. 60-second timeout kills the hung worker; supervisor restarts it. Set this on every production install.

## Step 4 — Nginx tuning

Edit `/etc/nginx/nginx.conf`:

```nginx
worker_processes auto;                # one per CPU core

events {
    worker_connections 2048;          # default 768 — too low for high-traffic sites
    use epoll;                        # default on Linux; explicit for clarity
    multi_accept on;
}

http {
    # gzip on responses
    gzip on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/javascript application/javascript application/json application/xml;
    gzip_comp_level 5;

    # Reasonable client + buffer settings for the admin UI
    client_max_body_size 300M;
    client_body_buffer_size 128k;
    client_header_buffer_size 8k;
    large_client_header_buffers 4 16k;

    # Keepalives reduce TCP overhead from the admin's repeated AJAX polls
    keepalive_timeout 65;
    keepalive_requests 100;
}
```

Reload nginx:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

## Step 5 — Redis on the same box (still — for now)

At 100k/day, **Redis stays on the same box**. Splitting it off is premature optimization until you hit ~1M/day or you need HA. Confirm Redis is the queue driver per [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing):

```bash
grep QUEUE_CONNECTION /var/www/acellemail/.env
# Expect: QUEUE_CONNECTION=redis

# Confirm Redis is sized for the workload:
redis-cli config get maxmemory
# Should be at least 2GB for 100k/day; 4GB for headroom

redis-cli config get maxmemory-policy
# MUST be: noeviction (queues require this — see Redis article)
```

## Step 6 — When to split DB to its own host

Co-located DB starts to bottleneck around **5-10M sends/month** (~150k-300k/day average). Symptoms:

- `iostat -x 1` shows sustained 80%+ disk %util during campaigns
- Top reports `mysqld` consistently in the top 2 CPU consumers
- `SHOW PROCESSLIST` shows 100+ active connections with worker queries waiting

At that point, move MySQL to its own host:

- **DigitalOcean Managed DB** — easy, ~$15/mo for the smallest tier. Adds 1-2ms latency per query (private network). Worth it for the operational simplicity.
- **AWS RDS** — same model. `db.t3.medium` is a reasonable starting point at ~$55/mo on-demand or ~$35 with 1-year Reserved.
- **Self-managed on a separate droplet** — cheapest, more ops work. Pick if you already have MySQL expertise in-house.

In AcelleMail's `.env`:

```env
DB_HOST=10.0.0.5         # private IP of the DB box (NEVER use public IP)
DB_DATABASE=acellemail
DB_USERNAME=acellemail
DB_PASSWORD=...

# Force private network if your cloud provides one
```

Run `php artisan config:clear` after.

## Step 7 — When to add a second app server

At ~10-20M sends/month (300k-600k/day), one app server starts to struggle even after all the tuning above. The right next step is **horizontal scaling**:

```
[Load Balancer]
       ↓
  ┌────┴────┐
[App1]   [App2]   ← stateless — run web UI + workers
       ↓
[MySQL on managed]
[Redis on its own box, replicated]
[Object storage for shared storage/ — S3 / DO Spaces / B2]
```

Key changes from single-server:

1. **Move `storage/` to shared object storage** (S3 / DO Spaces). Both app servers must see the same files.
2. **Move sessions to Redis** (`SESSION_DRIVER=redis` in `.env`). Otherwise customers get logged out when the LB routes them to a different app server.
3. **Add a sticky-session policy on the LB** for the WYSIWYG editor (it does background autosaves keyed to session).
4. **Run cron + supervisor on only one node** (or use leader-election like `php artisan schedule:work` with a lock). Running cron on both = double-firing every scheduled task.
5. **Add a Redis password** if Redis is now on a network reachable from multiple boxes.

This is the boundary where you should consider Docker / Kubernetes — orchestrating multiple stateless app instances by hand gets tedious fast. See the [Docker deployment guide](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail).

## Step 8 — Sending-provider rate limits

You can scale AcelleMail all you want, but **your sending provider's rate limit caps your throughput**. Watch these:

| Provider | Default limit | How to raise |
|---|---|---|
| Amazon SES | 14 sends/sec sandbox; production starts at 50/s and ramps with reputation | Open AWS Support ticket; usual cadence is +50/s/week as reputation builds |
| SendGrid | 100 sends/sec on Pro plan | Upgrade plan |
| Mailgun | 100 sends/sec on starter; higher tiers go to 1000/s | Upgrade plan |
| Postmark | 10 sends/sec default; up to 100/s on request | Email support |

At 100k/day = ~1.16 sends/second average, you're well within any provider's limits. At 1M/day = 11.5/sec average, you'll bump SES's sandbox cap during burst peaks. Plan ahead — see [SES Sending Limits Cookbook](https://acellesend.com/kb/articles/ses-sending-limits-cookbook).

## Quick-reference tuning checklist

After all the above, your config delta from default looks like:

| Component | Setting | Default | Tuned (100k/day) |
|---|---|---|---|
| Supervisor master | numprocs | 2 | 4 |
| Supervisor worker | numprocs | 15 | 30 |
| MySQL | innodb_buffer_pool_size | 128M | 4G |
| MySQL | max_connections | 151 | 300 |
| MySQL | innodb_log_file_size | 50M | 512M |
| MySQL | innodb_flush_log_at_trx_commit | 1 | 2 |
| PHP-FPM | pm.max_children | 5 | 50 |
| PHP-FPM | request_terminate_timeout | (unset) | 60s |
| nginx | worker_connections | 768 | 2048 |
| Redis | maxmemory | (unset) | 2-4G |
| Redis | maxmemory-policy | noeviction (already) | noeviction (verify) |
| `.env` | QUEUE_CONNECTION | sync | redis |
| `.env` | CACHE_DRIVER | file | redis |
| `.env` | SESSION_DRIVER | file | redis |

## Common issues

| Symptom | Cause | Fix |
|---|---|---|
| Queue depth grows during campaign blast, doesn't drain | Worker pool too small | Step 1 — bump worker numprocs |
| MySQL CPU pegs at 100% during sends | Buffer pool too small; reading from disk constantly | Step 2 — increase `innodb_buffer_pool_size` |
| Random 502 Bad Gateway on admin pages | PHP-FPM pool exhausted | Step 3 — bump `pm.max_children`; add `request_terminate_timeout` |
| OOM kills during list import | `memory_limit` too low for big CSV | `php.ini` `memory_limit = 1G` (for fpm + cli) |
| Workers slow even when queue is empty | Misconfigured `--sleep` (workers spinning) | Verify supervisor configs include `--sleep=3` |
| Mail-merge campaigns take minutes per recipient | Heavy template + many merge tags | Profile with `xhprof` / `blackfire`; cache merged content where possible |
| Sending IP getting rate-limited by Gmail/Outlook | Throughput exceeds receiver's per-IP cap | Add IP rotation; see [Multi-Server Rotation Pattern](https://acellesend.com/kb/articles/multi-server-rotation-pattern) |
| Free disk space dropping fast | `email_log` table growing without bound | `system:cleanup` daily task should prune; verify cron is firing |
| `php artisan` operations slow | Cached config / view files stale | `php artisan optimize:clear` after major config changes |

## When to stop tuning and just scale up

A single tuned 8 vCPU / 16 GB box handles ~1M-2M sends/month comfortably. Above that, **add hardware before tuning further**:

- 2M+/mo → 16 GB → 24-32 GB RAM; consider RDS for DB
- 5M+/mo → Multi-app-server architecture (Step 7)
- 20M+/mo → Multi-region, dedicated sending IPs, custom partition for `email_log`, full DBA review

The cost of an additional $30-100/mo of hardware is much less than the cost of 4 hours of your time spent micro-tuning.

## FAQ

**Should I tune PHP-FPM `pm = static` or `pm = ondemand` instead of `dynamic`?** For a busy-most-of-the-time AcelleMail (campaigns going out throughout the day), `dynamic` is the best balance. `static` wastes RAM on idle nights. `ondemand` adds latency on the first request after idle. `dynamic` is the right default for 100k/day.

**Why not Cloudflare in front?** Cloudflare can absorb traffic spikes to public AcelleMail pages (tracking pixel endpoints, unsubscribe links). It can't help with worker throughput (those are SSE/API calls from the server). Worth adding for the tracking-pixel layer; not a substitute for the tuning above.

**Should I tune the MySQL `tmp_table_size` / `max_heap_table_size`?** Defaults are usually fine. If `SHOW STATUS LIKE 'Created_tmp_disk_tables'` shows a high number relative to `Created_tmp_tables`, bump both to 256M.

**What about HTTP/2 / HTTP/3?** nginx 1.24+ supports both. Modest improvement on admin UI responsiveness; no impact on send throughput. Worth enabling: `listen 443 ssl http2;` in the vhost.

**Does Acelle support read replicas?** Yes — Laravel's `read` / `write` database config in `config/database.php` supports separate read endpoints. Useful at 5M+/mo when read-heavy operations (campaign reports, subscriber search) start to slow down. Pre-configured Laravel pattern; AcelleMail honours it.

**Can I cap a single campaign's send rate?** Yes — set a per-sending-server throttle. See [Sending Throttling Strategies](https://acellesend.com/kb/articles/sending-throttling-strategies) for the full configuration.

## Related articles

- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — the supervisor baseline (this article extends it)
- [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing) — Redis is a prerequisite for any meaningful scaling
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — protect your scaled-up DB
- [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) — pick the right tier for your volume
- [Install AcelleMail on AWS EC2](https://acellesend.com/kb/articles/install-acellemail-on-aws-ec2) — for the multi-AZ / multi-server architecture
- [Docker Deployment Guide for AcelleMail](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) — for the multi-app-server pattern
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — pair with SES at scale
- [SES Sending Limits Cookbook](https://acellesend.com/kb/articles/ses-sending-limits-cookbook) — raise the sending-provider cap
- [Multi-Server Rotation Pattern](https://acellesend.com/kb/articles/multi-server-rotation-pattern) — multi-IP setup beyond a single SES account
- [Sending Throttling Strategies](https://acellesend.com/kb/articles/sending-throttling-strategies) — pace sends to avoid receiver throttling
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — apply hardening after every scale-up

---

# How to set up a welcome email automation for new subscribers

> New subscribers are most engaged the second they join. Build a welcome automation that emails them the instant they sign up — set it up once, and it greets every new contact for you.

- Canonical URL: https://acellesend.com/kb/articles/set-up-a-welcome-email-automation
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: beginner
- Tags: Automation, Welcome Series, Drip Campaign, Triggers, Workflows
- Published: 2026-05-18 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

The very first email a new subscriber receives sets the tone for everything that follows — yet most lists send nothing at all until the next campaign happens to go out. A welcome automation fixes that: the moment someone joins your list, they get a warm, on-brand email automatically, with no manual work from you. This guide walks you through building one from scratch in the visual automation editor, one step at a time. It's written for beginners — if you can fill in a form, you can finish this in about ten minutes.

By the end you'll have a live automation that greets every new contact the instant they subscribe.

## Before you start

A few things should be in place first:

- **A mailing list** that new contacts actually join — through a signup form, an import, or your API. Sign-ups on this list are what will trigger the welcome email. (Still filling your list? See [importing contacts from a CSV](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping).)
- **An authenticated sending domain**, so your welcome email is signed and lands in the inbox rather than the spam folder. If you haven't done this yet, work through [setting up SPF, DKIM, and DMARC](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) first.
- **A sender name and address** your subscribers will recognize at a glance.

> **Why bother automating it?** Welcome emails are opened far more often than regular campaigns — people just signed up, so they are expecting to hear from you. Sending automatically, within seconds, catches that attention at its peak. A welcome that arrives three weeks later has already missed the moment.

## Step 1 — Choose the "Welcome new subscribers" trigger

Open **Automation** in the left sidebar and click **New Automation**. Every automation begins with a *trigger* — the event that makes a subscriber enter the workflow. You'll land on a grid headed **"What should trigger this automation?"**

Pick the first card, **Welcome new subscribers**. This trigger fires the moment a contact is added to a list, which is exactly the behavior a welcome email needs.

![The trigger picker with "Welcome new subscribers" selected](https://acellesend.com/plugins/acelle/landing/images/kb-bot/set-up-a-welcome-email-automation/step-1-trigger.png)
*Choose "Welcome new subscribers" — it starts the workflow the instant someone joins your list.*

## Step 2 — Name it and choose the list

As soon as you pick the trigger, a short **Configure your trigger** panel appears below. Fill in three things:

1. **Automation name** — type something you'll recognize later, such as `Welcome series`. Only you see this; it is never shown to subscribers.
2. **Mailing list** — pick the list that new contacts join. In the example we've chosen *Free Trial Users*. This is the single most important choice on the page: the automation only reacts to sign-ups on *this* list.
3. **Segment (optional)** — leave it as **All subscribers** for now. If you'd rather greet only part of the list (say, contacts who match certain criteria), you can point it at a [saved segment](https://acellesend.com/kb/articles/how-to-build-a-subscriber-segment) instead.

![Naming the automation and choosing the mailing list](https://acellesend.com/plugins/acelle/landing/images/kb-bot/set-up-a-welcome-email-automation/step-2-configure.png)
*Give the automation a name, then pick the list whose new subscribers should be welcomed.*

Click **Create automation**. You'll arrive at the visual flow editor with one node already placed — your trigger, sitting at the top of the canvas.

## Step 3 — Add a "Send email" step

The editor is a canvas you build on. Your trigger sits at the top, and directly beneath it is a small **+** button. Click it to open the **Add node** menu.

Five kinds of step are offered — **Send email**, **Wait**, **Condition**, **Operation**, and **Webhook**. For the welcome message, choose **Send email**.

![The Add node menu with "Send email" chosen](https://acellesend.com/plugins/acelle/landing/images/kb-bot/set-up-a-welcome-email-automation/step-3-add-email.png)
*Click the + under the trigger and choose "Send email" to attach your welcome message.*

> **Tip:** The other node types are how you grow a single email into a proper series later — a **Wait** to pause a day or two, then another **Send email**. We'll come back to that at the end.

## Step 4 — Write your welcome email

A panel slides in from the right with three tabs across the top: **Setup → Template → Overview**.

On the **Setup** tab:

- **Subject** — write the line that lands in the inbox. Friendly and specific beats clever, e.g. *Welcome aboard — here's how to get started*. You can drop in list fields like `{{ list.name }}` if you want.
- **From name** and **From email** — these pre-fill from your list's defaults. Double-check they're the sender your subscribers expect to see.
- **Tracking & delivery** — keep **Track opens**, **Track clicks**, and **Sign with DKIM** switched on. Opens and clicks let you measure how the welcome performs; DKIM signing helps it reach the inbox.

![The email setup panel with the subject line filled in](https://acellesend.com/plugins/acelle/landing/images/kb-bot/set-up-a-welcome-email-automation/step-4-write-email.png)
*Set the subject and sender, keep tracking on, then Save & Continue.*

Click **Save & Continue** to move to the **Template** tab. Click **Browse templates**, pick a starter design that suits your brand, and refine it in the visual editor if you like. Once the email looks right, the step is complete and the **Overview** tab shows a summary of it.

> **The subject is doing the heavy lifting here.** Because welcome emails are opened so often, the subject line earns its keep. If you'd like a shortcut, [these subject-line formulas](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work) are a solid starting point.

## Step 5 — Turn it on

Back on the canvas, your flow now reads **Trigger → Send email**. Until you switch it on, the automation stays in **PAUSED** — nothing goes out.

When you're happy with it, click **Activate** in the top-right corner. From that moment, every new contact added to the chosen list receives your welcome email automatically. You can pause it again anytime from the same button.

> **Test before you trust it.** Add yourself (or a test contact) to the list and confirm the welcome email actually arrives and looks right, before real sign-ups start depending on it.

## Make it a welcome *series* (optional)

One email is a great start; a short series is even better. Back in the editor, click the **+** below your first email and add:

1. A **Wait** step — pause for, say, two days.
2. A second **Send email** — a follow-up with a useful tip, a popular resource, or a gentle call to action.

Repeat to build a three- or four-email onboarding sequence. To send different messages to different people, add a **Condition** node — see [advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) for how branching works.

## Troubleshooting

- **The email node shows "No email bound."** You added the Send email step but haven't finished the **Template** tab. Open the node and pick a template so the step turns valid — an automation won't activate while an email is unfinished.
- **New subscribers aren't getting the email.** Check that the automation is **Activated** (not Paused), and that new contacts are joining the *exact* list you chose in Step 2. The trigger reacts only to that one list.
- **The welcome lands in spam.** Make sure your sending domain is authenticated. Working through [SPF, DKIM, and DMARC](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) is the biggest single lever on inbox placement.

## Recap

You just built a welcome automation: a trigger that fires when someone joins your list, a welcome email attached to it, and one click to make it live. Every new subscriber now gets a warm first impression automatically — no manual sending, and nobody slipping through the cracks.

From here, turn that single email into a short onboarding series, or branch it with conditions so different subscribers get different messages. The welcome is the highest-leverage email you'll ever set up once and then forget about.

---

# Contributing to AcelleMail Development

> AcelleMail is a commercial CodeCanyon product — not an open-source project with public PRs and a Discord. But that doesn't mean you can't contribute. This guide walks the four real channels that move AcelleMail forward: high-quality bug reports through CodeCanyon support, well-framed feature requests, public knowledge-sharing through case studies + tutorials, and translation contributions. Plus what to expect in response.

- Canonical URL: https://acellesend.com/kb/articles/contributing-to-acellemail-development
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: AcelleMail Updates — Release notes, changelogs, new features, roadmap, community news
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-17 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail is a **commercial CodeCanyon product** — not an open-source project with public pull requests, a Discord, or a "community fork on GitHub". That said, **operators do meaningfully shape the product** through other channels.

This article is the honest answer to "how do I contribute?" Four real channels that work:

1. **High-quality bug reports** via CodeCanyon support
2. **Well-framed feature requests** with business cases
3. **Public knowledge-sharing** — blog posts, KB-style articles, conference talks
4. **Translation contributions** via the `resources/lang/` directory

Each section explains what works and what the team actually accepts. The legacy version of this article promised a "Community Edition on GitHub" and a Discord — neither of which we can verify exist. We've rewritten honestly.

## Channel 1 — High-quality bug reports

Bug reports are the single most valuable contribution. The team prioritises fixes for bugs that come with clean reproductions.

### The format that gets bugs fixed fast

Send via your CodeCanyon Item page → Comments tab (public) OR Support tab (private; for security-relevant issues). Include:

```
**AcelleMail version:** 5.x.x (from Admin → About)
**Server:** Ubuntu 24.04 / PHP 8.3 / MySQL 8.0 / Redis 7
**Customer-facing impact:** describe what a real customer experiences

**Steps to reproduce:**
1. Log in as admin
2. Navigate to Lists → New
3. Enter "Test List" + click Save
4. (etc.)

**Expected:** The new list appears in the Lists index.

**Actual:** Error page with message "SQLSTATE[42S02]: Base table not found".

**Error log excerpt** (from /var/www/acellemail/storage/logs/laravel.log):
[2026-05-16 14:32:11] production.ERROR: SQLSTATE[42S02]: Base table or view not found...
{stack trace 5 lines}

**Workaround in use (if any):** Manually run `php artisan migrate --force` makes it work; reproducible on fresh install.

**Frequency:** Every time, on every install I've tried.
```

**Why each piece matters:**

- **Version + server stack:** the team can reproduce in their matching environment
- **Customer-facing impact:** signals priority (a bug that breaks "create list" affects every operator)
- **Steps to reproduce:** lets the team verify the fix
- **Error log excerpt:** points the team at the right code area
- **Workaround:** signals you're patient (workaround is in place) and the bug is well-understood
- **Frequency:** "always" is higher priority than "sometimes"

### What NOT to send

- "It's broken, please fix" with no details — ignored
- Screenshots of error pages without the underlying server log — half the data is missing
- Reports from heavily customised installs without first reproducing on a stock install
- Reports of issues already fixed in a newer version (check changelog first)

### What to expect in response

- **Acknowledgement within 1-3 business days** (CodeCanyon's support SLA)
- **Investigation within 1-2 weeks** for clear reproductions
- **Fix in the next patch** for confirmed bugs (so 2-6 weeks until you can deploy it)
- **For security bugs:** faster — usually patched within days, distributed via emergency patch

## Channel 2 — Well-framed feature requests

The team listens to operators who frame requests as **customer problems**, not feature wishlists.

### The format that gets considered

```
**The customer problem:**
My customer ABC Restaurant Group runs daily promotional campaigns to their
50k-subscriber list. They want to throttle sends to a maximum of 1000 emails
per minute to avoid overwhelming their kitchen with simultaneous orders.

Currently the only way to do this is configuring throttle at the sending
server level — but that affects ALL my customers, not just this one. I can't
give each customer their own throttle.

**The ask:**
Per-customer (or per-plan) sending-rate throttle, configurable from the
plan's `options` JSON or a dedicated field.

**Business case:**
This customer pays $400/month. They've asked twice. They're threatening to
move to ActiveCampaign which has this feature. Losing them = $4,800 ARR.

**Workaround we're using:**
Manually pause their campaign and resume it every 60 seconds via a cron
job. Brittle and ugly.

**Would you accept a contributed patch implementing this?** Yes / No.
```

### What gets prioritised

- Features that unlock new market segments for AcelleMail (cited by multiple operators)
- Compliance-blocking features (GDPR, CASL, accessibility)
- Quality-of-life improvements that ship with high test coverage

### What doesn't

- "It would be cool if..." (no business case)
- Asks that conflict with the platform's direction (e.g. "rewrite the admin in Vue")
- Features needed by exactly one operator with no broader applicability
- Speculative architecture requests

### What to expect in response

- **Triage within 2-4 weeks** — the team responds with: accept, decline, defer, or "tell me more"
- **Accept:** added to internal backlog, no specific ETA
- **Defer:** "interesting but not now" — re-pitch in 6-12 months with fresh data
- **Decline:** rare; usually with a reason
- For accepted features: typical timeline is 3-12 months from accept to ship

## Channel 3 — Public knowledge-sharing

This is the highest-leverage contribution most operators can make — it doesn't require the dev team's bandwidth at all, and it benefits every operator who comes after you.

### Things to share

- **Tutorial-style blog posts** on your own site about how you solved problem X with AcelleMail
- **Open-source extensions / WordPress plugins** that wire AcelleMail to other tools
- **Conference talks / podcast appearances** at marketing-ops events
- **Detailed reviews** on tool-comparison sites (G2, TrustRadius, Capterra)
- **Translated documentation** in your local language
- **Case studies** of your install (anonymised or attributed — your choice)

### Why this matters

Every potential AcelleMail buyer searches "AcelleMail vs Mailchimp" or "self-hosted email marketing alternatives" before purchasing. The content that ranks shapes the buying decision. Operators sharing real experiences move buyers off Mailchimp toward AcelleMail more effectively than any AcelleMail marketing.

### Reciprocity

The dev team has historically been responsive to operators who do this consistently — feature requests get prioritised, support gets faster, beta access offered. Not transactional, but the team notices.

## Channel 4 — Translation contributions

The most direct form of code contribution that doesn't require dev-team coordination.

AcelleMail's UI strings live in `resources/lang/<locale>/refactor/*.php`. Each file is a PHP array of `key => translation`. If your locale's translations are missing or incomplete, you can add them and submit via CodeCanyon support.

```bash
# Find your locale's translation status
cd /var/www/acellemail/resources/lang
ls vi/    # Vietnamese
ls fr/    # French
ls es/    # Spanish

# Compare against the canonical en/
diff <(ls en/) <(ls vi/)   # Files missing from Vietnamese
```

For each missing file:

1. Copy from `en/`: `cp en/refactor/admin_plans.php vi/refactor/admin_plans.php`
2. Translate each string value (keep the keys identical)
3. Test by setting your install's language to vi and walking through the affected screens
4. Submit the translated files via CodeCanyon support with "I'd like to contribute Vietnamese translations for files X, Y, Z"

Translations are usually accepted within 1-2 weeks and shipped in the next patch.

> **Note:** the i18n architecture is evolving — see the [translations developer guide](https://acellesend.com/developers/translations) if you're contributing structurally rather than just translating strings.

## What you CANNOT do

To set realistic expectations:

- **Submit a pull request to a public repo** — AcelleMail's source isn't on a public GitHub
- **Become a maintainer** in the open-source sense
- **Fork and redistribute** the code (the licence prohibits this)
- **Talk to other contributors in a public chat** — there's no official Discord/Slack as of 2026
- **See the internal issue tracker** — it's not public

If "public open-source collaboration" is core to how you want to engage with a product, AcelleMail isn't currently set up for that. Channels 1-4 are the actual surface area.

## Building a long-term relationship with the team

Operators who become "known to the team" over months/years get:

- Faster bug fixes
- Priority on feature requests
- Beta access to upcoming features
- Sometimes informal roadmap conversations

This isn't transactional or hidden — it's just normal human relationship-building, applied to a small dev team. The pattern that works:

- File **high-quality** issues consistently (not many low-quality ones)
- **Respond promptly** when the team asks for clarification
- **Share publicly** (Channel 3) — being visible in the AcelleMail ecosystem makes the team notice you
- **Be patient** about timelines — 6-12 months is normal for feature delivery
- **Pay it forward** by helping other operators in public channels

## FAQ

**Is there a public roadmap I can vote on?** No formal voting system. See [AcelleMail 2026 Roadmap](https://acellesend.com/kb/articles/acellemail-2026-roadmap) for the roadmap-discovery channels.

**Can I sponsor a feature?** Talk to support — for substantial bounties on specific features, the team has historically been open to sponsored development. Don't expect <$5k bounties to move the needle.

**Can I build and sell an AcelleMail plugin?** Yes — extensions you build under the plugin architecture (or as WP / Zapier integrations) are yours to sell. They aren't part of AcelleMail's source.

**What if I find a security vulnerability?** Report PRIVATELY via CodeCanyon Support (not in public comments). Include reproduction steps + your suggested severity. The team typically responds within 24-48 hours for security reports.

**Is there a bug bounty?** Not formally advertised. Significant security findings have historically been rewarded; coordinate via private support.

**Can I publish an AcelleMail "Awesome List" on GitHub?** Yes — operators have created repos listing extensions, integrations, related tools. Not officially endorsed but welcomed.

**Where do I find other AcelleMail operators to talk to?** Beyond direct CodeCanyon comments + relationships you build at conferences, no official central channel as of 2026. Suggest creating one if you have the bandwidth — it would be a meaningful contribution.

## Related articles

- [What's New in AcelleMail](https://acellesend.com/kb/articles/whats-new-in-acellemail-52) — release cadence
- [AcelleMail 2026 Roadmap](https://acellesend.com/kb/articles/acellemail-2026-roadmap) — direction the platform is heading
- [Community Spotlight: Business Case Studies](https://acellesend.com/kb/articles/community-spotlight-business-case-studies) — operator stories worth contributing to
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — how to build extensions you may want to share
- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — for integration extensions
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — the SaaS-operator community is the most active contributor pool
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — security knowledge to share back

---

# Email Throttling & Rate Limits — What's the Difference

> Throttle, rate limit, sending limit. Different words, often confused. This guide separates them plainly — who enforces each — and shows the one control you actually set in AcelleMail: the per-server Sending limit.

- Canonical URL: https://acellesend.com/kb/articles/email-throttling-and-rate-limits-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-05-16 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

You've heard "throttle," "rate limit," and "sending limit" used like they mean the same thing. They don't. Two are something you set; the rest are decided by other people's servers. This guide draws the line clearly, then shows you the one control you actually have in AcelleMail — the per-sending-server **Sending limit**.

## The two words people mix up

**Throttle** is *your* side. You decide to pace how fast AcelleMail hands messages to a sending server — say, no more than 1,000 emails an hour. It's a voluntary cap you set. If you hit it, the rest of your campaign simply waits in the queue and goes out as the window rolls forward. Nothing fails; it just spreads over time.

**Rate limit** is *their* side. The receiving mailbox provider (Gmail, Yahoo, a corporate mail server) decides how much mail it will take from you in a given window. You don't set this and you can't see the exact number — it's the provider's call, often tied to your reputation. When you bump into it, the receiving server temporarily defers your mail with a "try again later" style response, and AcelleMail retries on its normal schedule.

The short version:

| Term | Who sets it | What happens when you hit it |
|---|---|---|
| **Throttle** (you, in AcelleMail) | You | Remaining mail queues, then sends in the next window — no failure |
| **Rate limit** (the receiver) | Gmail / Yahoo / the recipient's mail server | Receiver defers with a temporary "try later" response; AcelleMail retries |

Everything else you might have read — vendor daily caps, per-domain throttling scripts — is either your email vendor's own contract limit (you manage that in *your vendor's* dashboard, not in AcelleMail) or behaviour on the receiving side that no email tool controls. The only knob that lives inside AcelleMail is the throttle, and it's called the **Sending limit**.

## The one control you set: Sending limit

The Sending limit is set per sending server. Two servers can have two different limits — handy when you run a fast transactional server and a paced marketing one.

### Open the sending-server detail

In AcelleMail's sidebar, click **Sending → Sending servers**. The list shows every server connected to this account, with its **Sending limit**, **Status**, and **Created** date:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

The **Sending limit** column tells you each server's current cap at a glance — for example `1,000 / hour`, or `Unlimited` if no cap is set. Click the server name to open its detail page.

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

### Set the limit

On the server's detail page, find the **Configuration** card and the **Sending limit** dropdown. It offers a short list of presets:

- **Unlimited** — no pacing; AcelleMail sends as fast as the server accepts
- **100 emails per 1 minute**
- **1,000 emails per 1 hour**
- **10,000 emails per 1 day**
- **Custom** — pick your own number and window

Choose **Custom** and three more fields appear: **Per** (the count), **Every** (how many time units), and **Unit** (Minute / Hour / Day). So "5,000 every 2 hours" is Per `5000`, Every `2`, Unit `Hour`. Click **Save configuration**.

That's the whole feature. AcelleMail enforces the cap for that server — when the rolling-window count reaches your number, the queue holds the rest until the window slides forward. You don't add anything to your campaigns; you set it once on the server.

## When to set a limit (and when not to)

| Situation | Set a Sending limit? |
|---|---|
| Warming a brand-new IP or domain | **Yes** — start low and raise it over days |
| Established server, steady volume | Optional — leave it Unlimited, or set a cap as a safety brake |
| Big one-off campaign blast | **Yes** — pace it so you don't dump everything in one spike |
| Transactional / password-reset server | **No** — those need to go out immediately; leave it Unlimited |
| Several servers sharing the load | **Yes** — a per-server cap divides the volume across them |

A limit that's too low just makes campaigns crawl for no reason. A limit that's sensible protects a young IP and smooths out spikes. Match the number to what you actually send, not to a number that "feels safe."

## Common issues

| What you see | What it means | What to do |
|---|---|---|
| A campaign sends far slower than expected | The server's Sending limit is set low | Open the server → **Configuration** → raise the **Sending limit** (or set it to Unlimited) |
| Sends pause, then resume in bursts | Normal — the rolling window filled up, then reset | Nothing to fix; raise the limit only if the pace is too slow for you |
| Mail to one provider keeps getting deferred and retried | That receiver is rate-limiting you (their side, not yours) | This isn't a Sending limit problem — see the deliverability guides below; check bounces and reputation |
| The Sending limit column shows `Unlimited` and you wanted a cap | No limit was ever set on that server | Set one via the Configuration card and **Save configuration** |

## What to do after

- Decide whether each of your servers should be capped or Unlimited, and set them in **Sending → Sending servers**.
- If deferrals are coming from the receiving side, work the deliverability angle instead — start with bounce decoding and an incident runbook.
- Spreading volume across more than one server? See the multi-server guides below.

## Related articles

- [Sending throttling strategies](https://acellesend.com/kb/articles/sending-throttling-strategies)
- [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)
- [SES sending limits cookbook](https://acellesend.com/kb/articles/ses-sending-limits-cookbook)
- [Multi-server rotation pattern](https://acellesend.com/kb/articles/multi-server-rotation-pattern)

---

# Post-Purchase Follow-Up Automation in AcelleMail — Thank, Review, Cross-Sell

> A confirmation email is just the start. Layer in a delivery-day check-in, a 7-day review request, a 30-day cross-sell, a 60-day "we miss you". This guide builds the full post-purchase flow in AcelleMail's automation builder using tags or custom events fired from your ecommerce backend.

- Canonical URL: https://acellesend.com/kb/articles/post-purchase-follow-up-automation
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-13 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What "post-purchase" actually means

A post-purchase flow is a sequence of emails that fires AFTER someone buys — automatically, without you scheduling each send. The right flow varies by what you sell, but every successful one shares the same skeleton:

| Stage | Timing | Goal |
|---|---|---|
| Confirmation | Immediately | Reassure: order received, here's what happens next |
| Shipping notification | When tracking is available | Set expectation for arrival |
| Delivery check-in | 1-2 days after delivery | Confirm satisfaction, surface issues early |
| Review request | 7 days after delivery | Capture social proof while memory is fresh |
| Cross-sell / replenishment | 30 days after purchase | Right-time the next-purchase nudge |
| Win-back | 60-90 days if no return purchase | Re-engage before they forget |

AcelleMail's automation builder handles all of this with three trigger types: **Tag added**, **Custom event**, and **Date relative** (post-tag).

## Setup: two paths depending on your ecommerce stack

### Path A: Tag-driven (simpler, manual or webhook-pushed)

When a purchase happens, your ecommerce system tags the subscriber in AcelleMail with a tag like `purchased-2026-05-19` or just `recent-purchaser`. The automation triggers on tag add.

### Path B: Custom-event-driven (richer, more flexible)

Your ecommerce system fires a custom event with payload like `{"event": "purchase_completed", "order_total": 87.50, "products": ["sku-001", "sku-007"]}`. The automation triggers on the event + uses payload data in subsequent steps.

For most senders Path A is enough to start. Switch to Path B when you need conditional flows ("if order over $100 → premium review request track; else standard track").

## Build the flow (Path A — tag-driven)

### Open the visual automation builder

In AcelleMail's left sidebar, click **Automation** → **Automations**. The index lists every automation in this account with its current state:

![Automations index](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/01-automations-index.png)

Click **New automation** in the top-right toolbar. The trigger picker opens:

![Trigger picker — pick what starts the automation](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)


For the confirmation email step:

1. **Trigger**: **Tag added**
2. **Tag**: `recent-purchaser`
3. **Action**: send the **Confirmation** email immediately

Drop in the email step, write your confirmation copy. Save. The trigger now fires the email whenever `recent-purchaser` tag is added to anyone.

Add downstream steps inside the same automation:

```
[Trigger: tag added 'recent-purchaser']
  →
[Email: Order Confirmation]
  Wait: 3 days
[Email: How's it going? (delivery check-in)]
  Wait: 4 days  (=7 days from purchase)
[Email: Leave a review]
  Wait: 23 days  (=30 days from purchase)
[Email: You might also like...]
  Wait: 30 days  (=60 days from purchase)
[Condition: did NOT purchase again]
  →
[Email: We miss you — here's 10% off]
```

The visual builder:

![Multi-step automation flow](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/03-flow-builder.png)

Each "Wait" step is delay-based — AcelleMail queues the next step's send time per-subscriber individually.

## Tag-source patterns

The tag `recent-purchaser` arrives in AcelleMail one of three ways:

### Webhook from ecommerce platform

Shopify / WooCommerce / Magento / BigCommerce all send order webhooks. Configure a bridge service (Cloudflare Worker, Zapier, n8n, custom backend) to translate the webhook into an AcelleMail API call:

```
[Shopify webhook: order/created]
  → bridge service receives
  → POST to https://acellemail.com/api/v1/subscribers/{uid}/tags
    body: { "tags": ["recent-purchaser"] }
```

The subscriber must already exist in your AcelleMail list (or use the upsert API to create-if-not-exists).

### Manual tag-add from the subscriber detail view

For small senders, the easiest path is manual: open the subscriber, **Tags** section, click **Add tag** → `recent-purchaser`:

![Subscriber tags UI](https://acellesend.com/plugins/acelle/landing/images/help/kb/preferences/03-subscriber-tags.png)

Workable for low-volume; not scalable.

### CSV import / bulk re-tag

Export today's orders → CSV with `email` + `tags` columns → import into AcelleMail via **Audience → [list] → Import** with the duplicate-handling set to **Update**. Existing subscribers get the new tag, the automation fires.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Confirmation email doesn't send when order placed | Webhook from ecommerce isn't reaching AcelleMail | Test the webhook destination URL directly with curl; verify auth header |
| Subscriber gets confirmation but no follow-ups | Wait step not advancing | Open automation → check run-log for blocked subscribers (waiting on Wait timer; OR exited automation due to unsubscribe) |
| Same subscriber gets 3 confirmation emails | Tag added 3 times (re-imported 3 times) | Switch to "Tag changed to" not "Tag added" — fires only once per state change |
| Review request fires before delivery | "7 days from tag add" but tag was added at order time, before shipping | Have ecommerce add a separate "order-delivered" tag when tracking shows delivered; trigger review on that tag instead |
| Cross-sell email recommends item already purchased | Email body hard-codes product list, ignores purchase history | Use merge tags from custom fields populated at purchase time, OR fall back to popular-items |

## Path B — Custom event triggers

Switch to events when the flow needs to react to *what* was purchased, not just *that* a purchase happened.

Fire the event from your ecommerce backend:

```bash
curl -X POST "https://acellemail.com/api/v1/events" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -d '{
    "subscriber_email": "buyer@example.com",
    "event": "purchase_completed",
    "data": {
      "order_total": 87.50,
      "currency": "USD",
      "products": ["yoga-mat-blue", "yoga-block-pair"],
      "category": "fitness"
    }
  }'
```

In AcelleMail, build the automation:

1. **Trigger**: **Custom event**, event name = `purchase_completed`
2. **Conditional step**: branch on `data.order_total > 100`
3. **Branch A** (high-value): premium review request track
4. **Branch B** (standard): standard review request track

Custom-event triggers let the automation read the event's payload — `{{ event.data.order_total }}` works in email body, `{{ event.data.products[0] }}` for the product name in subject, etc.

<details>
<summary><strong>Advanced: ecommerce webhook setups for Shopify, WooCommerce, Stripe</strong></summary>

The bridge between your store and AcelleMail is where most "post-purchase doesn't fire" bugs hide. Here are concrete patterns for the three most common platforms.

**Shopify → AcelleMail:**

```bash
# 1. In Shopify admin: Settings → Notifications → Webhooks → Create webhook
#    Event: Order creation
#    Format: JSON
#    URL: https://your-bridge.example.com/shopify-to-acelle

# 2. Your bridge service (Cloudflare Worker example):
addEventListener('fetch', event => {
  event.respondWith(handle(event.request))
})

async function handle(req) {
  if (req.method !== 'POST') return new Response('', { status: 405 })

  // Verify Shopify HMAC signature
  const body = await req.text()
  const sig = req.headers.get('x-shopify-hmac-sha256')
  const expected = await hmacSha256(body, env.SHOPIFY_WEBHOOK_SECRET)
  if (sig !== expected) return new Response('Bad signature', { status: 401 })

  const order = JSON.parse(body)

  // Push to AcelleMail
  await fetch('https://acellemail.com/api/v1/events', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + env.ACELLE_TOKEN,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      subscriber_email: order.email,
      event: 'purchase_completed',
      data: {
        order_total: order.total_price,
        currency: order.currency,
        products: order.line_items.map(l => l.sku),
      }
    })
  })

  return new Response('ok')
}
```

The `verify HMAC` step is critical — without it anyone can spoof a Shopify webhook and trigger your automation.

**WooCommerce → AcelleMail (via WordPress action hook):**

```php
add_action('woocommerce_order_status_completed', function($order_id) {
    $order = wc_get_order($order_id);
    $email = $order->get_billing_email();
    $total = $order->get_total();

    wp_remote_post('https://acellemail.com/api/v1/events', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLE_TOKEN,
            'Content-Type' => 'application/json',
        ],
        'body' => json_encode([
            'subscriber_email' => $email,
            'event' => 'purchase_completed',
            'data' => [
                'order_total' => $total,
                'products' => array_map(fn($i) => $i->get_product()->get_sku(), $order->get_items()),
            ]
        ]),
    ]);
});
```

Add to your theme's `functions.php` or a custom plugin.

**Stripe Subscriptions → AcelleMail:**

```bash
# Stripe sends 'customer.subscription.created' / 'customer.subscription.updated' / 'invoice.paid'
# Forward via Stripe webhook → your endpoint:

# Express.js example
app.post('/stripe-to-acelle', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, STRIPE_WEBHOOK_SECRET);
  } catch (err) { return res.status(400).send('webhook error'); }

  if (event.type === 'invoice.paid') {
    const invoice = event.data.object;
    await fetch('https://acellemail.com/api/v1/events', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + ACELLE_TOKEN,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        subscriber_email: invoice.customer_email,
        event: 'subscription_renewed',
        data: { amount: invoice.amount_paid / 100, currency: invoice.currency },
      }),
    });
  }

  res.json({received: true});
});
```

**Reverse-direction sync** (AcelleMail → CRM):

When the post-purchase flow's "review request" comes back with a 1-star NPS, you want a sales / customer-success ticket to open in your CRM. Use AcelleMail's automation **Send webhook** action:

```
[Form submission: NPS feedback]
  Condition: score <= 6
    [Send webhook: POST https://your-crm.example.com/tickets
       body: { customer_email: '{{ subscriber.email }}', issue: 'low_nps', score: '{{ form.nps_score }}' }]
```

Your CRM receives, opens a ticket, assigns to a CSM. The whole loop closes without anyone manually copy-pasting.

**Replenishment timing — when to nudge the next purchase:**

Most products have a "natural reorder cycle" — coffee at 4 weeks, supplements at 30 days, razors at 6 weeks. Set the cross-sell Wait step to ~80% of the typical cycle so the email arrives *before* they think about reordering, not after they've already gone to a competitor. For unknown products, default to 30 days and measure CTR by Wait-duration A/B test (split the audience: 21d vs 30d vs 45d, compare conversion).

**Test the full flow before launching:**

In AcelleMail, the automation **Test** button (or **Preview run**) lets you simulate a subscriber going through the entire flow with shortened wait times — useful before launching for real.

```bash
# Manual test from API:
curl -X POST "https://acellemail.com/api/v1/events" \
  -H "Authorization: Bearer $ACELLE_TEST_TOKEN" \
  -d '{
    "subscriber_email": "your-test-email@example.com",
    "event": "purchase_completed",
    "data": {"order_total": 87.50}
  }'
```

Then watch the automation logs to verify each step ran + when.

</details>

## Related articles

- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Using API triggers for custom automation workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows)
- [Lead scoring with email automation](https://acellesend.com/kb/articles/lead-scoring-with-email-automation)
- [Birthday and anniversary email automations](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations)

---

# List Hygiene in AcelleMail: Verify, Filter, Blacklist

> AcelleMail gives you three concrete tools to keep a list clean — per-list email verification, the Subscribers status filter, and a customer-managed Blacklist. This walkthrough shows when to use each and what good hygiene looks like in production.

- Canonical URL: https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: guide · Difficulty: intermediate
- Tags: Segmentation, List Cleaning
- Published: 2026-05-12 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Your sender reputation is the single biggest factor in whether your campaigns land in the inbox or in spam. Mailbox providers (Gmail, Yahoo, Outlook) judge every email against a rolling score of your bounces, complaints, and engagement — a dirty list quietly drags that score down for months before you notice the open-rate drop.

AcelleMail gives you **three concrete tools** to keep a list healthy without leaving the app: per-list **email verification**, the Subscribers tab's **Status + Verification filters**, and a customer-managed **Blacklist**. This guide shows where each one is, what it does, and the order to use them in.

## Step 1 — Verify every list at least once

The fastest payoff is a one-time verification pass on every list. AcelleMail runs each email through a verification server that checks DNS, mailbox existence, and known-bad patterns, then tags each subscriber with the result.

Open your list, switch to the **Email verification** tab in the top navigation. URL: `/rui/lists/<list-uid>/verification`.

![Email verification page for "Newsletter Subscribers" showing a "Keep your list clean" banner, 4 stat cards (1,711 Total subscribers, 1,094 Verified, 617 Unverified, "Not included / Credits available"), Verification controls section with a "Select verification server" dropdown showing "Awesome Solutions Verifier", and a green "Start verification" button next to a "Reset verification data" link](https://acellesend.com/plugins/acelle/landing/images/help/kb/list-hygiene/01-verification-overview.png)

What you see:

- **Stat cards at the top** — *Total / Verified / Unverified / Credits available*. The gap between Total and Verified is your work-in-progress. Credits are paid per address checked (your admin configures the verification server's pricing).
- **Select verification server** dropdown — your admin connects one or more verifiers (Athena EV is AcelleMail's first-party option; external services plug in here too). If the dropdown is empty, the verification server isn't configured yet — ask your admin to set one up under *Sending → Email verification*.
- **Start verification** — kicks off a background job that walks every unverified subscriber in this list. Closes the UI; the job continues. You can come back later and see progress.
- **Reset verification data** — clears all prior verification results. Use sparingly; you'll pay credits again to re-verify.

Click **Start verification**. The job runs in the background — close the tab and come back in 10–30 minutes depending on list size.

> **What does verification actually catch?** Four bucket-states are returned per address: *Deliverable* (safe to send), *Risky* (catch-all servers, role addresses), *Undeliverable* (rejected by the destination), and *Unknown* (couldn't determine). Treat *Risky* and *Undeliverable* as "do not send" until you've manually reviewed them.

## Step 2 — Filter Subscribers by Status + Verification

Once verification finishes, the Subscribers tab gets a lot more powerful. Switch to **Subscribers** in the list nav.

![Subscribers list with two filter dropdowns visible at top — "All statuses" and "All verification" — alongside Columns and a search box. Subscriber rows show two badge columns: Status (Blacklisted / Subscribed) and Verification (Unknown / Deliverable / Risky / —)](https://acellesend.com/plugins/acelle/landing/images/help/kb/list-hygiene/02-subscribers-statuses.png)

Two filter dropdowns at the top do the heavy lifting:

| Filter | Values it offers | What it means |
|---|---|---|
| **All statuses** | Subscribed · Unconfirmed · Unsubscribed · Bounced · Blacklisted | The subscription state — only *Subscribed* receives campaigns |
| **All verification** | Deliverable · Risky · Undeliverable · Unknown · Not verified | Verification verdict from Step 1 |

The row-level badges in the table reflect the same two columns. **Verification "—"** means the subscriber hasn't been run through verification yet — they'll be included in the next *Start verification* run.

**Combos worth bookmarking:**

| Filter combo | Use case |
|---|---|
| Status = Bounced | Subscribers AcelleMail has already auto-marked from bounce events — review and remove |
| Status = Subscribed + Verification = Undeliverable | Highest-priority cleanup — they're getting your campaigns but will bounce |
| Status = Subscribed + Verification = Risky | Borderline — keep for now, but exclude from high-volume sends |
| Status = Unconfirmed (older than 7 days) | Double opt-in dropouts who never confirmed |
| Status = Subscribed + Verification = Unknown | Run *Start verification* again |

Use the row checkboxes + bulk actions to move filtered subscribers in batches: **Move to blacklist**, **Delete**, or copy to another list.

## Step 3 — Maintain the customer Blacklist

When you blacklist a subscriber, they get permanently blocked from receiving any campaign you send — even if they're added back to a list later (e.g. via re-import). Open **Blacklist** in the left sidebar (under *Sending*). URL: `/rui/sending/blacklist`.

![Customer Blacklist management page with title "Manage your blacklist", description "Blacklisted email addresses are permanently blocked from receiving any campaigns. Use this to suppress bounced addresses, complainers, or any contacts you need to exclude.", action buttons + Add email and Import top right, and a "Blacklist is empty" placeholder in the middle](https://acellesend.com/plugins/acelle/landing/images/help/kb/list-hygiene/03-blacklist.png)

Three ways addresses land on the blacklist:

- **Automatic from bounces.** When AcelleMail's bounce handler classifies a hard bounce, the address auto-blacklists. You don't need to do anything.
- **Bulk add via Import.** Click **Import** top-right and paste a CSV of addresses you want to suppress (e.g. a contractor list, prior unsubscribes from another tool).
- **Manual add.** Click **+ Add email** for one-off blocks — useful when a complainer reaches out directly.

The blacklist is **account-wide** — once an address is on it, no list owned by your customer can send to it. That's the right default; you don't want a complainer accidentally re-added through a separate signup form.

> **Don't blacklist live engaged subscribers.** If someone just unsubscribed, they're already in the *Unsubscribed* status — campaigns skip them automatically. Only blacklist when you want a hard, permanent block (complaints, spam-trap suspects, legal requests).

## A practical hygiene schedule

| Task | Frequency | Where in AcelleMail |
|---|---|---|
| **Auto-remove hard bounces** | Continuous (AcelleMail does this) | Visible under Subscribers / Status = Bounced |
| **Full verification sweep on imported lists** | Before the first campaign | Email verification tab → Start verification |
| **Verification sweep on growing lists** | Quarterly | Same |
| **Audit Unconfirmed older than 7 days** | Weekly during DOI rollout, monthly steady-state | Subscribers / Status = Unconfirmed |
| **Audit "Subscribed + Undeliverable"** | After every verification sweep | Subscribers / Status=Subscribed & Verification=Undeliverable |
| **Re-engagement campaign on inactives** | Every 6 months | Build a segment (*Last opened email > 180 days*) + run a re-engagement campaign |
| **Manual blacklist additions** | As complaints arrive | Sending → Blacklist → + Add email |

Three signs your hygiene is paying off:

1. **Bounce rate per campaign trends down** below 2% hard bounce (visible on each campaign's report).
2. **Verified percentage** on the verification page climbs over time as you remove undeliverables.
3. **Inbox placement** (visible via Google Postmaster / Microsoft SNDS once you connect those) improves over the same period.

## Common issues

| What you see | What to do |
|---|---|
| Verification dropdown is empty | Your admin hasn't connected a verification server. Ask them to set one up under *Sending → Email verification* (admin view). |
| "Credits available" shows 0 | Verification servers charge per address. Buy credits or rotate to a server with remaining balance via the dropdown. |
| Verification job is stuck for hours | Open the **Verification results** card lower on the page — there's a *Stop verification* button to cancel. Then re-start. |
| Status column shows lots of "Bounced" but you've never sent | These were imported from a CSV that pre-flagged statuses, or AcelleMail processed bounces from a previous customer. Delete or blacklist as appropriate. |
| You verified the list but Subscribed campaigns still bounce | Verification is a snapshot. Addresses go stale — re-run quarterly. Also check that your **Sending domain** has DKIM/SPF set up; verification doesn't fix authentication problems. |
| Customer blacklist shows addresses you didn't add | They were auto-added by the bounce handler. That's the system working correctly; do not remove unless you can confirm the bounce was a one-off. |

## What NOT to do

- **Don't bulk-delete Bounced or Undeliverable subscribers from the list — blacklist them instead.** Deletion lets them re-enter on the next signup form submission. Blacklist makes the block permanent.
- **Don't run verification on every import day-one.** Verification servers cost per check. Plan your runs around campaign sends — verify a week before a big send, not on the day a single subscriber joins.
- **Don't ignore the *Risky* bucket.** Catch-all servers (`*@company.com` accepts everything) accept the email but never deliver it to a real human. Risky = unengaged = drag on your reputation.
- **Don't manually maintain a CSV of bounces outside AcelleMail.** Use the built-in *Bounced* status filter and the blacklist. Anything that lives in a spreadsheet is one rename away from being lost.

## Related articles

- [Import Contacts from CSV: A 3-Step Walkthrough](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Segments: Slice Your List Into Audiences That Convert](https://acellesend.com/kb/articles/advanced-segmentation-strategies)
- [Double Opt-In in AcelleMail](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose)
- [Re-engagement Campaigns: Win Back Inactive Subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# REST API Authentication and Endpoints

> The complete reference for AcelleMail's REST API — all resources, all routes, the per-endpoint request/response shapes, and the scoping rules. Use this alongside the getting-started guide; this article is the lookup table.

- Canonical URL: https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints
- Author: Tom Becker, Developer Editor
- Category: Developer Guide — REST API, webhooks, customization, extending source code, plugins
- Content type: reference · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-11 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The complete reference for AcelleMail's REST API. Use this alongside the [Getting Started guide](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — this article is the lookup table.

**Base URL:** `https://mail.example.com/api/v1/`
**Auth:** token (see below) — every authenticated route requires it
**Format:** request `Content-Type: application/json` (POST/PATCH/PUT) + `Accept: application/json` on every request

## Authentication

AcelleMail uses Laravel's `auth:api` driver — a simple bearer-token scheme.

### Get a token

Two methods:

- **UI:** Profile → API Token → Generate / Copy
- **API:** `POST /api/v1/user/login` (unauthenticated):
  ```json
  POST /api/v1/user/login
  email=you@example.com&password=Yours
  → 200 OK
  { "api_token": "..." }
  ```

### Send the token

Either is valid:

| Method | Format | Use when |
|---|---|---|
| Header (preferred) | `Authorization: Bearer <token>` | Production. Doesn't leak to access logs. |
| Query param | `?api_token=<token>` | Quick CLI tests. Avoid in production. |

### Scoping rules

A token is tied to one user, and inherits their scope:

- **Customer user token** → can read/write only resources owned by that customer (their lists, campaigns, subscribers, automations)
- **Admin user token** → can additionally hit admin-only endpoints (`/customers`, `/subscriptions`, `/sending_servers` create/edit)

You cannot impersonate another user. To act as a customer, log in as that customer and use *their* token.

## Conventions

- **UIDs vs IDs:** every resource has both an integer `id` (DB primary key) and a string `uid` (URL-safe, opaque). Routes use `uid` (e.g. `/lists/{uid}/subscribers`). Responses include both.
- **Pagination:** list endpoints return paginated responses with `data`, `current_page`, `per_page`, `total`, `last_page`. Use `?page=2&per_page=50` to navigate (max `per_page` typically 100).
- **Filtering:** most list endpoints support `?filter[field]=value` style filters; specific allowed filters vary per endpoint.
- **Errors:** standard Laravel error responses — `422` for validation, `404` for missing, `401` for auth, `403` for forbidden, `500` for server error. Body includes `message` + (on 422) `errors[]`.
- **Timestamps:** ISO 8601 UTC strings (`2026-05-16T14:32:11.000000Z`).

## Resources

### Public (unauthenticated)

#### Subscribers — public signup form

```
POST /api/v1/public/subscribers
Content-Type: application/json
{
  "MAIL_LIST_UID": "abc123",
  "EMAIL": "alice@example.com",
  "FIRST_NAME": "Alice",
  "LAST_NAME": "Doe",
  ...custom fields per the list schema...
}
→ 200 OK
{ "uid": "subUID", "status": "subscribed" }
```

The only public endpoint. Use it for signup forms on your marketing site — no token needed.

**Add rate limiting at the nginx level** to defend against spam bots (see [post-install hardening](https://acellesend.com/kb/articles/post-install-hardening-checklist)).

### Authentication + identity

| Endpoint | Method | Description |
|---|---|---|
| `/me` | GET | Current authenticated user + their customer |
| `/whoami` | GET | Lighter-weight identity check (id + email only) |
| `/user/login` | POST | Exchange email+password for an api_token (unauthenticated) |
| `/user/info` | GET | Same as `/me` (legacy alias) |
| `/dashboard` | GET | Customer's dashboard summary stats |

### Lists

| Endpoint | Method | Description |
|---|---|---|
| `/lists` | GET | List all subscriber lists owned by current customer |
| `/lists` | POST | Create a new subscriber list |
| `/lists/{uid}` | GET | Get a specific list |
| `/lists/{uid}` | PUT/PATCH | Update list fields |
| `/lists/{uid}` | DELETE | Delete a list (with all its subscribers) |
| `/lists/{uid}/add-field` | POST | Add a custom field to the list |

Create example:

```json
POST /api/v1/lists
{
  "name": "Newsletter Q2",
  "from_email": "news@example.com",
  "from_name": "Example Team",
  "default_subject": "Update from Example",
  "contact": {
    "company": "Example Co",
    "address_1": "123 Main St",
    "city": "San Francisco",
    "country_id": 234,
    "url": "https://example.com"
  }
}
→ 201 Created
{ "uid": "newListUID", "name": "Newsletter Q2", ... }
```

### Subscribers

| Endpoint | Method | Description |
|---|---|---|
| `/subscribers` | GET | List subscribers (paginated) — filter via `?list_uid=` |
| `/subscribers` | POST | Add a new subscriber to a list |
| `/subscribers/{id}` | GET | Get a subscriber by id |
| `/subscribers/{id}` | PATCH | Update subscriber fields |
| `/subscribers/{id}` | DELETE | Delete a subscriber |
| `/subscribers/email/{email}` | GET | Find subscriber by email address |
| `/lists/{list_uid}/subscribers/{id}/subscribe` | PATCH | Mark subscribed |
| `/lists/{list_uid}/subscribers/{id}/unsubscribe` | PATCH | Mark unsubscribed |
| `/lists/{list_uid}/subscribers/email/{email}/unsubscribe` | PATCH | Unsubscribe by email (no id lookup needed) |
| `/subscribers/{id}/add-tag` | POST | Tag a subscriber |
| `/subscribers/{id}/remove-tag` | POST | Untag a subscriber |

Add subscriber:

```json
POST /api/v1/subscribers
{
  "MAIL_LIST_UID": "abc123",
  "EMAIL": "alice@example.com",
  "FIRST_NAME": "Alice"
}
→ 201 Created
{ "uid": "subUID", "email": "alice@example.com", "status": "subscribed" }
```

### Campaigns

| Endpoint | Method | Description |
|---|---|---|
| `/campaigns` | GET | List campaigns (paginated) |
| `/campaigns` | POST | Create a new campaign |
| `/campaigns/{uid}` | GET | Get campaign details + stats |
| `/campaigns/{uid}` | PATCH | Update campaign fields (only allowed before "running") |
| `/campaigns/{uid}` | DELETE | Delete a campaign |
| `/campaigns/{uid}/run` | POST | Start sending the campaign |
| `/campaigns/{uid}/pause` | POST | Pause an in-progress send |
| `/campaigns/{uid}/resume` | POST | Resume a paused campaign |
| `/campaigns/{uid}/tracking-log/download` | GET | Download tracking event log (CSV) |
| `/campaigns/{uid}/open-log/download` | GET | Download open events (CSV) |
| `/campaigns/{uid}/click-log/download` | GET | Download click events (CSV) |
| `/campaigns/{uid}/bounce-log/download` | GET | Download bounce events (CSV) |
| `/campaigns/{uid}/feedback-log/download` | GET | Download spam-complaint events (CSV) |
| `/campaigns/{uid}/unsubscribe-log/download` | GET | Download unsubscribe events (CSV) |

### Automations

| Endpoint | Method | Description |
|---|---|---|
| `/automations` | GET | List automations |
| `/automations/{uid}/api/call` | POST | Trigger an "API trigger" inside an automation (start a subscriber down the flow) |
| `/automations/{uid}/execute` | POST | Manually fire the automation for a given subscriber |

The `api/call` endpoint is what powers the [API trigger feature](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — your external app POSTs to this URL to begin an automation flow for a specific subscriber.

### Sending servers

| Endpoint | Method | Description |
|---|---|---|
| `/sending_servers` | GET | List sending servers (admin only) |
| `/sending_servers` | POST | Create sending server (admin only) |
| `/sending_servers/{uid}` | GET / PATCH / DELETE | Standard resource ops |

### Customers (admin only)

| Endpoint | Method | Description |
|---|---|---|
| `/customers` | GET | List all customers |
| `/customers` | POST | Create a new customer |
| `/customers/{uid}` | GET / PATCH / DELETE | Standard resource ops |
| `/customers/by-email/{email}` | GET | Find customer by email |
| `/customers/{uid}/disable` | PATCH | Suspend the customer |
| `/customers/{uid}/enable` | PATCH | Reactivate |
| `/customers/{uid}/assign-plan/{plan_uid}` | POST | Assign a plan |
| `/customers/{uid}/change-plan/{plan_uid}` | POST | Change current plan |
| `/customers/{uid}/subscription/update` | POST | Update subscription metadata |
| `/login-token` | GET/POST | Generate a temporary login URL for impersonation (admin debugging) |

### Subscriptions (admin only)

| Endpoint | Method | Description |
|---|---|---|
| `/subscriptions` | GET / POST | List / create subscription records |
| `/subscriptions/{uid}` | GET / PATCH / DELETE | Standard resource ops |

### Notifications (sending-provider webhook inbound)

These endpoints **receive webhooks from the sending provider** (SES SNS, Mailgun, SendGrid event hooks) — you don't typically call them directly:

| Endpoint | Method | Description |
|---|---|---|
| `/notification/bounce` | POST | Sending provider posts bounce events here |
| `/notification/feedback` | POST | Sending provider posts complaint events here |

### File upload + management

| Endpoint | Method | Description |
|---|---|---|
| `/file/upload` | POST | Upload a file (for use in email templates / campaigns) |
| `/filemanager/list` | GET | List uploaded files |
| `/filemanager/upload` | POST | Upload to file manager |
| `/filemanager/delete` | DELETE | Delete uploaded file |
| `/filemanager/rename` | PATCH | Rename |
| `/filemanager/view` | GET | Read file contents |

### System / admin

| Endpoint | Method | Description |
|---|---|---|
| `/plugins/install` | POST | Install a plugin from zip (admin only) |
| `/upgrade/run` | POST | Run an upgrade from a URL |
| `/upgrade/run-file` | POST | Run an upgrade from an uploaded `.bin` patch file |
| `/upgrade/finalize` | POST | Run post-upgrade tasks |
| `/license/refresh` | POST | Re-check the CodeCanyon license |

## Common patterns

### Paginated list

```bash
# Get page 2, 50 per page
curl -H "Authorization: Bearer $TOKEN" \
  "https://mail.example.com/api/v1/subscribers?page=2&per_page=50&filter[list_uid]=abc123"
```

### Create then add many subscribers

```bash
# 1. Create the list
LIST_UID=$(curl -s -X POST ... /api/v1/lists -d '...' | jq -r .uid)

# 2. Bulk-add subscribers (loop or batch via the import flow for > 1000)
for email in $(cat emails.txt); do
  curl -X POST ... /api/v1/subscribers \
    -d "MAIL_LIST_UID=$LIST_UID&EMAIL=$email"
done
```

For > 1000 subscribers, use the [list-import flow](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping) — much faster than per-subscriber POST.

### Send a one-off campaign via API

```bash
# 1. Create the campaign
CAMP_UID=$(curl -s -X POST ... /api/v1/campaigns -d '{
  "name": "Q2 Newsletter",
  "type": "regular",
  "subject": "Our June updates",
  "from_email": "news@example.com",
  "from_name": "Example",
  "html_content": "<h1>Hi {FIRST_NAME}</h1>...",
  "mail_list_uid": "abc123",
  "sign_dkim": true,
  "track_open": true,
  "track_click": true
}' | jq -r .uid)

# 2. Start sending
curl -X POST -H "Authorization: Bearer $TOKEN" \
  "https://mail.example.com/api/v1/campaigns/$CAMP_UID/run"
```

### Trigger an automation

```bash
# Customer X just bought a product; start them on the post-purchase flow
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "EMAIL": "buyer@example.com",
    "FIRST_NAME": "Buyer",
    "product_name": "Widget Pro",
    "purchase_amount": 99.00
  }' \
  https://mail.example.com/api/v1/automations/AUTOMATION_UID/api/call
```

The custom fields (`product_name`, `purchase_amount`) are available as merge tags inside the automation's emails.

## Error reference

| HTTP code | Meaning | Common cause |
|---|---|---|
| `200` | OK | Success |
| `201` | Created | Resource created (POST) |
| `204` | No Content | Success with no response body |
| `400` | Bad Request | Malformed JSON, missing required header |
| `401` | Unauthenticated | Missing/expired token |
| `403` | Forbidden | Token valid but lacks permission (e.g. customer trying admin endpoint) |
| `404` | Not Found | UID/email not found, or endpoint typo |
| `415` | Unsupported Media Type | Missing `Accept: application/json` header |
| `422` | Unprocessable Entity | Validation failed; response includes `errors[]` per-field |
| `429` | Too Many Requests | Rate limit hit (if configured) |
| `500` | Internal Server Error | Server bug; check `storage/logs/laravel.log` |

A typical `422` looks like:

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "EMAIL": ["The EMAIL field is required."],
    "MAIL_LIST_UID": ["The selected MAIL_LIST_UID is invalid."]
  }
}
```

## Tips for production use

- **Log every API call's request id** (Laravel emits one per request in the response headers) for traceable debugging
- **Set request timeout** in your client to ≥ 30 seconds — bulk operations (large list creation, campaign run) can take that long
- **Retry on 5xx with exponential backoff**; don't retry on 4xx (it'll just fail again)
- **Never log the token itself** — sanitize before logging headers
- **Rotate the token quarterly** by regenerating in the UI

## FAQ

**Is the API stable across Acelle versions?** v1 has been stable across multiple major releases. Additive changes (new fields, new endpoints) are common; breaking changes are rare and pre-announced in [acellemail-updates](https://acellesend.com/kb/category/acellemail-updates).

**Is there an OpenAPI / Swagger spec?** Not officially shipped. Build by introspecting `routes/api.php` and generating from controller signatures.

**Does the API support GraphQL?** No — REST only.

**Can the API run a campaign for me without an existing list?** No — campaigns must reference a `mail_list_uid` that already exists. Create the list first.

**What's the difference between `id` and `uid`?** `id` is the DB primary key (integer); `uid` is the URL-safe string identifier. Always use `uid` in routes; you'll see both in responses.

**Webhooks?** Different system from the API. See [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) for outbound events your app can subscribe to.

## Related articles

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — the 5-minute kickoff (start here if you haven't)
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — outbound events
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — when the API isn't enough
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — the `/automations/{uid}/api/call` use case
- [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) — no-code API client
- [Importing Contacts: CSV Best Practices and Field Mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping) — for bulk subscriber adds
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — per-customer API tokens
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — securing the API surface

---

# Abandoned Cart Sequence in AcelleMail (WooCommerce Trigger)

> AcelleMail has a built-in "Abandoned Cart Reminder" trigger card — WooCommerce-specific via the vbrandsync plugin. Build a 3-email sequence (1h / 24h / 72h) in the visual flow builder. No webhook coding required.

- Canonical URL: https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-09 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

About 70% of e-commerce carts get abandoned. A 3-email sequence — *gentle reminder → objection handling → urgency/incentive* — typically recovers 3-8% of those carts. That's revenue you'd otherwise lose, automatically.

AcelleMail has a **built-in Abandoned Cart Reminder trigger** for WooCommerce-backed stores. This walkthrough builds the 3-email sequence in the visual flow builder, end to end.

## Before you start

This automation is **WooCommerce-specific**. AcelleMail's abandoned-cart trigger fires when WC reports a cart abandonment via the AcelleMail sync plugin. Required:

- **A WooCommerce site** running on WordPress
- **The AcelleMail sync plugin** installed on that WP site (handles the cart → AcelleMail bridge)
- **A connected mail list** receiving WC subscribers' contact info

If you're on Shopify / BigCommerce / a custom-built cart, this trigger doesn't fire. The alternative there is a **Contact Attribute Update** or **API 3.0** trigger driven by your own webhook — see [Advanced triggers](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions).

## Step 1 — Pick the Abandoned Cart Reminder trigger

Open **Automation → + New Automation**. The 12-trigger picker appears.

![Create Automation trigger picker showing 12 trigger cards: Welcome new subscribers, Say goodbye to subscriber, Say 'Happy birthday', Subscriber added date, Specific date, API 3.0, Weekly recurring, Monthly recurring, Tag based, Remove Tag, Contact Attribute Update, and "Abandoned Cart Reminder — Send follow-up email to someone who has added items to their cart and left the site without purchasing." (bottom right card)](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)

(Screenshot from [Build a Welcome Email Series](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) — same trigger picker.)

Click **Abandoned Cart Reminder** (bottom right). Then click the **Configure** tab and pick the mail list that mirrors your WC customer/subscriber list.

## Step 2 — Lay out the 3-email backbone

The standard 3-email cadence:

| Email | Timing | Job | Tone |
|---|---|---|---|
| **Reminder** | 1 hour after abandonment | Gentle nudge, assume positive intent | Helpful |
| **Objection handler** | 24 hours after | Address shipping / returns / security concerns | Informative |
| **Urgency + incentive** | 72 hours after | Discount code or urgency hook | Persuasive |

In the visual flow builder, that's:

```
TRIGGER  → Abandoned Cart Reminder
WAIT     → 1h
SEND EMAIL → "You left something behind" (Reminder)
WAIT     → 23h    (total 24h since trigger)
SEND EMAIL → "Questions about your order?" (Objection handler)
WAIT     → 48h    (total 72h since trigger)
SEND EMAIL → "{{first_name}}, your cart expires soon" (Urgency)
```

You build this by clicking the **+** on each node's downstream arrow and picking *Wait* or *Send Email*. See the worked example in [Welcome Series article §3](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) for what the canvas looks like with these node types wired up.

## Step 3 — Stop the sequence when they convert

The single most important rule of abandoned-cart sequences: **stop sending the moment the customer buys.** Nothing is worse than a discount email landing 2 hours after they paid full price.

The pattern in AcelleMail:

1. **In WooCommerce / your sync plugin**, configure a webhook to add the tag `purchased-recent` to a subscriber the moment their order completes.
2. **In the automation flow**, after each WAIT and before each SEND EMAIL, add a **CONDITION** node:

```
CONDITION → Has tag "purchased-recent"?
            ├─ YES → END (exit the flow)
            └─ NO  → continue to next SEND EMAIL
```

This conditional check costs you 3 extra nodes (one per email), but it pays for itself the first time it catches a purchaser mid-flow.

## Step 4 — Compose each email

Each SEND EMAIL node opens a side panel where you pick the email's template (or design it inline). Here's what each one should actually say.

### Email 1 — Reminder (sent 1h after abandonment)

```
Subject:    You left something behind
Preheader:  Your cart is saved — pick up where you left off.
Body:       Hi {{first_name}},

            It looks like you left something in your cart. Life gets
            busy — we get it.

            [Product image from WC]
            [Product name + price]

            [Return to your cart] — button

            Your cart is saved and waiting for you.
```

**Do NOT discount in Email 1.** You don't know why they left — maybe they were just distracted. Offering a discount immediately trains future shoppers to abandon for a discount.

### Email 2 — Objection handler (sent 24h after)

```
Subject:    Questions about your order?
Preheader:  Free returns, secure checkout, and we're here to help.
Body:       Hi {{first_name}},

            Still thinking it over? Here's what people ask us most:

              • Shipping: Free on orders over $50
              • Returns: 30-day, no questions asked
              • Payment: Stripe, PayPal, all major cards

            [Product image]
            ⭐⭐⭐⭐⭐ "Best purchase I made this year." — Sarah M.

            [Complete my purchase] — button
```

Use real social proof — pull testimonials specific to the abandoned product if your data allows.

### Email 3 — Urgency + incentive (sent 72h after)

```
Subject:    {{first_name}}, your cart expires soon
Preheader:  Here's 10% off — because we want you to love this product.
Body:       Hi {{first_name}},

            Your saved cart expires in 24 hours. So we're sending one
            last nudge — with a small thank-you for sticking around.

            Use code WELCOME10 at checkout for 10% off.

            [Product image with sale price highlighted]

            [Claim my discount] — button

            (Only 3 left in stock at this price.)
```

**Reserve the discount for Email 3 only.** Customers who would have paid full price already converted from Email 1 or 2; the discount is for the long-tail holdouts.

## Step 5 — Activate and monitor

After wiring nodes:

1. Click **Settings** (top right of the builder)
2. Confirm the connected list
3. Toggle to **Active**
4. The status badge flips to **ACTIVE** at the top of the canvas

From the Automations index, click **Statistics** on this automation's row to see:

- How many subscribers entered the flow this week
- Per-email open / click rates
- Conversion rate (subscribers who bought during or after the sequence)

> **Test before activating.** Add an item to your own cart in WC, walk away for 1+ hour, and confirm Email 1 arrives. If it doesn't, the WC↔AcelleMail sync isn't wired up correctly. Check the sync plugin's logs.

## Cadence — why these specific intervals?

The 1h / 24h / 72h cadence isn't arbitrary:

| Interval | Why |
|---|---|
| **1 hour** | Catches the "I'll come back to this" abandoners. Long enough they've moved on; short enough they still remember. |
| **24 hours** | Next-day check-in feels like a real human touchpoint, not a sales push. |
| **72 hours** | Three days is the practical window before they've fully forgotten or bought elsewhere. Beyond 96h, recovery rate drops to near-zero. |

Push the second send to 36h or 48h if your audience is B2B / weekday-only. Pull the first send to 30 minutes if you have a flash-sale product where urgency is the value prop.

## Common issues

| What you see | What to do |
|---|---|
| Trigger never fires | The WC sync plugin isn't reporting cart events. Check the plugin's settings page → "Cart abandonment tracking" toggle + the connected AcelleMail mail list. |
| Customer received Email 3 after buying | The `purchased-recent` tag isn't getting added on order completion, OR the CONDITION nodes aren't checking it. Audit the WC webhook + each SEND EMAIL's upstream CONDITION. |
| Subscribers from non-WC lists are entering | The trigger is global to the connected list. If a non-WC subscriber lands in that list via signup form, they're eligible too. Filter via list, OR add an upstream CONDITION checking for a WC-specific custom field. |
| Email 1 shows generic placeholder instead of the abandoned product | The WC sync plugin's product-meta passing isn't configured. Open the sync plugin → custom fields mapping → confirm `cart_product_name` / `cart_product_url` / `cart_product_price` are mapped to AcelleMail subscriber fields. |
| Discount code in Email 3 doesn't work | The code in the email body is hard-coded; you need to generate a unique-per-customer code via the WC discount-codes plugin and pass via a `{{subscriber.discount_code}}` merge tag. |
| Recovery rate is below 1% | Either the sync isn't tracking real abandonments (likely most "abandons" are bots/scrapers), OR your Email 3 discount is too small to motivate. Bump to 15% for one test cycle and compare. |

## What NOT to do

- **Don't send more than 3 emails.** 4-5 emails over a week tips into harassment. If they didn't buy by Email 3, accept the loss and move on.
- **Don't make the urgency fake.** *"Cart expires in 1 hour!"* — when the cart actually persists for weeks — destroys trust. Either implement real cart expiry in WC, or use *"This price won't last forever"* phrasing.
- **Don't include the cart contents in plain text only.** Subscribers expect to see the product image. Confirm your sync plugin passes the product image URL.
- **Don't pause the automation during sales.** Pausing pulls every in-flight subscriber out of the flow. They lose any pending discount. Edit the discount instead.

## Related articles

- [Build a Welcome Email Series in AcelleMail (Visual Flow Builder)](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Post-Purchase Follow-Up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation)
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Send Your First Campaign: 5-Step Wizard Walkthrough](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail)

---

# Setting Up Queue Workers and Cron Jobs

> AcelleMail uses Laravel queues + a system cron to do everything beyond rendering web pages — sending campaigns, processing automations, handling bounces, refreshing stats, dispatching scheduled sends. This guide walks the production-correct two-tier supervisor split (master + worker), the cron schedule, and the operational discipline that keeps the queue healthy under load.

- Canonical URL: https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs
- Author: Daniel Reyes, Technical Editor
- Category: Server Management — Cron jobs, queues, Supervisor, Redis, Nginx, performance tuning
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-05-03 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail uses Laravel queues + a system cron to do everything beyond rendering web pages — **sending campaigns, processing automations, handling bounces, refreshing stats, dispatching scheduled sends, verifying senders, monitoring subscriptions**. If the queue workers aren't running or the cron isn't firing, the web UI looks fine but nothing actually happens.

This guide walks the production-correct setup as Acelle's own scaffolding defines it: a **two-tier supervisor split** with different queue priorities, a system cron line, and the operational discipline to keep it healthy.

**Prerequisites:** a working AcelleMail install per the [Ubuntu 24.04 canonical guide](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) (Steps 1-7 are enough; this article details Step 8 — supervisor — and Step 9 — cron).

## Step 1 — System cron

This is the simpler one. Add a single one-minute cron line, run as the web user:

```bash
sudo crontab -u www-data -e
# Add:
* * * * * cd /var/www/acellemail && php artisan schedule:run >> /dev/null 2>&1
```

> **Rocky / RHEL:** `sudo crontab -u nginx -e` instead of `www-data`.

The Laravel scheduler runs once per minute and fans out to AcelleMail's recurring tasks. Per `routes/console.php`, these are what AcelleMail actually schedules:

| Command | Frequency | What it does |
|---|---|---|
| `campaign:schedule` | every minute | Picks up scheduled campaigns whose send time has arrived; dispatches send jobs to the queue |
| `automation:dispatch` | every 5 minutes | Evaluates active automation workflows; enqueues "trigger" jobs for subscribers who match conditions |
| `handler:run` | every 30 minutes | Bounce + FBL handler — polls the configured IMAP/SES/SNS feedback endpoints, parses bounces, marks subscribers undeliverable |
| `sender:verify` | every 5 minutes | Checks pending domain verifications (SPF/DKIM/DMARC); marks them verified once DNS propagates |
| `queue:adjust` | every minute | Auto-scaler — launches up to 20 extra worker processes if priority-queue depth crosses a threshold |
| `campaign:rerun` | every 10 minutes | Resumes campaigns that were paused due to transient failures (e.g. quota exhausted, sending server temporarily unreachable) |
| `geoip:check` | every minute | GeoIP database refresh check (low-cost; only acts when a new database is available) |
| `system:cleanup` | daily | Prunes old log entries, expired sessions, finished one-shot jobs from `jobs` table |
| `subscription:monitor` | hourly | Reconciles SaaS subscriptions with the payment provider (catches missed webhooks) |
| `update_list_stats` | daily | Recomputes list-level subscriber counts / engagement stats |

**Verify cron is wired:**

```bash
sudo -u www-data crontab -l | grep schedule:run
# Should print the cron line.

# Watch the scheduler tick in real-time (Laravel logs to storage/logs/laravel.log):
sudo tail -f /var/www/acellemail/storage/logs/laravel.log
# After 60 seconds you should see the scheduler entries.
```

If cron isn't firing, the most common cause is the wrong user — the cron must be in `www-data`'s crontab (Ubuntu/Debian) or `nginx`'s (Rocky), not your sudo user's.

## Step 2 — Supervisor (the two-tier split)

Supervisor keeps the queue worker processes alive — restart on crash, restart on deploy, automatic startup on boot.

```bash
sudo apt install -y supervisor
sudo systemctl enable --now supervisor
```

> **Rocky:** `sudo dnf install -y supervisor && sudo systemctl enable --now supervisord` — note the trailing `d` in the service name.

### Why two tiers, not one big pool

Acelle ships two distinct supervisor configs (`resources/documents/supervisor_master_config.tmpl` + `supervisor_worker_config.tmpl`) for a reason: **import jobs are slow and CPU-heavy** (parsing 200k-row CSVs, deduping against existing subscribers), while **send jobs are fast and I/O-bound** (one HTTP call to SES per email). Mixing them in a single pool means a single import job blocks 5 send jobs behind it — sending throughput collapses during list imports.

The fix: dedicate a small pool (2 workers) to `import,default` queues, and a larger pool (15 workers) to the send + automation queues.

### Master pool — for imports + default

```bash
sudo tee /etc/supervisor/conf.d/acellemail-master.conf <<'CONF'
[program:acellemail-master]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/acellemail/artisan queue:work --queue=import,default --tries=1 --max-time=180
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/acellemail-master.log
stopwaitsecs=3600
CONF
```

### Worker pool — for sends + automations

```bash
sudo tee /etc/supervisor/conf.d/acellemail-worker.conf <<'CONF'
[program:acellemail-worker]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/acellemail/artisan queue:work --queue=high,batch,single,automation,automation-dispatch --tries=1 --max-time=180
autostart=true
autorestart=true
user=www-data
numprocs=15
redirect_stderr=true
stdout_logfile=/var/log/supervisor/acellemail-worker.log
stopwaitsecs=3600
CONF
```

### Apply + start

```bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start acellemail-master:* acellemail-worker:*
sudo supervisorctl status
# Expect: 2 master processes + 15 worker processes, all RUNNING
```

> **Rocky:** drop the configs at `/etc/supervisord.d/*.ini` (not `conf.d/*.conf`) and adjust the `user=` to `nginx`.

### Why `--tries=1 --max-time=180`?

These are Acelle's chosen defaults from the shipped templates, and they're right:

- `--tries=1` — a single attempt per job. Email-sending jobs aren't safely retryable (you might double-send to a subscriber); Acelle handles retries explicitly at the campaign level for the few cases where it's safe.
- `--max-time=180` — each worker exits after 180 seconds. Supervisor immediately restarts it. This bounds memory growth (PHP doesn't always release memory cleanly across requests) and ensures workers pick up any code changes from the most recent deploy within 3 minutes.

If you increase `--max-time`, you increase the worst-case memory footprint per worker. Don't go above 600s unless you've profiled memory.

## Step 3 — Sizing the pools for your workload

The 2 + 15 default from Acelle's templates is the **Medium tier** baseline (4 vCPU / 8 GB RAM). Adjust per your hardware:

| Tier | Master numprocs | Worker numprocs | Server (per [server-reqs](https://acellesend.com/kb/articles/server-requirements-and-hosting-options)) |
|---|---|---|---|
| Hobby | 1 | 2 | 1 vCPU / 2 GB |
| Small | 1 | 4 | 2 vCPU / 4 GB |
| Medium | 2 | 15 | 4 vCPU / 8 GB |
| Large | 4 | 30 | 8 vCPU / 16 GB |

**Memory math:** each worker peaks at ~256-512 MB on heavy campaigns. With `numprocs=15`, worst case is ~7.5 GB just for workers — that's why Medium tier is 8 GB RAM minimum. If your server can't fit the recommended pool, scale down (and accept slower throughput) rather than OOM-killing workers mid-campaign.

**CPU math:** each worker is mostly I/O-bound (waiting for the sending API), so they share CPU cores well. 15 workers on 4 vCPU is fine; 30 workers on 4 vCPU starts to context-switch-thrash.

**Auto-scaling:** the `queue:adjust` scheduled command launches up to 20 *extra* worker processes when the priority-queue depth crosses a threshold (defined per-campaign via the "Priority" setting in admin). This is opportunistic, not a substitute for sizing the steady-state pool correctly.

## Step 4 — Restart on deploy

Every code deploy (patch upgrade, manual git pull, Docker image rebuild) requires restarting all workers — otherwise they keep running the old code from when they started:

```bash
sudo supervisorctl restart acellemail-master:* acellemail-worker:*
```

Make this the **last step of every deploy script**. The `stopwaitsecs=3600` in the configs gives in-flight jobs up to an hour to finish gracefully before being killed (campaigns in progress aren't interrupted).

## Step 5 — Monitoring

The five things worth watching:

```bash
# 1. Workers running?
sudo supervisorctl status
# All entries should be RUNNING; any FATAL or BACKOFF needs investigation

# 2. Queue depth (Redis driver)
redis-cli llen queues:default
redis-cli llen queues:high
redis-cli llen queues:import
redis-cli llen queues:automation
# Steady-state should be 0-100. Sustained > 1000 means workers are
# under-provisioned for the load. Spiking > 10k during a campaign is
# normal; should drain within a few minutes.

# 3. Failed jobs (one-time check + ongoing)
cd /var/www/acellemail && sudo -u www-data php artisan queue:failed
# Should be empty on a healthy install

# 4. Worker logs
sudo tail -f /var/log/supervisor/acellemail-worker.log
# Watch for "Failed:" or "Error:" lines

# 5. Cron last run (via Laravel scheduler log)
sudo tail -50 /var/www/acellemail/storage/logs/laravel.log | grep -E "Scheduled|cronjob"
```

For production, wire these into your monitoring stack:

- Redis queue depths via a 1-minute Prometheus exporter or DataDog integration
- Worker count via `supervisorctl status | grep -c RUNNING` — alert if < expected
- Failed-jobs count → daily Slack notification

## Step 6 — Diagnose common failures

| Symptom | Likely cause | Diagnostic + fix |
|---|---|---|
| Campaigns stuck in "Sending" status | All workers crashed; or 0 workers running | `sudo supervisorctl status`; restart with `sudo supervisorctl restart acellemail-worker:*` |
| Imports timing out | Master pool exhausted (only 2 procs by default) | Temporarily bump master `numprocs` to 4 during the import window; revert after |
| Automations not firing | `automation:dispatch` not running (cron broken) | Verify cron user = `www-data`; check Laravel scheduler log entries |
| Failed jobs accumulating | Sending server credentials wrong, or quota exhausted | `php artisan queue:failed`; inspect each failed payload; fix the underlying cause; `php artisan queue:retry all` only after fixing |
| New code not taking effect | Workers running old code in-memory | `sudo supervisorctl restart acellemail-master:* acellemail-worker:*` |
| Worker logs flooded with "Connection refused" | MySQL or Redis down/restarting | `sudo systemctl status mysql redis-server`; check if a recent restart killed connections |
| `supervisorctl: command not found` | Supervisor not installed | `sudo apt install -y supervisor && sudo systemctl enable --now supervisor` |
| Workers eat all RAM | `--max-time` too long or memory leak | Drop `--max-time` to 120; check `storage/logs/laravel.log` for hints; profile with `htop` |

## FAQ

**Can I use systemd instead of supervisor?** Technically yes — systemd has `Restart=always` and can manage worker pools. Operationally, supervisor's `numprocs` + per-process logs + `supervisorctl restart group:*` ergonomics are noticeably better for queue-worker management. Stick with supervisor unless you have a specific reason not to.

**Why not use Laravel Horizon?** Horizon is a great web UI for Redis-backed queues — auto-balancing, real-time dashboard, failed-job UI. It's not bundled with AcelleMail; you'd install it as a Composer dependency. **It works alongside Acelle's supervisor setup** but doesn't replace the supervisor configs (Horizon needs supervisor to run *its* main process). If you're operating multiple AcelleMail installs or you want better visibility, Horizon is worth the setup overhead.

**What happens if a worker dies mid-job?** Supervisor restarts it within seconds. The job that was in-flight is re-enqueued only if it's marked as such by Laravel — with `--tries=1`, it'll instead show up in `failed_jobs`. For email-sending jobs this is correct behaviour (better one missed send than a duplicate).

**Can I run workers on a separate server?** Yes — point them at the same Redis + MySQL as the web server. This is the standard pattern at Large tier — see [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day).

**Why does `queue:adjust` use a `custom01` queue I've never heard of?** It's an opt-in priority queue for campaigns marked "Priority" in admin. If you never use the priority setting, `queue:adjust` is a no-op (it sees no jobs on `custom01` and exits). Keep the scheduled command — it's cheap and the feature is useful.

**What queue driver should I use?** Redis (`QUEUE_CONNECTION=redis` in `.env`). The database driver works but creates excess load on MySQL — at any reasonable send volume, Redis is significantly faster. See [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing).

**Can I skip supervisor and just run workers in a screen/tmux session?** For development, yes. For production, no — they'll die when the session disconnects and won't restart on reboot. Supervisor exists for exactly this.

## Related articles

- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — sets up the prerequisites (this is Step 8 + 9 of that guide, expanded)
- [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing) — Redis tuning (recommended queue driver)
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — companion ops task
- [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) — when to add more workers + split off DB
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — secure the queue + cron infra
- [Docker Deployment Guide for AcelleMail](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) — Docker equivalent of supervisor (compose services)
- [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) — pool sizing reference table

---

# Docker Deployment Guide for AcelleMail

> Containerised AcelleMail trades a small ergonomic cost (volumes, image rebuilds, patch flows) for dev/prod parity and clean image-tag rollback. This guide ships a production-ready Dockerfile (with all wizard-required PHP extensions baked in), a docker-compose.yml stack, and the operational recipes for patches, scaling, logs, and backups.

- Canonical URL: https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-05-02 · Last updated: 2026-08-15
- Reading time: 13 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Containerised AcelleMail trades a small ergonomic cost (you have to think about volumes, image rebuilds, and patch flows) for two big wins: **parity between dev and prod environments**, and **clean rollback by image tag** rather than by manual file restoration.

The fundamental trick: AcelleMail wasn't built container-first. The patch-upgrade flow assumes file mutations on the live filesystem (the `/upgrade/run-file` API replaces files in `/var/www/acellemail` in place). In Docker, this means **the AcelleMail code lives in a named volume, not baked into the image** — otherwise every patch would be wiped on the next `docker compose up`.

This guide ships a production-ready stack — a custom `Dockerfile` (so the wizard System Check passes), a `docker-compose.yml`, and the operational recipes for patches, scaling, logs, and backups.

**👉 Bare-metal alternative: [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404)** — simpler if you're not already operating Docker.

## Step 1 — Custom Dockerfile (the wizard fix)

The stock `php:8.3-fpm` image ships with **only the bare PHP core** — no imap, no sqlite3, no mailparse, no gd, no intl, no redis client. The AcelleMail install wizard's System Check hard-fails on any of those.

Save as `php/Dockerfile`:

```dockerfile
FROM php:8.3-fpm AS base

# System libs needed by the PHP extensions below
RUN apt-get update && apt-get install -y --no-install-recommends \
        libpng-dev libjpeg62-turbo-dev libfreetype6-dev \
        libicu-dev libonig-dev libxml2-dev libzip-dev \
        libc-client-dev libkrb5-dev libsqlite3-dev libgmp-dev \
        zlib1g-dev libssl-dev pkg-config \
    && rm -rf /var/lib/apt/lists/*

# imap needs --with-imap-ssl=yes
RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
 && docker-php-ext-configure gd --with-freetype --with-jpeg \
 && docker-php-ext-install -j$(nproc) \
        pdo_mysql mysqli mbstring xml curl zip gd intl \
        imap gmp bcmath pdo_sqlite exif

# PECL extensions
RUN pecl install redis mailparse \
 && docker-php-ext-enable redis mailparse

# AcelleMail-specific php.ini tweaks (workload, not OS)
RUN { \
        echo 'memory_limit = 512M'; \
        echo 'upload_max_filesize = 300M'; \
        echo 'post_max_size = 300M'; \
        echo 'max_execution_time = 300'; \
    } > /usr/local/etc/php/conf.d/zz-acellemail.ini

# Composer (used during /upgrade/run-file patches)
RUN curl -fsSL https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Cron, supervisor, useful utilities
RUN apt-get update && apt-get install -y --no-install-recommends \
        cron supervisor unzip \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /var/www/acellemail
```

This image takes ~6 minutes to build first time, ~30 seconds with the apt + pecl layers cached. Tag it `acellemail/php:8.3-fpm` and push to your private registry, or build locally with `docker compose build app worker scheduler`.

> **Why not `serversideup/php` or `webdevops/php-fpm`?** Both are excellent third-party images that include most of these extensions. The reason to roll your own is **supply chain control** — every byte in this Dockerfile is from PHP upstream or Debian main. Pin to specific versions if you're regulated.

## Step 2 — The compose file

Save as `/srv/acellemail/docker-compose.yml`:

```yaml
name: acellemail
services:
  app:
    build:
      context: .
      dockerfile: php/Dockerfile
    image: acellemail/php:8.3-fpm
    restart: unless-stopped
    working_dir: /var/www/acellemail
    volumes:
      - acellemail_code:/var/www/acellemail
    depends_on: [mysql, redis]
    networks: [internal]

  nginx:
    image: nginx:1.27-alpine
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    volumes:
      - acellemail_code:/var/www/acellemail:ro
      - ./nginx/acellemail.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    depends_on: [app]
    networks: [internal, edge]

  mysql:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: acellemail
      MYSQL_USER: acellemail
      MYSQL_PASSWORD_FILE: /run/secrets/mysql_pw
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_pw
    volumes:
      - mysql_data:/var/lib/mysql
    secrets: [mysql_pw, mysql_root_pw]
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    networks: [internal]

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes: [redis_data:/data]
    networks: [internal]

  worker:
    image: acellemail/php:8.3-fpm
    restart: unless-stopped
    working_dir: /var/www/acellemail
    command: ["php", "artisan", "queue:work", "--sleep=3", "--tries=3", "--max-time=3600"]
    volumes:
      - acellemail_code:/var/www/acellemail
    depends_on: [mysql, redis]
    deploy: { replicas: 2 }
    networks: [internal]

  scheduler:
    image: acellemail/php:8.3-fpm
    restart: unless-stopped
    working_dir: /var/www/acellemail
    entrypoint: ["sh", "-c"]
    command: ["while :; do php artisan schedule:run; sleep 60; done"]
    volumes:
      - acellemail_code:/var/www/acellemail
    depends_on: [mysql, redis]
    networks: [internal]

volumes: { acellemail_code: {}, mysql_data: {}, redis_data: {} }
networks: { internal: {}, edge: {} }
secrets:
  mysql_pw: { file: ./secrets/mysql_pw.txt }
  mysql_root_pw: { file: ./secrets/mysql_root_pw.txt }
```

Each design decision is a battle scar:

- **Code lives in a named volume.** `acellemail_code` is shared read-write with `app` + `worker` + `scheduler`, read-only with `nginx`. The first `docker compose up` populates the volume from the unzipped install bundle (Step 3); patches mutate it in place.
- **Workers and scheduler are separate services.** Mixing the queue worker into the FPM container is tempting and broken — when FPM restarts (image upgrade, OOM) the worker also dies, in-flight jobs become orphaned, and the scheduler stops firing. Separate services restart independently.
- **Two worker replicas.** Same Small-tier sizing as bare-metal. Bump to 4 at Medium tier.
- **MySQL secrets via files, not env vars.** Compose has supported file-backed secrets for years; using them is one less audit-log entry showing the password in plain text.
- **Nginx mounts code read-only.** If PHP-FPM is compromised it can write the volume, but the public-facing nginx cannot.

The nginx config at `nginx/acellemail.conf`:

```nginx
server {
    listen 80;
    server_name mail.example.com;
    root /var/www/acellemail/public;
    index index.php;
    client_max_body_size 300M;

    location / { try_files $uri $uri/ /index.php?$query_string; }
    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300;
    }
    location ~ /\.(?!well-known).* { deny all; }

    # Certbot HTTP-01 challenge
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
}
```

## Step 3 — Initial install (one-time)

```bash
mkdir -p /srv/acellemail/{php,nginx,certbot/conf,certbot/www,secrets}
cd /srv/acellemail

# Drop in the Dockerfile, compose file, and nginx config from Steps 1-2

# Generate MySQL secrets
openssl rand -base64 32 > secrets/mysql_root_pw.txt
openssl rand -base64 32 > secrets/mysql_pw.txt
chmod 600 secrets/*.txt

# Build the custom PHP image
docker compose build app worker scheduler

# Bring up MySQL + Redis first, wait for healthy
docker compose up -d mysql redis
sleep 15

# Populate the code volume from the install zip
# (download acellemail-latest.zip from CodeCanyon to /srv/acellemail/ first)
docker run --rm -v acellemail_code:/dst -v $PWD:/src alpine \
  sh -c 'cd /dst && unzip -q /src/acellemail-latest.zip && chown -R 33:33 .'

# Bring up the rest
docker compose up -d
```

> `chown -R 33:33` is the critical one-time fix. The `php:8.3-fpm` image runs as UID 33 (`www-data`); without that ownership, AcelleMail can't write logs or cached views and the web installer loops forever.

Now browse to `http://mail.example.com/install` and run the wizard. With the custom Dockerfile from Step 1, all System Check rows should be green on the first try:

![AcelleMail install wizard welcome page showing the 5-step top nav and the System Requirements card with green checkmarks](https://acellesend.com/plugins/acelle/landing/images/help/kb/install/01-starting.png)

![Full system requirements page — 14 PHP requirements all green plus 5 directory permission checks all green, with a Continue button at the bottom](https://acellesend.com/plugins/acelle/landing/images/help/kb/install/02-system-check.png)

![Configuration step showing Site Name + License Key + Site Description fields plus an Admin Account card](https://acellesend.com/plugins/acelle/landing/images/help/kb/install/03-site-config.png)

For the Database step, use `mysql` as the host (the compose service name), `acellemail` as the database + user, and the contents of `secrets/mysql_pw.txt` as the password.

## Step 4 — Add HTTPS

Run certbot in a one-shot container against the running nginx:

```bash
docker run --rm -it \
  -v /srv/acellemail/certbot/conf:/etc/letsencrypt \
  -v /srv/acellemail/certbot/www:/var/www/certbot \
  certbot/certbot certonly --webroot -w /var/www/certbot \
  -d mail.example.com --email you@example.com --agree-tos --non-interactive
```

Add the HTTPS server block to `nginx/acellemail.conf` (mirror the HTTP block, listen on 443 with `ssl_certificate` paths under `/etc/letsencrypt/live/mail.example.com/`) and force-redirect HTTP. Reload:

```bash
docker compose exec nginx nginx -s reload
```

Weekly renewal cron on the host:

```cron
0 3 * * 0 docker run --rm -v /srv/acellemail/certbot/conf:/etc/letsencrypt \
  -v /srv/acellemail/certbot/www:/var/www/certbot certbot/certbot renew --quiet \
  && docker compose -f /srv/acellemail/docker-compose.yml exec nginx nginx -s reload
```

## Step 5 — Patch upgrade workflow (the Docker quirk)

API mode is the cleaner choice with Docker — it writes to the named volume and the change is live without a container rebuild. From any host with `curl`:

```bash
TOKEN="..."   # your AcelleMail API token
HOST="https://mail.example.com"
curl --max-time 900 -X POST "$HOST/api/v1/upgrade/run-file" \
  -H "Authorization: Bearer $TOKEN" \
  -F "patch=@/path/to/patch-latest.bin"

# Finalize from inside the app container so cache + migrations refresh
docker compose exec app php artisan migrate --force
docker compose exec app php artisan view:clear
docker compose exec app php artisan config:clear
docker compose restart app worker scheduler
```

The Acelle support handbook documents this restart requirement explicitly: opcache + Laravel's cached config will hold the old code path until the PHP process restarts. **Always restart `app`, `worker`, `scheduler` together** after any patch — partial restarts cause hard-to-debug version-mismatch behaviour.

## Step 6 — Scaling

- **Vertical:** bump the host's CPU + RAM and increase `deploy.replicas` on `worker` (4 at Medium, 8 at Large). Workers are stateless from each other's perspective — Laravel's queue driver (database or Redis) handles coordination.
- **Horizontal app:** running multiple `app` replicas against the same code volume works for stateless requests (the admin UI, REST API) but Laravel session storage assumes a single writer by default — configure Redis-backed sessions in `.env` (`SESSION_DRIVER=redis`) before scaling `app` beyond 1 replica.
- **Database:** at Medium tier and above, extract MySQL to a managed instance (RDS, DigitalOcean Managed DB, Hetzner Cloud DB). Update the `DB_HOST` in `.env` and remove the `mysql` service from the compose file.

## Logging

Container stdout/stderr is captured by Docker's logging driver. For production, configure `local` driver with rotation at minimum — add to every service:

```yaml
logging:
  driver: local
  options: { max-size: "20m", max-file: "5" }
```

For centralized logging, swap to `loki`, `gelf`, or push to a vector/fluentbit sidecar. Loki + Grafana is a popular self-hosted option; Datadog / Logtail / Better Stack are managed SaaS alternatives.

## Backups

```bash
# Daily MySQL dump (host cron)
0 2 * * * docker compose -f /srv/acellemail/docker-compose.yml exec -T mysql \
  mysqldump --single-transaction --routines acellemail \
  | gzip > /srv/backups/acellemail-$(date +\%F).sql.gz

# Weekly volume snapshot (host cron)
0 3 * * 0 docker run --rm -v acellemail_code:/data -v /srv/backups:/backup \
  alpine tar czf /backup/acellemail_code-$(date +\%F).tar.gz -C /data .
```

Rotate retention with a separate `find ... -mtime +30 -delete` daily cleanup. For offsite copies, push the backup dir to S3 / Backblaze B2 / Wasabi using `rclone sync` or the storage provider's CLI.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Wizard System Check red on `IMAP` or any other extension | Using stock `php:8.3-fpm` instead of the custom Dockerfile | Rebuild with `docker compose build app worker scheduler --no-cache` and re-up |
| Wizard System Check red on a `storage/...` permission row | Volume was populated as root, not UID 33 | Re-run the `chown -R 33:33` from Step 3 |
| Web installer keeps showing the welcome step after submitting | The `installed` marker file can't be written | `docker compose exec app ls -la storage/app/installed` — should be writable by UID 33 |
| `docker compose exec app php artisan ...` says "could not find driver" | Custom image build failed silently on a PECL extension | Rebuild from scratch: `docker compose build --no-cache app && docker compose up -d` |
| MySQL container exits immediately | Stale `mysql_data` volume from a previous root password | `docker compose down -v` (deletes data!) then recreate, OR mount the old data and run `mysql_upgrade` |
| Patch upgrade leaves the wrong PHP version showing in admin | Forgot to restart all 3 PHP services after patch | `docker compose restart app worker scheduler` |
| Workers stop processing jobs after a few hours | `--max-time=3600` is hit; supervisor (in compose: `restart: unless-stopped`) should auto-restart | Check `docker compose ps` — if a worker is exited, look at `docker compose logs worker` |
| nginx returns 502 immediately after `docker compose up` | nginx started before php-fpm was listening | Wait 10s and refresh; or add a `healthcheck` on `app` and `depends_on: app: condition: service_healthy` |

## FAQ

**Can I bake the AcelleMail code into a custom image?** Technically yes, but you lose the API-driven patch upgrade flow — every patch becomes a rebuild + redeploy + cache flush. The volume-based pattern in this guide keeps the upgrade flow simple.

**What about Kubernetes?** Same architecture, more YAML. Use a `PersistentVolumeClaim` for the code volume (RWX-capable storage class), separate `Deployment` resources for app + worker + scheduler, and an `Ingress` for nginx. Avoid running multiple `app` replicas against the same code volume unless you configure Redis-backed sessions.

**Why two worker replicas?** Throughput. One worker handles one queue job at a time. AcelleMail's `send-campaign` jobs spawn child jobs per chunk; two workers process them concurrently without overwhelming the upstream sending API.

**Does this work on Docker Desktop?** For development, yes. For production, no — Docker Desktop's networking and volume performance aren't production-grade. Use Linux + Docker Engine (or a managed container service like ECS, GKE, or Hetzner Container Service).

**Network policy — restricting east-west traffic.** The compose above puts `app`, `worker`, `scheduler`, `mysql`, `redis` on a shared `internal` network and `nginx` on `internal + edge`. To go further, split into per-service networks: `app-db` (app/worker/scheduler ↔ mysql/redis), `edge` (nginx ↔ app). A compromised nginx then cannot directly query MySQL. Worth doing on multi-tenant hosts.

**Secret rotation.** The compose secrets are file-backed. Rotate by: (1) `openssl rand -base64 32 > secrets/mysql_pw.txt.new`, (2) `mv secrets/mysql_pw.txt.new secrets/mysql_pw.txt`, (3) `docker compose restart app worker scheduler` (they pick up the new file at startup), (4) inside MySQL: `ALTER USER 'acellemail'@'%' IDENTIFIED BY '<new value>';`. Quarterly is reasonable. For shorter cadences, integrate with Hashicorp Vault or AWS Secrets Manager via a sidecar.

**Why not run as root?** The `php:8.3-fpm` image runs as `www-data` (UID 33) by default. Running as root inside a container is no safer than running as `www-data` — but it does mean any compromise has root-equivalent access if the container escapes. Stay with the default UID.

## Related articles

- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — bare-metal alternative
- [Install AcelleMail on AWS EC2](https://acellesend.com/kb/articles/install-acellemail-on-aws-ec2) — for ECS/Fargate alternative
- [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) — sizing reference
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — security follow-up
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — worker tuning concepts (apply to Docker `worker` service)
- [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing) — Redis tuning
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — first sending server after install
- [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) — when to outgrow a single-host Docker stack

---

# GDPR Compliance for Email Marketing with AcelleMail

> GDPR governs how you process EU residents' personal data — and email addresses are personal data. This guide walks the five obligations that touch email marketing (consent, access, erasure, portability, processor agreements) and shows the AcelleMail UI for each.

- Canonical URL: https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing
- Author: Daniel Reyes, Technical Editor
- Category: Security & Compliance — GDPR, CAN-SPAM, CASL, SSL/TLS, encryption, data privacy, audit
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-04-30 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The General Data Protection Regulation (GDPR) applies to any business that processes personal data of EU or UK residents — **regardless of where the business is located**. An email address is personal data. Sending marketing email to an EU recipient = processing personal data = GDPR applies.

The non-negotiables for email marketing are five: lawful basis for processing, right of access, right to erasure, right to portability, and a data-processing agreement with every third-party processor in the chain. This guide walks each one with the AcelleMail UI path.

## 1. Lawful basis (Article 6) — consent

For marketing email, the only practical lawful basis is **explicit consent**. The legal bar is:

- **Freely given** — not bundled with terms of service, not a condition of purchase
- **Specific** — they consented to marketing email, not "communications"
- **Informed** — they knew who they were consenting to and what they'd receive
- **Unambiguous** — a clear affirmative action; **pre-ticked boxes do not count**

In AcelleMail:

1. Open **Lists → [your list] → Edit** → **Subscription settings**
2. Turn ON **Double opt-in** — this produces a server-side timestamped confirmation log for every subscriber, the strongest form of consent record
3. In your signup form (**Forms → [your form] → Edit**), add an **unchecked** consent checkbox with explicit text — "I agree to receive marketing emails from [Company]. I can unsubscribe at any time." — linked to your privacy policy

The double opt-in confirmation timestamp is your audit trail. See [Double Opt-In vs Single Opt-In](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose) for the full setup.

## 2. Right of access (Article 15)

A subscriber can request all data you hold on them, free of charge, within one month.

In AcelleMail:

1. Open **Lists → [their list] → Subscribers**
2. Search for their email
3. Open the subscriber detail page — the profile shows email, MERGE fields (name, company, custom fields), subscription date, source list, and engagement history (opens / clicks / bounces)
4. From the subscriber's action menu, export the profile as CSV

If they're on multiple lists, repeat per list — or use **Search → Subscribers** to find every occurrence at once.

## 3. Right to erasure / "right to be forgotten" (Article 17)

The subscriber asks to be removed; you have one month to comply and must purge:

- The subscriber row from every list they're on
- All custom field data tied to them
- (Where possible) engagement history

In AcelleMail:

1. **Search → Subscribers** to find every occurrence of the email
2. Open each occurrence and click **Delete** — confirm
3. Subsequent campaign sends will not be able to target that email; AcelleMail's deduplication also prevents re-import as a "live" subscriber if they later try to re-subscribe via the same form (they'll need a fresh signup with a new confirmation).

Note: GDPR does *not* require you to delete from immutable system logs (e.g. webserver access logs, bounce-handler logs). Mainline subscriber records and your marketing CRM are what's in scope.

## 4. Right to portability (Article 20)

They get their data in a "structured, commonly used, machine-readable format" — CSV satisfies this. The Article 15 export above is sufficient for portability requests too.

## 5. Data Processing Agreement (Article 28)

If you use a third-party sending provider — Amazon SES, SendGrid, Mailgun, Postmark, SparkPost — that provider is a **data processor** and you must have a signed DPA with them. All the major SMTP providers publish their DPA online; sign once, retain a copy.

If you use AcelleMail **self-hosted** with your own SMTP and your own server, AcelleMail is not a processor of your subscriber data — your server is. You only need DPAs with services that touch the data downstream (e.g. your hosting provider, your bounce-handler IMAP host).

## The self-hosted advantage

GDPR has a "data residency" preference: keep EU residents' data inside the EU when practical. With AcelleMail self-hosted, you choose your server — put it in Frankfurt, Dublin, or Paris and you've solved data residency for free. SaaS competitors that run from US-only infrastructure complicate this; you don't have that complication.

## Common issues

| What you see | What to do |
|---|---|
| Pre-ticked consent checkbox on legacy signup form | Un-tick it. Re-permission anyone collected through it via a confirmation campaign. |
| Subscriber on 4 lists asks to be deleted; you delete from one and they keep getting email | Use **Search → Subscribers** (global) to find every occurrence before clicking delete. |
| Lawful-basis question on cold B2B outreach | "Legitimate interest" basis is debated for B2B; the safest read is to still use explicit opt-in. ICO (UK) guidance: B2B individual addresses still attract PECR / GDPR. |
| You're not sure whether your SMTP provider has a DPA | Their compliance/legal page will publish it. Amazon SES, SendGrid, Mailgun, Postmark all have public DPAs you sign electronically. |

## What to do after

1. Audit every signup form for un-pre-ticked consent + explicit text + privacy link.
2. Turn on **double opt-in** for any list that collects EU/UK residents.
3. If you use a third-party SMTP, sign their DPA today.
4. If you don't know where your server is hosted geographically — find out. EU residency is a feature, US-only is a friction point.

## Related articles

- [CAN-SPAM Compliance in AcelleMail](https://acellesend.com/kb/articles/can-spam-compliance-checklist)
- [CASL: Canadian Anti-Spam Requirements](https://acellesend.com/kb/articles/casl-canadian-anti-spam-requirements)
- [Double Opt-In vs Single Opt-In: Which to Choose](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose)
- [Managing Subscriber Preferences and Frequency](https://acellesend.com/kb/articles/managing-subscriber-preferences-and-frequency)

---

# Automation Triggers + Conditions: Full Reference

> Complete reference for AcelleMail's 12 built-in triggers, the CONDITION node operators, and the OPERATION actions. Plus the multi-branch patterns power users actually deploy.

- Canonical URL: https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: reference · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-04-26 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

This is the **power-user reference** for AcelleMail's automation system — the catalog you skim when designing a flow, not a how-to walkthrough. If you're just starting with automations, read [Build a Welcome Email Series](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) first; come back here when you need to know what trigger to pick for a specific scenario.

## All 12 built-in triggers

These are every trigger card on the *Create Automation* page:

![Create Automation trigger picker showing all 12 trigger cards in a 4x3 grid — full reference of available trigger types](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)

| # | Trigger | Fires when | Most common use | Deep-dive article |
|---|---|---|---|---|
| 1 | **Welcome new subscribers** | New subscriber added to the connected list | Welcome series | [Welcome Series](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) |
| 2 | **Say goodbye to subscriber** | Subscriber unsubscribes from the connected list | Exit survey, win-back, internal alert | — |
| 3 | **Say 'Happy birthday'** | Subscriber's *Birthday* date field matches today (year-agnostic) | Birthday email | [Birthday + Anniversary](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations) |
| 4 | **Subscriber added date** | Yearly anniversary of `subscribed_at` | Anniversary email | [Birthday + Anniversary](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations) |
| 5 | **Specific date** | One-time send on a fixed calendar date | Event reminder, appointment | — |
| 6 | **API 3.0** | External webhook call to AcelleMail's API | Custom server-side triggers | [API Triggers](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) |
| 7 | **Weekly recurring** | Same day every week | Weekly digest, weekly check-in | — |
| 8 | **Monthly recurring** | Same day every month | Monthly invoice, billing reminder | — |
| 9 | **Tag based** | Subscriber receives a specified tag | Post-purchase, re-engagement, lead scoring | [Post-Purchase](https://acellesend.com/kb/articles/post-purchase-follow-up-automation), [Re-engagement](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers), [Lead Scoring](https://acellesend.com/kb/articles/lead-scoring-with-email-automation) |
| 10 | **Remove Tag** | Subscriber loses a specified tag | Exit-flow, bucket transitions | — |
| 11 | **Contact Attribute Update** | Custom field value matches a specified condition (incl. wildcards) | Lifecycle stage changes, score thresholds | [Lead Scoring](https://acellesend.com/kb/articles/lead-scoring-with-email-automation) |
| 12 | **Abandoned Cart Reminder** | WC cart abandonment via the sync plugin | Cart recovery | [Abandoned Cart](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence) |

### Trigger anatomy

Every trigger you configure has the same three pieces:

| Piece | What it does |
|---|---|
| **Trigger event** | One of the 12 above. Determines WHAT starts the workflow. |
| **Audience list** | Limits which subscribers are eligible. Only subscribers on this list enter the flow. |
| **Filters** *(trigger-dependent)* | Tag-based / Contact Attribute Update / Specific date need additional config (which tag, which field+value, which date). |

Every trigger fires **once per subscriber per qualifying event**. AcelleMail prevents re-firing on the same subscriber while they're in the flow — to re-enter, they need to be removed from the active session first (via *Contacts → Delete trigger* on the automation's Contacts tab).

## The CONDITION node — the branching workhorse

The CONDITION node creates a YES/NO split in your flow. It's how you build *"if engaged, do X; if not, do Y"*.

### CONDITION evaluates against four kinds of facts

| Fact | Example | Use |
|---|---|---|
| **Tag presence** | "Has tag `engagement-hot`?" | Bucket-based routing |
| **Custom field value** | "Plan field equals `enterprise`?" | Account-type-based content |
| **Activity history** | "Opened campaign 'Welcome'?" | Engagement-based pruning |
| **Compound (AND/OR)** | "Tag X AND field Y > 50?" | Complex multi-criteria checks |

For each fact, AcelleMail offers operators appropriate to the data type:

| Data type | Operators |
|---|---|
| Tag | has tag · doesn't have tag |
| Custom field (text) | equals · not equals · contains · doesn't contain · starts with · ends with · is empty · is not empty |
| Custom field (number) | equals · greater than · less than · between |
| Custom field (date) | greater than (days) · less than (days) · is past · is future |
| Activity (email opened) | opened · did not open |
| Activity (link clicked) | clicked any link · clicked specific link · did not click |

### Build a CONDITION node

In the flow builder:

1. Click **+** on the downstream arrow where you want the split
2. Pick **CONDITION** from the node picker
3. The side panel opens with three dropdowns: **Field → Operator → Value**
4. Configure your check
5. Two arrows appear from the new node: **YES** (top) and **NO** (bottom)
6. Build the downstream flow on each branch

### Multi-branch via chained CONDITIONs

The YES/NO split is binary. For more than two outcomes, chain CONDITION nodes:

```
CONDITION → Has tag `vip`?
            ├─ YES → SEND EMAIL: VIP exclusive
            └─ NO  → CONDITION → Has tag `customer`?
                                  ├─ YES → SEND EMAIL: Customer offer
                                  └─ NO  → CONDITION → Engagement-warm?
                                                       ├─ YES → SEND EMAIL: Warm nurture
                                                       └─ NO  → SEND EMAIL: Cold-list welcome
```

4 branches built from 3 CONDITIONs. Each YES/NO is one decision; the chain reaches every outcome.

## The OPERATION node — mutate the subscriber

The OPERATION node is how a flow changes the subscriber's data, not just sends them email. Five operation types:

| Operation | What it does | Use |
|---|---|---|
| **Add tag** | Adds a tag to the subscriber | Mark progress (`completed-onboarding`), trigger downstream flows |
| **Remove tag** | Removes a tag | Reset state, exit a bucket |
| **Update field** | Sets a custom field to a new value (literal or formula) | Lead scoring, lifecycle stage |
| **Move to blacklist** | Permanent block on all sends | Sunset non-responders ([Re-engagement](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)) |
| **Copy to list** *(in some installs)* | Add the subscriber to another mail list | Multi-list segmentation |

The **Update field** operation can take a literal value (`Plan = enterprise`) OR a formula referencing the current value (`{{subscriber.lead_score}} + 25`). Formula support makes OPERATION the engine of any points-based scoring system.

## The WAIT node — pause the flow

WAIT pauses the subscriber's journey for a configurable duration before the next node fires.

| WAIT unit | Practical range | Use |
|---|---|---|
| Minutes | 5 min – 60 min | Cart-recovery near-real-time |
| Hours | 1h – 48h | Day-to-day welcome series |
| Days | 1d – 365d | Multi-week nurture, anniversaries |

**WAIT pauses persist across automation pauses** — if you pause the automation, all WAIT nodes hold their place; un-pausing resumes from where each subscriber was. This is critical to know if you're iterating on a flow in production.

## Designing multi-trigger flows

Sometimes one automation isn't enough. Common patterns:

### Pattern 1 — One canonical flow, multiple entries

Build one *"customer nurture"* automation with a Tag based trigger on `customer-new`. Then build:

- Pre-purchase flow → adds `customer-new` tag → enters nurture
- Post-purchase flow → adds `customer-new` tag → enters nurture (via the same trigger)

The nurture flow doesn't care HOW the subscriber became a customer — it just sees the tag.

### Pattern 2 — Coordinated trigger pairs

For a 2-stage funnel:

```
Automation A — Welcome series
  TRIGGER: Welcome new subscribers
  …5-email flow…
  Final OPERATION: Add tag `nurture-eligible`

Automation B — Long-tail nurture
  TRIGGER: Tag based: nurture-eligible
  …monthly content + offers…
```

Two automations, one handoff. The first ends with a tag operation; the second triggers off that tag. Clean separation, easier to maintain than one giant flow.

### Pattern 3 — Exit-on-tag-set

Any automation can exit early if a sentinel tag appears. The pattern:

```
… (any path) …
CONDITION → Has tag `exit-now`?
            ├─ YES → END
            └─ NO  → continue
```

Place these checks before each SEND EMAIL. Useful for "stop sending if customer complained" or "stop sending if customer converted".

## Anti-patterns

| Pattern | Problem | Better |
|---|---|---|
| **One giant flow with 30+ nodes** | Hard to debug, slow to evaluate, easy to break | Split into multiple smaller automations linked via tags |
| **CONDITION on every step** | Performance overhead; flow becomes unreadable | Use ONE upstream CONDITION + segment-based filtering |
| **Trigger on Contact Attribute Update with no filter** | Fires on every field change to any field — chaos | Always specify the field + condition in the trigger config |
| **WAIT 0 between nodes** | Pointless; just remove the WAIT | Remove |
| **Multiple automations triggering on the same event** | Subscribers receive duplicate emails | Use one canonical automation + handoff via tags |
| **Manual subscriber adds to "trigger" the flow** | Easy to forget, doesn't scale | Use the actual trigger — Tag based with a bulk-tag action |

## Performance notes

- AcelleMail evaluates trigger conditions on a polling basis (typically every minute for tag/attribute changes, daily for date triggers). Don't expect sub-second responsiveness.
- A subscriber can be in **multiple automations simultaneously** — each automation tracks them independently. Watch for cross-automation send overlaps.
- The **Contacts → Trigger all subscribers** action on an automation fires the trigger for every subscriber on the list at once. Use sparingly on large lists (10k+).

## Related articles

- [Build a Welcome Email Series in AcelleMail (Visual Flow Builder)](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Tag-Based Re-engagement Pattern](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)
- [Lead Scoring in AcelleMail](https://acellesend.com/kb/articles/lead-scoring-with-email-automation)
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows)

---

# How to Set Up SPF, DKIM, and DMARC Records — The Basics

> New to email sending? These are the 3 DNS records you absolutely need before sending a single campaign. This guide is the basics — what each does, what to publish, how AcelleMail verifies them.

- Canonical URL: https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-04-26 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The 3 must-have records

Email deliverability has many factors. The 3 DNS records you MUST have to send legitimate email at any volume:

| Record | What it does |
|---|---|
| **SPF** (TXT) | Tells receiving servers which IPs are authorized to send for your domain |
| **DKIM** (TXT or CNAME) | Cryptographically signs your emails so receivers can verify they're really from you |
| **DMARC** (TXT) | Tells receivers what to do when SPF or DKIM check fails |

Without ALL THREE, your emails go to spam (or worse — blocked entirely).

## AcelleMail's verify-domain wizard generates these for you

You don't write SPF / DKIM / DMARC from scratch. AcelleMail's wizard generates the records based on your sending vendor + setup.
### Open the sending-domain detail

In AcelleMail's sidebar, **Sending → Sending domains**. The list shows every domain you've registered with status chips (Verified / Pending / Failed) and per-auth indicators:

![Sending domains list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into your domain row. The detail page surfaces exactly which DNS records to publish (TXT for SPF, CNAMEs for DKIM, TXT for DMARC) with copy-paste-ready values + current verification state per check:

![Sending-domain detail — DNS records + auth status](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

The sending-domain detail page shows you the EXACT records to publish at your DNS host. Click "Show DNS records" — copy each line, paste into your DNS provider (Cloudflare, Route 53, Namecheap, etc.) as a new record.

After publishing, click **Verify domain** in AcelleMail. The dig-check confirms each record is reachable + correctly formatted.

## What you'll publish

### SPF (TXT record at apex)

```
Type:  TXT
Name:  yourdomain.com  (the root — no subdomain)
Value: v=spf1 include:amazonses.com -all
TTL:   3600
```

Replace `amazonses.com` with whatever vendor you use:

- Amazon SES → `include:amazonses.com`
- Mailgun → `include:_spf.mailgun.org`
- SendGrid → `include:sendgrid.net`
- Postmark → `include:spf.mtasv.net`

Combine for multi-vendor:

```
v=spf1 include:amazonses.com include:_spf.mailgun.org -all
```

`-all` tells receivers to REJECT mail from any IP not in your SPF. Use `~all` while testing; switch to `-all` once production-stable.

### DKIM (CNAME or TXT)

AcelleMail generates a keypair when you add a sending domain. The publish is typically:

```
Type:  CNAME
Name:  acellemail._domainkey.yourdomain.com
Value: acellemail._domainkey.acellemail.com
TTL:   3600
```

The selector (`acellemail`) and target are shown in your sending-domain detail page.

### DMARC (TXT at _dmarc subdomain)

Start in monitor mode:

```
Type:  TXT
Name:  _dmarc.yourdomain.com
Value: v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com
TTL:   3600
```

Key parts:

- `p=none` — monitor only (no enforcement); collect aggregate reports for 30+ days
- `rua=mailto:...` — where to send aggregate reports

After 30 days of clean reports, step up to `p=quarantine` and eventually `p=reject`. See [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration) for the staged plan.

## Verify in AcelleMail

After publishing all 3 records, return to AcelleMail and click **Verify domain**. The auth chips:
### The auth chips on the sending-server detail

Open Settings → Sending servers → click your active server. The toolbar shows the live SPF / DKIM / DMARC chip status:

![Sending server detail with auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

- **Green chips on all three** = receiving servers can confirm you're authorized to send from this domain.
- **Any chip red** = receiving servers immediately downgrade reputation. Click *Verify domain* in the toolbar to walk through the DNS-fix wizard.

All three should turn Green within minutes (depending on DNS propagation speed). If any stays Red:

- Check the record at your DNS host (typo? wrong record type?)
- Wait — DNS takes 5-60 minutes typically, up to 24h for stubborn caches
- Use `dig` from your terminal to verify the record IS published: `dig TXT yourdomain.com +short`

## Common questions

| Question | Answer |
|---|---|
| Can I have multiple SPF records? | NO. Receivers accept only one TXT starting with `v=spf1`. Combine includes. |
| Where does the DKIM key go on my DNS? | Wherever your sending-domain detail page tells you (usually CNAME at `{selector}._domainkey.yourdomain.com`) |
| Should `p=` be `none`, `quarantine`, or `reject`? | Start with `none` (monitor). Move up gradually. |
| What happens if I skip DMARC? | Receivers fall back to general spam filtering; your reputation builds slower |
| My DNS host doesn't accept TXT >255 chars | Split via quoted-string concatenation: `"v=spf1 ..." "..." "..."` |
| When can I add BIMI? | After DMARC reaches `p=quarantine` or `p=reject` — see [BIMI setup](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail) |

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Verify-domain stays *Pending* | Records not yet propagated | Wait up to 24h; verify with `dig` from terminal |
| SPF chip Red despite published record | Syntax error (e.g. `~all` typo, missing `v=spf1`) | Re-copy exact value from AcelleMail's wizard |
| DKIM chip Red despite CNAME | CNAME pointing to wrong target | Verify selector + target match AcelleMail's wizard |
| DMARC chip Red | TXT record name not `_dmarc.yourdomain.com` (typo) | Re-publish at correct name |
| All Green but emails still go to spam | Reputation/content issue, not auth | See [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring) |
| Records show "MULTIPLE" warning | Two SPF records published — receivers reject | Delete duplicate; keep one combined record |

<details>
<summary><strong>Advanced: 2048-bit DKIM, DMARC subdomain policies, vendor-specific gotchas</strong></summary>

**2048-bit DKIM** is the modern standard. Some DNS hosts have TXT record character limits — if your DKIM record is >255 chars, you may need to:

- Switch to CNAME (recommended; AcelleMail's default)
- Use quoted-string concatenation: `"v=DKIM1; k=rsa; p=ABC..." "...DEF"`

**DMARC subdomain policies** via `sp=`:

```
v=DMARC1; p=none; sp=reject; rua=mailto:...
```

`p=none` (apex policy) but `sp=reject` (subdomain policy = strict). Useful when you want apex in monitor mode but subdomains in strict.

**Vendor-specific SPF gotchas:**

```
Amazon SES (multi-region):  include:amazonses.com   (covers all regions)
SES dedicated IP:           include:amazonses.com   (same — uses shared SES MX)
SendGrid (specific subaccount): include:u123456.sendgrid.net  (per-subaccount)
Mailgun (EU region):        include:_spf.eu.mailgun.org
```

**Multi-vendor SPF combine:**

```
v=spf1 include:amazonses.com include:_spf.mailgun.org include:sendgrid.net -all
```

Stay under 10 DNS lookups total (see [SPF record deep dive](https://acellesend.com/kb/articles/spf-record-deep-dive) for the math).

**Per-customer DKIM (SaaS):**

For SaaS sending-on-behalf, customers add their own DKIM CNAME. Your platform exposes the CNAME via the sending-domain detail page; customer publishes at their DNS host.

**DKIM rotation:**

Industry standard: rotate DKIM key every 6-12 months. AcelleMail's admin → Sending → DKIM Key Rotation generates a new keypair + updates the CNAME target. Old keys remain valid for 30 days during transition (so in-flight messages don't fail signature check). See [DKIM key rotation playbook](https://acellesend.com/kb/articles/dkim-key-rotation-playbook).

**Test before going live:**

mail-tester.com — free service. Send a test email to a generated address; receive a deliverability report:

- SPF: pass/fail + reason
- DKIM: pass/fail + reason
- DMARC: pass/fail + reason
- Spam score: 0-10 (10 = perfect)

Aim for 9.0+. Below 8.0, fix issues before live sending.

</details>

## Related articles

- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [SPF record deep dive](https://acellesend.com/kb/articles/spf-record-deep-dive)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)
- [DKIM key rotation playbook](https://acellesend.com/kb/articles/dkim-key-rotation-playbook)
- [DNS propagation explained](https://acellesend.com/kb/articles/dns-propagation-explained)

---

# Automated Database Backups for AcelleMail

> The first backup is the cheapest you'll ever take. By the time you actually need to restore, the cost of NOT having a backup is hours of downtime, lost subscribers, and a furious customer-success conversation. This guide walks the production-correct backup setup: daily mysqldump with rotation, offsite copies, file-tree snapshots, restore drill workflow, and the monitoring that tells you when it stops working.

- Canonical URL: https://acellesend.com/kb/articles/automated-database-backups
- Author: Daniel Reyes, Technical Editor
- Category: Server Management — Cron jobs, queues, Supervisor, Redis, Nginx, performance tuning
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-04-25 · Last updated: 2026-08-15
- Reading time: 12 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The first backup is the cheapest you'll ever take. By the time you actually need to restore — a botched migration, a hardware failure, a misclicked "Delete List" — the cost of *not* having a backup is hours of downtime, lost subscribers, lost campaign history, and a furious customer-success conversation.

This guide walks the production-correct setup: **daily mysqldump with rotation, offsite copies, file-tree snapshots, a restore drill workflow, and monitoring that tells you when it silently stops working.**

The whole setup is 30 minutes. Do it within the first hour of going to production.

## What needs backing up

Two things, distinct and both required:

1. **The MySQL database** (`acellemail` schema) — contains subscribers, lists, campaigns, automations, templates, billing history, sending logs. This is the irreplaceable bit.
2. **The storage directory** (`/var/www/acellemail/storage/`) — contains user-uploaded campaign images, attached files, exports, generated PDFs. Lost storage = broken historical campaigns (images 404 in old emails). Recoverable from sources, but painful.

**What you don't need to back up:**

- Application code (`/var/www/acellemail/` minus `storage/` and `.env`) — restorable from the CodeCanyon install bundle + your specific patches
- `vendor/`, `node_modules/`, `bootstrap/cache/` — regenerable
- `.env` — back up separately to a password manager / secret vault, not in the regular file backup (contains DB password, API keys, app key)

## Step 1 — Daily MySQL backup

Create the script `/usr/local/bin/acellemail-db-backup.sh`:

```bash
#!/bin/bash
# Daily MySQL backup for AcelleMail — single-transaction, gzipped, 30-day retention

set -euo pipefail

DEST="/var/backups/acellemail/db"
TIMESTAMP="$(date +%Y-%m-%d_%H%M%S)"
KEEP_DAYS=30

# Read DB credentials from the app's .env (single source of truth)
ENV="/var/www/acellemail/.env"
DB_NAME="$(grep '^DB_DATABASE=' "$ENV" | cut -d= -f2-)"
DB_USER="$(grep '^DB_USERNAME=' "$ENV" | cut -d= -f2-)"
DB_PASS="$(grep '^DB_PASSWORD=' "$ENV" | cut -d= -f2- | tr -d '"')"

mkdir -p "$DEST"

# --single-transaction = consistent snapshot without table locks (works on InnoDB)
# --routines = include stored procedures + functions
# --triggers = include triggers
# --quick = stream rows row-by-row (lower memory for large tables)
mysqldump \
    --user="$DB_USER" \
    --password="$DB_PASS" \
    --single-transaction \
    --routines \
    --triggers \
    --quick \
    --no-tablespaces \
    "$DB_NAME" \
  | gzip -9 \
  > "$DEST/$DB_NAME-$TIMESTAMP.sql.gz"

# Rotation: delete anything older than KEEP_DAYS
find "$DEST" -name "$DB_NAME-*.sql.gz" -mtime "+$KEEP_DAYS" -delete

# Sanity-check the backup is non-empty
if [ ! -s "$DEST/$DB_NAME-$TIMESTAMP.sql.gz" ]; then
    echo "FATAL: backup is empty!" >&2
    exit 1
fi

# Optional: log size for monitoring
SIZE=$(du -h "$DEST/$DB_NAME-$TIMESTAMP.sql.gz" | cut -f1)
echo "[$(date)] Backup ok: $DEST/$DB_NAME-$TIMESTAMP.sql.gz ($SIZE)"
```

Make it executable + schedule:

```bash
sudo chmod +x /usr/local/bin/acellemail-db-backup.sh
sudo chown root:root /usr/local/bin/acellemail-db-backup.sh

# Run as root to read /var/www/acellemail/.env (which is 0640 root-readable)
# Daily at 02:00 — pick a low-traffic time
echo "0 2 * * * /usr/local/bin/acellemail-db-backup.sh >> /var/log/acellemail-backup.log 2>&1" \
  | sudo tee -a /etc/crontab
```

> `--single-transaction` is critical — without it, `mysqldump` locks tables for the duration of the dump, which can be minutes on a large DB. With InnoDB (the default) and `--single-transaction`, you get a consistent point-in-time snapshot without any lock.

Verify the script works manually before relying on cron:

```bash
sudo /usr/local/bin/acellemail-db-backup.sh
ls -lah /var/backups/acellemail/db/
# Should show one .sql.gz file from "just now"
```

## Step 2 — File-tree snapshot

The `storage/` directory needs less frequent backup — campaign assets change rarely once uploaded. Weekly is plenty:

```bash
sudo tee /usr/local/bin/acellemail-files-backup.sh <<'EOF'
#!/bin/bash
set -euo pipefail

DEST="/var/backups/acellemail/files"
TIMESTAMP="$(date +%Y-%m-%d)"
KEEP_WEEKS=8

mkdir -p "$DEST"

# Tar the storage tree, exclude transient subdirs that would just bloat
tar czf "$DEST/storage-$TIMESTAMP.tar.gz" \
    --exclude='framework/cache' \
    --exclude='framework/sessions' \
    --exclude='framework/views' \
    --exclude='logs/laravel.log' \
    -C /var/www/acellemail storage

# Rotation: keep last KEEP_WEEKS only
find "$DEST" -name 'storage-*.tar.gz' -mtime "+$((KEEP_WEEKS * 7))" -delete

SIZE=$(du -h "$DEST/storage-$TIMESTAMP.tar.gz" | cut -f1)
echo "[$(date)] Files backup ok: $DEST/storage-$TIMESTAMP.tar.gz ($SIZE)"
EOF

sudo chmod +x /usr/local/bin/acellemail-files-backup.sh

# Weekly on Sunday at 03:00
echo "0 3 * * 0 /usr/local/bin/acellemail-files-backup.sh >> /var/log/acellemail-backup.log 2>&1" \
  | sudo tee -a /etc/crontab
```

Why exclude `framework/cache,sessions,views,logs/laravel.log`? They regenerate themselves and bloat the backup. Anything truly user-uploaded sits under `storage/app/` and is captured.

## Step 3 — Offsite copy

A local backup that's still on the same disk as the database protects against accidental deletion. It does **not** protect against:

- Drive failure (RAID dies; backup goes with it)
- Server compromise (attacker `rm -rf` everything)
- Cloud-provider-level data loss (rare but real)

For real durability you need an offsite copy. Three good options ranked by ops simplicity:

### Option A — rclone to Backblaze B2 / Wasabi (cheapest, simplest)

```bash
sudo apt install -y rclone

# Interactive config — set up your B2/Wasabi credentials
sudo rclone config
# Choose: New remote → Name: b2 (or whatever) → Type: b2 → enter Application Key ID + Application Key

# Add to /etc/crontab — runs daily at 04:00 (after the DB backup)
0 4 * * * /usr/bin/rclone sync /var/backups/acellemail/ b2:your-bucket/acellemail/ --log-file=/var/log/rclone-backup.log
```

Backblaze B2 pricing as of 2026: **$6/TB-month storage, free egress up to 3× monthly storage**. A 10 GB AcelleMail backup costs $0.06/month + free restore. Hard to beat.

### Option B — restic to S3 / B2 (incremental + encrypted)

```bash
sudo apt install -y restic

# One-time init (creates an encrypted repo)
sudo restic -r s3:s3.amazonaws.com/your-bucket/acellemail init
# It'll prompt for a password — STORE IT SOMEWHERE SAFE; without it backups are unrecoverable

# Backup
sudo restic -r s3:s3.amazonaws.com/your-bucket/acellemail backup /var/backups/acellemail

# Prune (run weekly)
sudo restic -r s3:s3.amazonaws.com/your-bucket/acellemail forget \
  --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
```

Restic does content-addressed deduplication — only changed bytes are uploaded after the first full sync. Significantly cheaper for the bandwidth side, with the trade-off of needing the repo password to restore.

### Option C — provider snapshots (operator-zero-effort)

DigitalOcean Snapshots, AWS EBS Snapshots, Hetzner Cloud Snapshots — all let you schedule daily droplet-wide snapshots. Pros: zero scripting; restores the full droplet exactly. Cons: more expensive than B2; restore requires building a new droplet (10–30 min); harder to extract just the DB.

**Recommended pattern:** local rotation (Step 1+2) for fast restores + B2 offsite (Option A) for disaster recovery + monthly provider snapshot for full-droplet protection.

## Step 4 — The restore drill (the part most people skip)

**A backup you've never restored is not a backup.** Schedule a monthly restore drill into your calendar:

```bash
# Restore to a non-production location to confirm it works

# 1. Pick a recent backup
LATEST=$(ls -t /var/backups/acellemail/db/*.sql.gz | head -1)
echo "Restoring from: $LATEST"

# 2. Create a scratch database
sudo mysql -e "CREATE DATABASE acellemail_restore_test;"

# 3. Restore into it
gunzip -c "$LATEST" | sudo mysql acellemail_restore_test

# 4. Sanity-check the restored data
sudo mysql acellemail_restore_test -e "
  SELECT (SELECT COUNT(*) FROM customers) AS customers,
         (SELECT COUNT(*) FROM subscribers) AS subscribers,
         (SELECT COUNT(*) FROM campaigns) AS campaigns,
         (SELECT MAX(created_at) FROM email_log) AS latest_send;
"
# Numbers should look sane; latest_send should be < 1 hour old (your live cron is firing)

# 5. Drop the scratch DB
sudo mysql -e "DROP DATABASE acellemail_restore_test;"
```

If any step fails, your backup is broken — fix it before you ever need it for real. Common breakage:

- `--single-transaction` errors because some tables aren't InnoDB → identify them and convert (`ALTER TABLE foo ENGINE = InnoDB`)
- Restore fails on a foreign-key constraint → add `SET FOREIGN_KEY_CHECKS=0; ... SET FOREIGN_KEY_CHECKS=1;` wrapping
- `Access denied` for restore → the user running the restore needs `CREATE`, `INSERT`, `ALTER` on the scratch DB

Document any tweaks in your backup script so they're applied for real next time.

## Step 5 — Production restore workflow (when something actually broke)

For a real disaster:

```bash
# 1. STOP the queue workers so nothing keeps writing to the broken state
sudo supervisorctl stop acellemail-master:* acellemail-worker:*

# 2. Stop incoming web traffic — put up a maintenance page
sudo -u www-data php /var/www/acellemail/artisan down

# 3. Take a "before" snapshot of the current (broken) database for forensics
DB_NAME=$(grep '^DB_DATABASE=' /var/www/acellemail/.env | cut -d= -f2-)
sudo mysqldump --single-transaction "$DB_NAME" | gzip > /tmp/pre-restore-$(date +%F-%H%M).sql.gz

# 4. Drop + recreate the database (clean slate)
sudo mysql -e "DROP DATABASE $DB_NAME; CREATE DATABASE $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "GRANT ALL ON $DB_NAME.* TO '$DB_NAME'@'localhost';"

# 5. Restore from the chosen backup
LATEST=/var/backups/acellemail/db/${DB_NAME}-XXXX.sql.gz   # pick the right one
gunzip -c "$LATEST" | sudo mysql "$DB_NAME"

# 6. If storage/ was also lost, restore from the latest weekly tar
sudo tar xzf /var/backups/acellemail/files/storage-YYYY-MM-DD.tar.gz \
  -C /var/www/acellemail
sudo chown -R www-data:www-data /var/www/acellemail/storage

# 7. Bring the site back up
sudo -u www-data php /var/www/acellemail/artisan up
sudo supervisorctl start acellemail-master:* acellemail-worker:*

# 8. Verify in the admin: most-recent campaigns visible, subscriber count reasonable
```

Document any deviations in a restore playbook PDF kept somewhere outside the server (Google Drive, Notion, password manager).

## Step 6 — Monitoring

A backup that silently stops working is the worst kind. Add these monitors:

### Backup age check

```bash
# A simple cron that screams if no backup was created today
0 8 * * * find /var/backups/acellemail/db/ -name '*.sql.gz' -mtime -1 | grep -q . || \
  curl -sf "https://hc-ping.com/your-ping-uuid/fail"
```

[Healthchecks.io](https://healthchecks.io) free tier gives you up to 20 monitored crons + Slack/email alerts. Set up two checks:

- **acellemail-db-backup-done** — pings at the end of the backup script (`curl -sf https://hc-ping.com/uuid`)
- **acellemail-offsite-sync-done** — pings at the end of the rclone job

If a ping doesn't arrive on schedule, you get an alert. Significantly more reliable than "I'll check the log file each morning."

### Backup size sanity check

A sudden 90% drop in backup size means something is broken (DB user lost a permission, a partition deleted, the backup truncated). Watch the trend:

```bash
# Add to the daily backup script
SIZE=$(stat -c '%s' "$DEST/$DB_NAME-$TIMESTAMP.sql.gz")
# Post to a metrics endpoint, or log a CSV for graphing
echo "$(date +%s),$SIZE" >> /var/log/acellemail-backup-sizes.csv
```

### Monthly restore-test cron

```bash
# Auto-run the restore drill from Step 4 on the 1st of each month
0 4 1 * * /usr/local/bin/acellemail-restore-test.sh >> /var/log/acellemail-restore-test.log 2>&1
```

If the script exits non-zero, get paged. Restore drills you never run will eventually fail when you need them.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Backup script exits "Access denied for user" | DB credentials in `.env` are wrong, or .env is restricted | Verify `mysql -u $DB_USER -p$DB_PASS $DB_NAME -e 'SELECT 1'` works manually |
| Backup file is 0 bytes | `mysqldump` failed silently (pipe to gzip swallows errors) | Use `set -o pipefail` (already in our script); inspect `/var/log/acellemail-backup.log` |
| Backup takes > 1 hour on a small DB | Tables aren't InnoDB, so `--single-transaction` is locking | Convert with `ALTER TABLE ... ENGINE = InnoDB`; verify with `SHOW TABLE STATUS LIKE 'table_name'\G` |
| rclone B2 sync says "Unauthorized" | Application Key expired or wrong bucket prefix | Regenerate the B2 Application Key, restricted to the specific bucket; re-run `rclone config` |
| Restore complains about missing user privilege | DB user from the backup doesn't exist on the restore target | Either re-create the user from the install instructions, or use root for the restore |
| Backup file size doubled overnight | A `repeating_campaign` is logging hundreds of MB to a table | `SELECT TABLE_NAME, ROUND(DATA_LENGTH/1024/1024) MB FROM information_schema.TABLES WHERE TABLE_SCHEMA = '$DB_NAME' ORDER BY DATA_LENGTH DESC LIMIT 10;` — identify the offender, prune |
| Restore drill says "table customer_quotas doesn't exist" | Restoring an old backup that predates a migration | Run `php artisan migrate --force` on the restored DB before sanity-checking |

## FAQ

**Should I use binary log + point-in-time recovery (PITR)?** For most AcelleMail installs, no. PITR (`mysqlbinlog | mysql` replay) lets you restore to any second within the binlog retention window, but it requires `log-bin = ON`, more disk I/O, and operationally complex restore steps. The daily-snapshot approach loses at most 24 hours; PITR is worth the complexity only if you genuinely can't tolerate that.

**MariaDB version of the same script?** Identical commands. `mysqldump` ships with MariaDB and accepts the same flags.

**Can I exclude tables to make backups smaller?** The `email_log` table grows fastest on busy installs. Excluding it cuts backup size dramatically but means you lose historical sending data on restore. Trade-off; document the choice.

```bash
# Exclude email_log structure + data
mysqldump ... --ignore-table=$DB_NAME.email_log "$DB_NAME"
# Still get the schema so the table exists:
mysqldump ... --no-data --tables email_log "$DB_NAME"
```

**Encrypted backups?** Restic (Option B in Step 3) encrypts client-side with a password. For rclone/B2, use `rclone crypt` to wrap an existing remote with encryption. Either way, **never lose the encryption password** — without it the backup is shredded data.

**RDS / managed DB equivalent?** AWS RDS, DigitalOcean Managed DB, Hetzner Cloud DB all offer point-in-time automated backups with 7-day retention by default. Use them; they're cheaper than rolling your own at scale. Still take periodic snapshots of `storage/` separately (managed DB doesn't cover the file tree).

**Backups are encrypted at rest on the server — do I need to encrypt them again?** Defense in depth. Server disk encryption protects against drive theft; encrypted offsite backups protect against the cloud provider seeing your data. Use both.

**How long should I keep backups?** Cost-vs-paranoia trade-off:

- Daily for 30 days (covers recent oopses)
- Weekly for 6 months (covers "we noticed last quarter that...")
- Monthly for 7 years (for legal / compliance reasons; GDPR + various data-retention regimes)

At ~10 GB compressed per backup, that's ~600 GB total at $6/TB-month = ~$3.60/month for full retention. Money well spent.

## Related articles

- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — pair with backups for full ops setup
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — Control 20 covers the basic daily-DB-dump cron; this article expands on it
- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — prerequisite
- [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) — at scale, consider RDS-style managed backups
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — retention requirements for EU sender programs
- [Docker Deployment Guide for AcelleMail](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) — Docker-equivalent backup recipes (mysqldump via `compose exec`)

---

# Importing Contacts CSV — Best Practices and the 6-Click Field Mapping Wizard

> Format the CSV right before you upload — column headers in row 1, UTF-8 encoding, one email per row. AcelleMail's import wizard handles the rest in 6 clicks. Visual walkthrough of the wizard plus error-recovery patterns + bulk-update strategies in the collapsible section.

- Canonical URL: https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-04-21 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Format the CSV right (before you upload)

The single biggest cause of "import failed for 1,247 rows" is malformed CSV. Get these right and the import flows smoothly:

| Requirement | What it means |
|---|---|
| **UTF-8 encoding** | Save the file as UTF-8 — Excel defaults to "CSV (Comma delimited)" which is often Windows-1252 and breaks accented characters. In Excel: Save As → CSV UTF-8. In Numbers: Export → CSV with default encoding. |
| **Column headers in row 1** | The first row is field names: `email,first_name,last_name,city`. Lowercase, snake_case, no spaces. |
| **One email per row** | Don't pack `john@example.com, jane@example.com` into a single cell. One row per subscriber. |
| **Quoted strings if they contain commas** | A `city` value of `Berlin, DE` must be quoted: `"Berlin, DE"`. Most spreadsheet tools do this automatically; hand-rolled CSV files often forget. |
| **Email column required** | Every row must have a non-empty email. AcelleMail rejects rows with blank email. |
| **No extra whitespace** | Leading/trailing spaces in emails or field values are stripped, but `john@example.com ` (with trailing space) might cause confusion in audit logs. Trim before export. |
| **Date format consistent** | If you have a `signup_date` column, use ISO 8601 (`2026-05-19`) — most reliable. Mixed `5/19/2026` + `19/05/2026` will trip the parser. |
## The 6-click import flow (same for every source)

Once you have the CSV ready, AcelleMail's import wizard is the same regardless of where the file came from.

### 1. Open the destination list

In the left sidebar, click **Audience** → choose the list that will receive these subscribers (or create one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

The list detail page shows current subscriber counts and a per-list toolbar:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is right there on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the file into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection — rows-counted + sample preview:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. For non-standard columns, pick the destination from the dropdown:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

On the same screen, choose what AcelleMail does when a row's email already exists in this list:

- **Skip** — keep the existing subscriber, don't overwrite anything
- **Update** — overwrite name / tags / custom fields with values from this CSV
- **Unsubscribe** — mark the existing row as unsubscribed (rare; for re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — you can close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

Pending → Running → Complete, with rows-imported / rows-skipped / errors counts per job.
## What happens after Start import

The job runs in the background. For a 10k-row file, expect 30-90 seconds. For 100k rows, 5-15 minutes. AcelleMail processes in batches of ~500 with database transactions per batch — safe to close the browser tab; the job continues server-side.

The **Import history** view in the list shows progress per batch:

- **Pending** — job queued, not yet processing
- **Running** — worker actively reading the CSV
- **Complete** — done; click to see the result summary

Result summary breaks down:

- **Rows imported** — net successful new subscribers + updates
- **Rows skipped** — duplicates handled per your chosen rule
- **Rows with errors** — bad emails, invalid date formats, FK violations — with a downloadable error CSV showing exactly which rows failed and why

Download the error CSV, fix the rows in your spreadsheet, re-import just those rows.

## Common CSV import errors and fixes

| Error in error.csv | What it means | Fix |
|---|---|---|
| `Invalid email format` | Row has missing `@`, malformed domain, or trailing whitespace | Validate email syntax in spreadsheet before upload (Excel: use regex) |
| `Email already exists` (with `Skip` mode) | Subscriber on this list already; per skip-rule, not overwritten | Expected; not an error — just informational |
| `Email already exists` (with `Update` mode) | Should be auto-updating; if it's an error here, the import ran in Skip mode | Re-run with duplicate handling = Update |
| `Field 'birthday' has invalid format` | Mixed date formats in CSV | Normalize all dates to ISO 8601 before upload |
| `Field 'lead_score' must be numeric` | A non-number value in a Number-type field | Check CSV — strip non-numeric values or change the field type |
| `Required field 'email' is empty` | Row has no email | Remove the row or fix the value |
| `Charset error: invalid UTF-8 sequence at row 4523` | File saved as Windows-1252 or other encoding | Re-export as UTF-8 |
| `Cannot map column 'birth_date' to field 'birthday'` | CSV header name differs from field name; not auto-detected | Use the field mapping step to manually map |

## Common UI patterns

### Duplicate handling cheat sheet

| Choose | When to use |
|---|---|
| **Skip** | Importing a fresh batch of leads where existing subscribers shouldn't have their names/tags overwritten |
| **Update** | Re-importing the same list from a master CRM where the CRM is the source of truth |
| **Unsubscribe** | Importing a bounced-list export to mark addresses inactive (rare; usually you'd just delete them) |

### When to import vs. when to use the API

| Scenario | Use |
|---|---|
| One-time migration from another platform | CSV import (fastest, easiest) |
| Daily sync from a CRM-of-record | API + scheduled cron (idempotent, automatic) |
| New batch of leads (every week or month) | CSV import works fine, OR set up CSV-upload-to-folder integration via your storage tool |
| Real-time subscribe (signup form) | AcelleMail's built-in signup forms OR your custom form posting to AcelleMail's API |

### Pre-import sanity check

Open the CSV in your spreadsheet tool. Five quick checks before uploading:

1. Row count matches expectations (a leading blank row will offset everything)
2. Column headers are present and snake_case (`email`, not `Email Address`)
3. Email column has no blanks (sort by email; blanks float to top)
4. No duplicate emails within the CSV itself (de-dupe via spreadsheet first; AcelleMail will skip dupes but it's cleaner to remove them upfront)
5. Try a 10-row test import first if the CSV is large — verify field mapping works, then re-run with the full file

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Import stuck on "Pending" for >10 minutes | Queue worker isn't running | Operator: check that `queue:work` is running (see [setup queue workers](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs)) |
| Import shows "Complete" but list count didn't increase | All rows skipped due to duplicates | Check duplicate handling — should be Update if you want overwrite |
| Custom field column not detected during mapping | Column header doesn't match field tag exactly | Either rename header in CSV, OR manually map in step 4 |
| Wizard rejects the file at upload step | Wrong file type (xlsx not csv), or too large (>50MB default) | Export as plain CSV from your spreadsheet tool; for >50MB, split into multiple files |
| Some subscribers imported, others not | Mixed error types in single file (some invalid emails, some duplicates) | Download error CSV → fix → re-import; the successful rows aren't re-counted |
| Tags column joins as single string | Tag values need to be comma-separated within the cell: `tag1,tag2,tag3` | Re-format the tags column in your spreadsheet |

<details>
<summary><strong>Advanced: bulk-update strategies, API-driven sync, audit + recovery</strong></summary>

For large lists or ongoing-sync scenarios, the CSV-through-UI path is one tool among several.

**Bulk update via re-import:**

If you need to update a single field across thousands of subscribers (e.g. reset everyone's `frequency` to `weekly`), the easiest path:

1. Export the list to CSV (Audience → list → Export → All fields)
2. Open in spreadsheet, edit the column for the values you want
3. Re-import with duplicate handling = **Update**

AcelleMail processes the file in 5-30 minutes depending on size. The advantage over API is no scripting required; the downside is no per-row tracking of which value changed.

**Scripted update via API** (when you need fine control):

```bash
ACELLE_TOKEN="..."

# Set everyone's 'frequency' to 'weekly' in one list:
curl -X POST "https://acellemail.com/api/v1/lists/{list_uid}/subscribers/bulk-update" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {"status": "subscribed"},
    "updates": {"fields": {"frequency": "weekly"}}
  }'
```

Returns `{updated_count, failed_count}` synchronously. Handle 429 with backoff for large updates.

**Idempotent CSV import pipeline:**

When syncing from a CRM nightly, the standard pattern is:

```bash
# 1. Export CRM contacts to CSV
crm-export --tag=marketing --output=/tmp/contacts.csv

# 2. Pre-validate (optional but recommended)
csvkit validate /tmp/contacts.csv

# 3. Upload to AcelleMail
curl -X POST "https://acellemail.com/api/v1/lists/{list_uid}/imports" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -F "file=@/tmp/contacts.csv" \
  -F "duplicate_handling=update" \
  -F "field_mapping={\"email\":\"email\",\"first_name\":\"first_name\"}"

# 4. Poll for completion + result
import_uid="..."  # from response
curl -H "Authorization: Bearer $ACELLE_TOKEN" \
  "https://acellemail.com/api/v1/imports/$import_uid"
```

The full audit trail (which rows were updated, which were skipped, which failed) is in the response JSON.

**Resumable imports for very large files:**

For files >100k rows, the wizard can run for 15+ minutes. If the queue worker is restarted mid-import, AcelleMail's import job is designed to resume from the last completed batch — no rows are double-processed. The audit trail shows the exact row offset where the resume happened.

**Pre-import deduplication:**

If your CSV has internal duplicates (same email twice within the file), AcelleMail processes the rows in order and the LAST row's values win for the duplicate. To control which row wins, sort the CSV in your spreadsheet first — most recent record last.

For server-side dedup, use the API in a loop with `upsert: true` and `last_updated_at` field comparison.

**Recovery from a bad import:**

If you imported with wrong field mapping (e.g. mapped `first_name` to `email` and now have garbage), the recovery path:

1. **Don't panic** — the original data is still there in your spreadsheet
2. AcelleMail keeps per-import audit logs (admin → Audit log → filter by import event)
3. Bulk-update the affected field back to correct values via API or re-import

There's no automatic "undo last import" — recovery is always forward-looking. Always test with a 10-row sample first on a non-critical list before running a 100k import.

**Field type changes:**

If you've imported 100k subscribers with `birthday` as a Text field, then realize you want it as a Date field — the change is in two steps:

1. Audience → list → Fields → edit `birthday` → change type Text → Date. AcelleMail re-validates all existing values; rows where the value doesn't parse remain as-is until you fix them.
2. Either bulk-update the bad rows via API, or export → fix in spreadsheet → re-import.

**Custom-field discovery during import:**

If your CSV has a column AcelleMail doesn't know about (e.g. `lifetime_value` and the list doesn't have that field yet), the field-mapping step in the wizard offers **Create new field on import**. Pick the field type (Number / Text / Date) → AcelleMail creates the field on the list + maps the column to it in one click. Useful for one-time migrations from systems with custom schemas.

**Compliance audit trail:**

Every import is logged with:

- Who triggered it (admin user ID)
- When (timestamp)
- Which file (filename + hash)
- How many rows added / updated / skipped / errored
- The full error CSV is retained 90 days (configurable in admin settings)

For GDPR audit requests ("show me when you obtained this subscriber's consent"), the import audit log answers it — the import row references the original CSV which presumably has the source/opt-in date column.

</details>

## Related articles

- [Email list hygiene — clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Managing subscriber preferences and frequency](https://acellesend.com/kb/articles/managing-subscriber-preferences-and-frequency)
- [Migration from Mailchimp to AcelleMail](https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail)
- [Double opt-in vs single opt-in — which to choose](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose)

---

# Mobile-First Email Principles

> More than 60% of email opens happen on mobile devices. Designing the desktop version first then "checking mobile" gives you a desktop email that's tolerable on mobile. Designing mobile-first gives you a mobile email that looks great on desktop. This guide walks the 6 principles that produce mobile-friendly templates from the start — single column, 16px body, 44px tap targets, 600px max, finger-friendly padding, image scaling.

- Canonical URL: https://acellesend.com/kb/articles/mobile-first-email-principles
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Design & Content — HTML email, responsive design, dark mode, accessibility, content strategy
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-04-20 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

**More than 60% of email opens happen on mobile devices** (per most published benchmarks; even higher in B2C). Designing the desktop version first then "checking mobile" gives you a desktop email that's tolerable on mobile. Designing **mobile-first** gives you a mobile email that looks great on desktop too.

This guide walks the 6 principles that produce mobile-friendly templates from the start.

## The 6 principles

### 1. Single-column layout (always)

**Why:** mobile screens are typically 360-440px wide. A 2-column layout at this width forces text to 180-220px columns = 4-6 words per line = brutal to read. Stack everything vertically.

For "two-column on desktop, stacked on mobile" sections, use the **stack-on-mobile pattern**:

```html
<!-- Desktop: 2 columns side-by-side -->
<!-- Mobile: each column 100% width, stacked vertically -->
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0">
  <tr>
    <td class="stack" valign="top" width="50%" style="padding: 20px;">
      Left content
    </td>
    <td class="stack" valign="top" width="50%" style="padding: 20px;">
      Right content
    </td>
  </tr>
</table>

<style>
  @media only screen and (max-width: 600px) {
    .stack {
      display: block !important;
      width: 100% !important;
    }
  }
</style>
```

The `@media` query flips `td` elements to `display: block` on mobile, which makes them stack.

### 2. 16px minimum body font

**Why:** smaller than 16px on a 4-inch screen is genuinely hard to read. Apple's iOS design guidelines specifically recommend 16px as the minimum for sustained reading; Google's Material Design says 14-16px minimum. Below that, recipients zoom in (causing horizontal-scroll layout damage) or just close the email.

```html
<!-- Body text -->
<p style="font-family: Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #333;">

<!-- Headlines -->
<h1 style="font-family: Arial, sans-serif; font-size: 24px; line-height: 1.3; color: #111;">
```

In an `@media (max-width: 600px)` block, bump body to 18px if you want even more headroom:

```css
@media only screen and (max-width: 600px) {
  .mobile-body { font-size: 18px !important; }
}
```

**Don't go below 14px** for legal disclaimers / unsubscribe footers — even if visually they "look like fine print", the actual fine print becomes invisible on mobile.

### 3. 44×44 px minimum tap targets

**Why:** Apple's Human Interface Guidelines specify 44×44 pt as the minimum tap target size — anything smaller produces frustrating mis-taps. Google's Material Design uses 48dp (similar). Tappable elements smaller than that fail accessibility checks too.

For buttons + CTA links:

```html
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
  <tr>
    <td bgcolor="#0066CC" style="padding: 16px 32px; border-radius: 8px;">
      <a href="https://..." style="color: #ffffff; text-decoration: none;
                                    font-family: Arial, sans-serif;
                                    font-size: 16px; font-weight: bold;
                                    display: inline-block;
                                    min-height: 44px; line-height: 44px;">
        Get started
      </a>
    </td>
  </tr>
</table>
```

The combination of `padding: 16px 32px` + `font-size: 16px` typically produces a button ~52px tall × at-least-44px wide = comfortably tappable.

For inline text links (e.g., "Learn more →"), wrap them with spacing:

```html
<p style="font-size: 16px; line-height: 1.8; padding: 8px 0;">
  Read more about <a href="..." style="padding: 4px 0;">our pricing</a> and how we compare.
</p>
```

### 4. 600px max width with responsive scaling

**Why:** 600px is the de-facto email standard. Wider tables either crop in Outlook desktop or trigger horizontal scroll in Gmail web. On mobile, the 600px table should scale to full width.

```html
<table class="mobile-fluid" role="presentation" width="600"
       cellpadding="0" cellspacing="0" border="0" align="center">
  ...
</table>

<style>
  @media only screen and (max-width: 600px) {
    .mobile-fluid {
      width: 100% !important;
      max-width: 100% !important;
    }
  }
</style>
```

### 5. Finger-friendly padding (20px+ on mobile)

**Why:** content butted against the screen edges feels claustrophobic on mobile. 20-30px padding on all sides gives reading comfort.

```html
<td class="mobile-pad" style="padding: 40px;">
  Content
</td>

<style>
  @media only screen and (max-width: 600px) {
    .mobile-pad { padding: 24px !important; }
  }
</style>
```

Don't let mobile padding drop below 16px — content needs breathing room.

### 6. Full-width images that scale

**Why:** an image fixed at `width="600"` will overflow a 360px-wide mobile screen, causing horizontal scroll. Make images responsive:

```html
<img src="hero.png"
     alt="Smiling barista handing coffee to customer"
     width="600" height="300"
     style="display: block;
            width: 100%;
            max-width: 600px;
            height: auto;" />
```

- `width="600"` (attribute, not style) — for Outlook desktop, which doesn't fully respect CSS width
- `width: 100%; max-width: 600px;` (style) — scales down on mobile, caps at 600px on desktop
- `height: auto;` — preserves aspect ratio
- `display: block;` — removes the 4px bottom gap browsers add to inline images

For decorative icons (small images): use fixed `width="40" height="40" style="width: 40px; height: 40px;"` — don't scale.

## Worked example — mobile-first responsive template

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="color-scheme" content="light dark">
  <style>
    @media only screen and (max-width: 600px) {
      .mobile-fluid { width: 100% !important; max-width: 100% !important; }
      .mobile-pad   { padding: 24px !important; }
      .mobile-stack { display: block !important; width: 100% !important; }
      .mobile-text-big { font-size: 18px !important; }
    }
  </style>
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f4;">
  <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
    <tr>
      <td align="center" style="padding: 20px;">

        <table class="mobile-fluid" role="presentation" width="600"
               cellpadding="0" cellspacing="0" border="0"
               style="background-color: #ffffff; border-radius: 8px;">
          <!-- Header -->
          <tr>
            <td class="mobile-pad" style="padding: 40px 40px 20px;">
              <img src="https://cdn.example.com/logo.png" alt="Acme"
                   width="180" height="40"
                   style="display: block; width: 180px; height: 40px;">
            </td>
          </tr>

          <!-- Hero image -->
          <tr>
            <td>
              <img src="https://cdn.example.com/hero.jpg" alt="Hero alt text"
                   width="600" height="300"
                   style="display: block; width: 100%; max-width: 600px; height: auto;">
            </td>
          </tr>

          <!-- Body -->
          <tr>
            <td class="mobile-pad" style="padding: 40px;">
              <h1 style="font-family: Arial, sans-serif; font-size: 24px;
                         line-height: 1.3; color: #111; margin: 0 0 20px;">
                Hi {FIRST_NAME}
              </h1>
              <p class="mobile-text-big"
                 style="font-family: Arial, sans-serif; font-size: 16px;
                        line-height: 1.5; color: #444;">
                Body content with comfortable 16px font, 1.5 line-height.
              </p>

              <!-- 44px-min tap target CTA -->
              <table role="presentation" cellpadding="0" cellspacing="0" border="0"
                     style="margin: 30px 0;">
                <tr>
                  <td bgcolor="#0066CC" style="padding: 16px 32px; border-radius: 8px;">
                    <a href="https://example.com/cta"
                       style="color: #ffffff; text-decoration: none;
                              font-family: Arial, sans-serif; font-size: 16px;
                              font-weight: bold; display: inline-block;
                              min-height: 44px; line-height: 44px;">
                      Get started
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>

          <!-- Stacked footer columns -->
          <tr>
            <td>
              <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
                <tr>
                  <td class="mobile-stack" valign="top" width="50%" style="padding: 20px 40px;">
                    <p style="margin: 0; font-family: Arial, sans-serif; font-size: 14px; color: #666;">
                      Acme HQ<br>123 Main St<br>San Francisco, CA 94102
                    </p>
                  </td>
                  <td class="mobile-stack" valign="top" width="50%"
                      style="padding: 20px 40px; text-align: right;">
                    <p style="margin: 0; font-family: Arial, sans-serif; font-size: 14px; color: #666;">
                      <a href="{UNSUBSCRIBE_URL}" style="color: #0066CC;">Unsubscribe</a>
                    </p>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
        </table>

      </td>
    </tr>
  </table>
</body>
</html>
```

## Testing protocol

1. **Send a test to yourself** on at least: iPhone (Apple Mail), Android (Gmail app), iPad (Apple Mail), and one desktop browser (Gmail web)
2. **Resize browser windows** — open the email on desktop Gmail, then resize the window from 600px down to 320px while watching how it adapts
3. **Use Litmus / Email on Acid** for cross-client at-scale verification
4. **Check tap-target sizes** physically — tap each link/button with your thumb on the actual mobile device, not just visual inspection

The "send to your own phone" test catches 80% of mobile issues. Spend the 5 minutes.

## Common failures

| What you see | Likely cause | Fix |
|---|---|---|
| Horizontal scroll on mobile | Image or table wider than screen | Use `width: 100%; max-width: 600px;` on images; use `mobile-fluid` class on outer table |
| Text too small to read on mobile | Body < 16px | Bump to 16px; or 18px on mobile via `@media` |
| Mis-taps on CTAs | Buttons < 44×44px | Increase padding + line-height per Principle 3 |
| Tested desktop, mobile broken | Designed desktop-first | Re-design starting from mobile constraints |
| Hero image cropped on mobile | Fixed-width attribute without `max-width: 100%` style | Add both width attribute (Outlook) AND CSS width style (everyone else) |
| Stacked columns don't stack | `display: block` not in `@media` query | Verify the `@media (max-width: 600px)` block exists + `!important` on the property |
| Padding looks tight on small phones | Mobile-only padding > screen width minus content | Use 20-24px mobile padding, never less than 16px |

## FAQ

**Can I just use AcelleMail's drag-and-drop builder?** Yes — recent versions generate responsive mobile-friendly HTML automatically. Use the raw HTML editor only when you need precise control (e.g., porting an externally-designed template).

**My audience is 90% desktop B2B. Do I still need mobile-first?** Less urgent — but mobile share is rising in B2B too. Mobile-first design also forces clearer hierarchy, which benefits desktop. Worth doing even at low mobile percentages.

**What about email-on-watch (Apple Watch)?** Mostly text-only rendering; recipients almost never act on watch — they triage and read in detail later on phone. Don't optimize for watch specifically.

**Tablet design — separate breakpoint?** Most tablets (iPad, Galaxy Tab) render emails at desktop sizes. Don't add a tablet-specific breakpoint; design for mobile + desktop and tablets handle themselves.

**AMP for Email mobile?** Gmail supports AMP for Email; the responsive primitives are stronger. Niche; not bundled with AcelleMail.

## Related articles

- [Email Template Design Best Practices](https://acellesend.com/kb/articles/email-template-design-best-practices) — umbrella design rules
- [Dark Mode Email Design](https://acellesend.com/kb/articles/dark-mode-email-design) — most mobile reading happens in dark mode
- [Understanding Email Client Rendering Differences](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences) — full per-client rendering guide
- [Email Preheader Text](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster) — preheader is most-visible on mobile
- [Click Map Analysis](https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout) — measure tap behavior to validate mobile design
- [Personalization Beyond First Name](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags) — merge tags + mobile rendering

---

# Webhook Events Reference

> AcelleMail has three distinct webhook systems — customer-lifecycle webhooks (SaaS billing events), per-campaign webhooks (open/click/bounce on a specific campaign), and inbound webhooks from sending providers (SES SNS, Mailgun events). This reference documents each, with the real event names from the Acelle source — not the made-up ones some older guides list.

- Canonical URL: https://acellesend.com/kb/articles/webhook-events-reference
- Author: Tom Becker, Developer Editor
- Category: Developer Guide — REST API, webhooks, customization, extending source code, plugins
- Content type: reference · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-04-17 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail has **three distinct webhook systems**, and they're often confused. This reference documents each, with the real event names from the Acelle source (per `config/webhook_events.php` and the `webhooks` migration) — not the invented event names some older third-party guides list.

| System | Direction | Use case | Configured at |
|---|---|---|---|
| **Customer-lifecycle webhooks** | Outbound (Acelle → your app) | SaaS billing events — when a customer signs up, changes plan, cancels | Admin → Webhooks |
| **Per-campaign webhooks** | Outbound (Acelle → your app) | When a specific campaign sends / errors | Campaign → Edit → Webhooks tab |
| **Sending-provider notifications** | Inbound (sending provider → Acelle) | Bounce + complaint events from SES / Mailgun / SendGrid | `/api/v1/notification/bounce` + `/feedback` (auto-configured per sending server) |

Pick the right one for your use case before reading further.

## System 1 — Customer-lifecycle webhooks (the SaaS billing system)

This is the system **most people mean** when they ask about Acelle webhooks. Used by SaaS operators to keep their CRM, analytics, or billing system in sync with Acelle's customer + subscription state.

Configure at **Admin → Webhooks → New Webhook**. Each webhook subscribes to one event and posts to a URL of your choice.

### Available events

Per `config/webhook_events.php` — this is the authoritative list:

| Event | When it fires | Params posted |
|---|---|---|
| `new_subscription` | A customer starts a paid subscription on a plan | `customer_id`, `plan_id` |
| `cancel_subscription` | A customer cancels (subscription remains active until period end) | `customer_id`, `plan_id` |
| `terminate_subscription` | A subscription ends (after cancel grace period, or hard end) | `customer_id`, `plan_id` |
| `change_plan` | A customer moves between plans | `customer_id`, `old_plan_id`, `new_plan_id` |
| `new_customer` | A customer account is created | `customer_id` |
| `automation_webhook` | A "webhook" action fires inside an Acelle automation flow | `automation_id` |

> **`subscriber.created`, `campaign.opened`, `campaign.clicked` etc. are NOT events in this system.** Older docs sometimes list them; they don't exist. For subscriber + campaign events, use system 2 (per-campaign webhooks) or poll the API.

### Webhook delivery shape

```http
POST https://your-app.example.com/webhooks/acelle
Content-Type: application/x-www-form-urlencoded

customer_id=123&plan_id=45
```

Plus any auth header you configured (see below).

### Webhook configuration

The webhook form lets you set:

- **Name** — your label
- **Event** — one of the six above
- **Request method** — POST (default), GET, PUT, PATCH, DELETE
- **Request URL** — your endpoint
- **Authentication** — None, Bearer Token, Basic Auth, or Custom Header
- **Headers** — extra headers (e.g. `X-Source: acellemail`)
- **Body type** — `form-urlencoded` (default), `JSON`, `raw text`
- **Body params** — the event payload params (auto-populated) or your custom mapping
- **Retry on failure** — number of retries + delay between attempts
- **Status** — Active / Inactive

A webhook only fires when its `status = active`. Disable to pause without deleting.

### Triggering an automation_webhook from a flow

Inside an [automation flow](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions), you can add a "Webhook" action. When a subscriber reaches that step, the configured `automation_webhook` fires with the `automation_id` (and optionally subscriber merge tags in the body).

This is the bridge from automation flows to external systems — fire a webhook to your CRM when a lead reaches "qualified", or to Slack when a high-value subscriber takes a key action.

## System 2 — Per-campaign webhooks

For events about a *specific campaign* (sent, opened, clicked, bounced, complained, unsubscribed), use the per-campaign webhook system.

Configure at **Campaign → Edit → Webhooks tab** (or via the campaign-creation wizard). Each campaign can have multiple webhooks; each subscribes to one event.

### Available campaign events

The per-campaign webhook table (`campaign_webhooks`) supports:

- `campaign.sent` — campaign send completed
- `campaign.open` — a subscriber opened the email
- `campaign.click` — a subscriber clicked a tracked link
- `campaign.bounce` — a delivery failure (hard / soft)
- `campaign.complaint` — a spam complaint
- `campaign.unsubscribe` — a recipient unsubscribed via the campaign

Payload includes the subscriber email, campaign uid, and event-specific fields (clicked URL, bounce reason, etc.).

### When to use this vs polling the API

| Pattern | Use when |
|---|---|
| Per-campaign webhook | You need real-time per-event reaction (push to Slack on bounce; sync to CRM on click) |
| Poll `/api/v1/campaigns/{uid}/...-log/download` | You need a batch import of all events for a campaign; or your app can't accept inbound HTTP |

For most external-system integrations, webhooks win — they're real-time and lower-cost than polling.

## System 3 — Sending-provider inbound notifications

The third system is for **inbound** webhooks — your sending provider (Amazon SES via SNS, Mailgun event hooks, SendGrid event webhook) posts bounce + complaint events to AcelleMail.

You usually don't configure these directly — Acelle auto-configures them when you set up the sending server (e.g. SES SNS topic + subscription is created during the SES sending-server wizard).

Routes:

- `POST /api/v1/notification/bounce` — receives bounce events
- `POST /api/v1/notification/feedback` — receives spam-complaint events

If you're building a custom sending provider integration, you'd POST events to these endpoints in the documented format. Otherwise, ignore — Acelle handles it transparently.

## Signature verification (where applicable)

For the **System 1 customer-lifecycle webhooks**, signature verification depends on what auth method you configured:

- **Bearer Token** — Acelle sends `Authorization: Bearer <your-token>`. Your endpoint checks the header matches the token you set. Simple, recommended.
- **Basic Auth** — Acelle sends `Authorization: Basic <base64(user:pass)>`. Your endpoint validates as usual.
- **Custom Header** — Acelle sends a custom header with a static value (e.g. `X-Webhook-Secret: my-shared-secret`). Verify equality with constant-time comparison.

Acelle's webhook system does NOT use HMAC signature on the body by default (unlike Stripe / GitHub). The auth header is the entire security mechanism. **Make sure your endpoint is HTTPS-only** so the auth header isn't exposed in transit.

If you need HMAC body signing, implement it via the **Custom Header** option — configure a header that contains the HMAC of the body computed in a Laravel listener you add via [extending source](https://acellesend.com/kb/articles/extending-acellemail-source-code).

## Retry behaviour

The webhook config lets you set:

- `setting_retry_times` — how many times to retry on non-2xx response
- `setting_retry_after_seconds` — wait time between retries

Recommended defaults: **3 retries with 60-second backoff**. Past 3 retries, the webhook is marked failed and won't fire again until manually re-enabled.

Your endpoint MUST:

- Return `2xx` within 30 seconds to count as success
- Be **idempotent** — Acelle may retry on transient failures; your endpoint should handle duplicate deliveries gracefully (key on `customer_id + event + timestamp`)
- Acknowledge quickly + process async — if your processing takes > 5s, accept the webhook, enqueue a job, return 200 immediately. Slow webhook handlers cause retry storms.

## Verifying webhooks work

```bash
# 1. Set up a temporary catch-all endpoint at https://webhook.site/
#    (or use ngrok to forward to localhost)

# 2. Configure a webhook in Admin → Webhooks → New
#    Event: new_customer
#    URL: <your webhook.site URL>

# 3. Trigger the event — create a new test customer in the admin

# 4. Check webhook.site — you should see the POST with customer_id in the body
```

If nothing arrives:

- Check **Admin → Webhooks → Logs** for the delivery attempt and the response code Acelle saw
- Verify your endpoint returned `2xx`
- Verify webhook status is "Active"
- Check the queue is processing (webhooks are dispatched via the same queue as the rest of Acelle — see [Setting Up Queue Workers](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs))

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| No webhook fires when expected | Webhook status = Inactive, or event mismatch | Verify status is Active; verify the event matches what you actually triggered |
| Endpoint receives one webhook many times | Endpoint returned non-2xx; Acelle retried | Make endpoint return 2xx faster; or fix the underlying issue causing the error response |
| Webhook payload missing custom fields | Customer-lifecycle webhooks only carry `customer_id` / `plan_id`. Subscriber data isn't included | Use `customer_id` to fetch the customer via API for additional data |
| Webhook arrives without auth header | Auth method = None | Reconfigure with Bearer / Basic / Custom Header |
| Endpoint says "signature invalid" | You enabled HMAC verification but Acelle doesn't ship HMAC | Use header-based auth instead, OR implement HMAC via a custom listener (extending source) |
| Webhooks back up after a sending storm | Queue worker pool saturated | Increase worker numprocs (see [scaling guide](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day)) |
| Per-campaign webhook fires before "sent" event for transactional sends | Race condition between send + tracking event | Process events idempotently; don't assume strict ordering |
| Need a subscriber.created event | Doesn't exist in customer-lifecycle webhooks | Use per-campaign webhook for `campaign.sent`, OR poll the subscribers API for new records |

## FAQ

**Why no `subscriber.created` event?** Acelle's customer-lifecycle webhook system was designed for SaaS billing — it covers events the operator cares about (new customer, plan change, cancellation), not events the customer would care about (new subscriber on their list). For subscriber events, use the per-campaign webhook system (system 2) or poll the API.

**Can I add custom events?** Yes — fire a custom event from a Laravel listener (added via [extending source](https://acellesend.com/kb/articles/extending-acellemail-source-code)), then add it to `config/webhook_events.php`. It'll appear in the webhook config UI.

**Do webhooks count against API rate limits?** No — webhooks are outbound from Acelle, not API calls from your code.

**Can the webhook payload be JSON instead of form-urlencoded?** Yes — set "Body type" to JSON in the webhook config. The same param keys are posted, just JSON-encoded.

**How do I retry a manually-failed webhook?** Admin → Webhooks → Logs → click the failed delivery → "Retry". Or trigger the original event again.

**Where can I see what URL Acelle is calling?** Admin → Webhooks → click the webhook → see configured URL + recent delivery log.

**Can I forward webhook payloads to multiple endpoints?** Configure multiple webhooks for the same event, each with a different URL. Each fires independently.

## Related articles

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — for polling the API as an alternative
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — adding custom events / listeners
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — webhooks pair with SaaS use case
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) — fire webhooks from automation flows
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — webhook delivery uses the queue
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — the inverse (your app calls Acelle)
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — SNS notification setup for inbound (system 3)

---

# Preheader Text — The Hidden Conversion Booster in AcelleMail

> After the subject line, recipients see the preheader — the gray text snippet that follows. Setting it explicitly can lift open rates 5-10% over the auto-extracted default. This guide walks the AcelleMail field + the writing patterns.

- Canonical URL: https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-04-03 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What the preheader is

The preheader is the gray text shown immediately AFTER the subject line in most inbox lists. Examples:

```
Inbox preview:
─────────────────────────────────────────────
Brand Marketing  ·  Last chance for 20% off
The sale ends tonight — your discount code...
─────────────────────────────────────────────
              ↑
         Preheader
```

If you don't set the preheader explicitly, the inbox auto-grabs the first text from your email body. Usually this is garbage like "Trouble seeing this email? View in browser" — wasted real estate.

Setting an explicit preheader = 5-10% higher open rates, measured across many senders.

## Set the preheader in AcelleMail

In the campaign builder's **Setup** step, find the **Preheader** field. It sits next to the Subject:

![Preheader field in setup](https://acellesend.com/plugins/acelle/landing/images/help/kb/preheader/01-preheader-in-context.png)

Type your preheader text. Once typed:

![Preheader typed + saved](https://acellesend.com/plugins/acelle/landing/images/help/kb/preheader/02-preheader-typed.png)

Save. The campaign's metadata now includes both subject + preheader; receiver inboxes display both.

## Preheader writing patterns

Treat the preheader as a "second subject line" — different angle, complementary message:

| Pattern | Subject | Preheader |
|---|---|---|
| **Extend the subject** | "Last chance for 20% off" | "Discount ends tonight at midnight" |
| **Add urgency** | "Spring sale is live" | "1,247 customers already saved $40+" |
| **Soften with empathy** | "We noticed you're inactive" | "No pressure — just curious how you're doing" |
| **Curiosity tease** | "This is the email I should have sent last month" | "Better late than never. Let me explain..." |
| **Concrete benefit** | "Your weekly digest is here" | "5 actionable tips, 5 minute read" |
| **Personalization** | "{{ subscriber.first_name }}, here's your update" | "We've been working on something — hope it helps" |

## Length matters

Preheaders display ~40-100 characters in most inboxes:

```
Apple Mail iOS:        ~50 chars before truncation
Gmail desktop:         ~80 chars
Gmail mobile (Android): ~55 chars
Outlook desktop:       ~60 chars
Yahoo:                 ~70 chars
```

Aim for 40-80 chars. Front-load the most important words.

## Verify in Preview

After typing the preheader, click **Preview** in the campaign builder:

![Campaign preview — desktop](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

The preview shows the full email body but NOT the inbox-preview format. For the actual recipient-side inbox view:

1. Click **Send test** → send to your own personal Gmail / Outlook
2. Open the test in each client
3. Verify the preheader text is what you typed (not the auto-extracted default)

In mobile view:

![Mobile preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Preheader field grayed out | Selected campaign template overrides preheader | Some templates hardcode preheader; remove the override in template settings |
| Preheader saved but inbox shows "View in browser" | Preheader text contains hidden HTML / image alt text first | Verify preheader is plain text only |
| Preview shows preheader, real inbox doesn't | Inbox auto-extracted first body text instead | Add hidden preheader markup at top of email body (advanced) |
| Mobile preview truncates the preheader | Too long | Cut to 40-50 chars front-loaded |
| Preheader includes merge tags that don't resolve | Tag name mismatch | Use `{{ subscriber.first_name | default('there') }}` with fallback |
| Test send shows preheader correctly in 3 of 4 clients | Client-specific auto-extraction differences | Lengthen the explicit preheader to 80+ chars to ensure all clients use it |

## Common preheader mistakes

| Mistake | Why it hurts |
|---|---|
| Leaving preheader blank | Inbox auto-extracts garbage ("View in browser") |
| Repeating the subject verbatim | Wasted space; no incremental signal |
| Putting the unsubscribe link in preheader | Recipient sees "unsubscribe" before opening — reduces open rate |
| Cramming 200+ chars | Most gets truncated; first 40 chars are all that matter |
| Using emoji-only preheader | Some clients render as boxes; not all support emoji |
| Generic ("Hi there!") | Provides no incremental signal |

## What to put in the preheader

Best uses (ranked by reliability):

1. **Restate the benefit + add urgency** — "Sale ends tonight"
2. **Show social proof** — "1,247 customers already saved"
3. **Hint at content** — "5 actionable tips inside"
4. **Tease curiosity** — "Wait until you see #3..."
5. **Personalize** — "Maria, your update is ready"

Worst uses:

- Generic greeting ("Hello!")
- Sender reminder ("From Brand")
- Already-in-subject information (redundant)
- Unsubscribe link (kills open rate)
- "View in browser" (signals automation; lowers trust)

<details>
<summary><strong>Advanced: hidden preheader markup + A/B testing preheaders + per-segment preheader personalization</strong></summary>

**Hidden preheader markup** (for ensuring the right text appears even when inboxes auto-extract):

Most email templates include hidden preheader text at the top of the body:

```html
<!-- Preheader (hidden visually; readable by inbox extractors) -->
<div style="display: none; max-height: 0; overflow: hidden;">
  Your preheader text here. Cannot exceed ~80 chars to avoid truncation.
</div>
```

Then add invisible filler to push the actual email content:

```html
<div style="display: none; max-height: 0; overflow: hidden;">
  &zwnj;&nbsp; &zwnj;&nbsp; &zwnj;&nbsp; (etc — pad with zero-width-non-joiner)
</div>
```

The inbox extractor reads the first ~80 chars; recipients see only the visible email body.

AcelleMail's builder handles this automatically when you set the Preheader field. For raw HTML mode, add it yourself.

**A/B testing preheaders:**

If your subject lines are well-optimized, the next ROI lever is preheader testing:

1. New campaign → A/B test type
2. Variant A: "Sale ends tonight"
3. Variant B: "1,247 customers already saved"
4. Same subject + same body for both
5. Test on 20% sample; winner sends to 80%

Subject + preheader together = "what they see before opening." Combined optimization yields compound gains.

**Per-segment preheader personalization:**

For sophisticated segmentation, vary the preheader by audience tag:

```
Engaged segment:   "You're one of our most active subscribers"
At-risk segment:   "We've missed you — here's something to come back"
New segment:       "Welcome — here's the catch-up you've been waiting for"
```

In AcelleMail, this requires per-segment campaign splits (one campaign per segment with appropriate preheader). Some senders use dynamic Liquid in the preheader:

```
{% if 'vip' in subscriber.tags %}VIP early access — 24h before everyone else
{% else %}Spring sale starts now{% endif %}
```

But preheader display from receiving clients can be unpredictable with dynamic content — test before relying on this.

**Preheader for transactional emails:**

For password resets / receipts / order confirmations, the preheader should:

- Mention the action briefly ("Password reset link inside")
- NOT include sensitive details (not "Your code: 123456" — visible in inbox preview = security issue)
- NOT include the unsubscribe link (transactional emails don't have one)

```
Transactional subject: "Your password reset link"
Transactional preheader: "Click below; expires in 30 minutes"
```

**Multi-language preheaders:**

For multilingual senders:

```
{% if subscriber.locale == 'es' %}Última oportunidad: 20% de descuento
{% else %}Last chance: 20% off{% endif %}
```

Test in Spanish-speaking + English-speaking client previews to ensure render correctly.

</details>

## Related articles

- [Your first email campaign in AcelleMail](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Email template design best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)
- [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)

---

# Build a Signup Form and Embed It on Your Site

> AcelleMail has a drag-and-drop visual builder for signup forms with 4 popup layouts and 5 display triggers (immediate, delay, exit intent, on click, first visit). Walkthrough with real screenshots of every panel.

- Canonical URL: https://acellesend.com/kb/articles/creating-effective-signup-forms-and-landing-pages
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: beginner
- Tags: Segmentation, Double Opt In, Custom Fields
- Published: 2026-04-02 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

You want to turn your website traffic into mail-list subscribers. AcelleMail has a visual form builder under **Forms** in the left sidebar — drag-and-drop, 4 popup layouts out of the box, 5 display triggers (immediate, after delay, on exit intent, on element click, first visit only), and a one-click way to publish the embed code into a connected website.

This walkthrough shows every panel of the builder and tells you what each setting actually does in production.

## Before you start

- **Decide which list new signups will land in.** Every form is bound to one list at creation time. If you don't have a list yet, create one in **Lists → + New list** first — you can't bind a form to a list that doesn't exist.
- **Know your minimum fields.** AcelleMail's form templates default to Email + First name + Last name + Company + Address + Phone + Web — way too many for a typical signup. Plan to remove what you don't need before publishing.
- **A connected website is optional.** You can publish a form and grab its HTML embed code without ever connecting a website. The "Connect to website" feature is for hosted/managed sites where AcelleMail can inject the popup script for you.

## Step 1 — Open the Forms section

Click **Forms** in the left sidebar (under *Audience*). The URL is `/rui/forms`.

![Forms index page with header "Capture new subscribers", stats (4 forms · 2 published · 2 draft), 3 action cards (Create a signup form / Connect to website / Embed on page), and a forms table with 4 forms](https://acellesend.com/plugins/acelle/landing/images/help/kb/forms/01-forms-index.png)

You see three things:

- **Three action cards** — *Create a signup form*, *Connect to website*, *Embed on page*. These are the same three things you'll do for every new form; the cards just deep-link into each step.
- **Stats row** — total forms, how many are published vs draft, how many connect to a website.
- **Forms table** — every form you've created, with its connected list, status (Published / Draft), website connection, and trigger (e.g. *On click*, *Not connected*). Each row has a **Build** button that re-opens the visual editor.

Click the green **+ Create form** button in the top right.

## Step 2 — Pick a list and a template

A modal slides in. AcelleMail asks for the bare minimum it needs before opening the builder.

![Create form modal showing Form name input "Untitled form", Connect to list dropdown (placeholder "Select a list..."), Form template tabs Base (4) / Extended (0), and 4 popup template thumbnails — Popup Form Banner, Popup Form Right Banner, Popup Form Top Banner, Popup Form Bottom Banner. Popup Form Banner is selected with a teal border. Footer has Cancel + "+ Create form" buttons.](https://acellesend.com/plugins/acelle/landing/images/help/kb/forms/02-create-popup.png)

Three fields, in order:

- **Form name** — what appears in the forms table and in the campaign builder later. Use something concrete like *"Homepage hero — newsletter"* or *"Footer minimal"*, not the default *"Untitled form"*.
- **Connect to list** — the mail list that receives new signups. Required. You can change it later in *Settings*, but new signups before the change land in the original list.
- **Form template** — there are two tabs: **Base** (4 popup layouts ship with AcelleMail) and **Extended** (custom templates your admin creates). The 4 base templates are *Popup Form Banner*, *Popup Form Right Banner*, *Popup Form Top Banner*, and *Popup Form Bottom Banner* — all variants of where the popup anchors on the page.

Click **+ Create form**. AcelleMail saves a draft and opens the visual builder.

## Step 3 — Drag fields onto the canvas

You land on the builder. There are three things on screen worth knowing.

![Form builder canvas with: left = browser mockup showing the form preview with "Welcome to {{LIST_NAME}}!" + fields (Email, First name, Last name, Company, Address, Phone, Web); right = "Form Fields" panel showing the connected list (Beta Testers - 18,829 subscribers - 8 fields), a "Load list defaults" button, and a draggable widget palette (Email REQUIRED, First name, Last name, Company, Address, Phone, Web)](https://acellesend.com/plugins/acelle/landing/images/help/kb/forms/03-builder-canvas.png)

- **Live preview** in the centre. The browser-mockup chrome shows where the form will sit relative to a real page. Anything you drag from the right panel appears here.
- **Form Fields panel on the right.** Lists every field on the connected mail list — Email is always required and pre-included. Drag any other field onto the canvas to add it. Drag a field off the canvas to remove it. The **Load list defaults** button repopulates the canvas with one widget per list field — a fast reset.
- **Top bar.** From left: form name + status badge (DRAFT / PUBLISHED), responsive previews (desktop / tablet / mobile), undo / redo, **Preview**, **Publish** / **Unpublish**, **Save**, **Close**. The tip at the top of the right rail — *"Just drag & drop the widget into the page content to insert!"* — is the only instruction the builder gives you.

**The two things you'll almost always do first:**

1. Click into the *Welcome to {{LIST_NAME}}!* heading and rewrite it. The `{{LIST_NAME}}` token auto-fills with the connected list's name; replace it if you want fixed copy.
2. Drag off fields you don't need (Address, Phone, Web are usually overkill for a newsletter signup). The fewer fields, the higher conversion.

## Step 4 — Change the layout (or fine-tune the design)

The right rail has three vertical tabs: **Layouts**, **Design**, **Settings**. Click **Layouts** to switch the popup's anchor position without losing your field work.

![Form builder showing the Layouts panel on the right with the 4 popup template thumbnails (Popup Form Banner, Popup Form Right Banner, Popup Form Top Banner, Popup Form Bottom Banner) under the heading "All layouts" and the instruction "Choose a layout to change the look and feel of your form."](https://acellesend.com/plugins/acelle/landing/images/help/kb/forms/04-builder-layouts.png)

The 4 layouts are different anchor positions:

| Layout | Where the popup appears | Typical use |
|---|---|---|
| **Popup Form Banner** | Centred modal overlay | First-visit lead magnets |
| **Popup Form Right Banner** | Anchored to the right edge | Slide-in offers (less intrusive) |
| **Popup Form Top Banner** | Pinned to the top of the page | Announcement strips |
| **Popup Form Bottom Banner** | Pinned to the bottom | Cookie-bar-style sign-ups |

Swap layouts at any time — your field work carries over. **Design** is the middle tab and gives you finer control over fonts, colours, and spacing.

Don't forget to preview narrow viewports — click the **mobile** icon in the top bar.

![Builder with the mobile preview active — same form rendered in a narrow phone-width canvas, fields stacked vertically](https://acellesend.com/plugins/acelle/landing/images/help/kb/forms/06-builder-mobile-preview.png)

Always check mobile before publishing. The default Base templates are responsive, but custom layouts and long field labels can wrap badly on narrow screens.

## Step 5 — Configure when the popup fires

Click the **Settings** tab on the right rail. This is the most important panel for popup forms — it controls *when* the form appears to visitors.

![Form builder Settings panel showing: Form name, Mail list dropdown (Beta Testers — 18829 subscribers), Overlay opacity slider at 50%, Display trigger dropdown showing "On element click" with help text "Show popup when a specific element is clicked.", Element ID or selector input "abc123", and a Save settings button](https://acellesend.com/plugins/acelle/landing/images/help/kb/forms/05-builder-settings.png)

What each setting does:

- **Form name** — change it any time, no signups affected.
- **Mail list** — swap which list receives new signups. New submissions after the save go to the new list; existing signups stay in their original list.
- **Overlay opacity** — only relevant for the centred *Popup Form Banner* layout. 0% = transparent background; 100% = fully black overlay. 40–60% is the typical sweet spot.
- **Display trigger** — the heart of the popup. Five options:

| Trigger | What happens | When to use |
|---|---|---|
| **Immediately** | Popup shows on page load | Lead-magnet landing pages where the offer IS the page |
| **After delay** | Wait N seconds (you pick) | Lets visitors see your content before being asked to subscribe |
| **On exit intent** | Mouse leaves the viewport | Cart abandonment, last-chance offers — desktop-only |
| **On element click** | When a specific element (by ID or CSS selector) is clicked | "Subscribe" links scattered across your site that all open the same popup |
| **First visit only** | Cookie-based — popup shows once per browser | Aggressive lead-gen without annoying returning visitors |

When you pick *On element click*, an extra **Element ID or selector** input appears — paste the element ID (`abc123` for `id="abc123"`) or any valid CSS selector (`.subscribe-link`).

Click **Save settings** when you're done. Settings are not part of the canvas — they save independently from your design.

## Step 6 — Preview, publish, and embed

When the form is ready:

1. Click **Preview** in the top bar — opens a real rendered version in a new tab. Click around as if you were a visitor.
2. Click **Publish**. The status badge switches from DRAFT to PUBLISHED, and AcelleMail makes the form's URL + embed snippet available.
3. To **embed on your own site**: from the forms list, click the form's row menu → **Embed on page**. AcelleMail gives you a small script tag to paste before the closing `</body>` of your website. The popup will fire according to the trigger you set in Step 5.
4. To **connect a managed website**: if you have a website registered in **Integration → Websites**, the *Connect to website* card on the Forms index links the form's popup directly to that site — no manual embedding required.

Unpublishing a form takes it off your connected websites immediately; the form URL also stops accepting new subscribers.

## Common issues

| What you see | What to do |
|---|---|
| Popup never appears on the website | The form is still in **Draft**. Open the form in the builder and click **Publish** — the badge must say PUBLISHED. |
| Popup appears on every page load even though you set "First visit only" | Cookie-based throttling can be defeated by Incognito mode or cookie clears. Test in a regular profile. |
| Submissions land in the wrong list | You changed the *Mail list* in Settings AFTER subscribers signed up. The change is only forward-looking — historical signups stay in the original list. |
| Mobile users see fields cut off | Switch to the **mobile** preview in the top bar and shorten field labels or remove non-essential fields. The Base templates are responsive, but long custom labels overflow on phones. |
| "Connect to website" shows no options | You haven't registered a website yet. Go to **Integration → Websites** and add one. Otherwise just use the manual embed snippet. |
| Embed code does nothing on the live site | Most common cause: it was pasted in the `<head>` before the page loaded. Move it just before `</body>`, OR wrap it in `DOMContentLoaded`. |
| "Untitled form" entries pile up | You opened the builder and closed without renaming. Delete them from the forms list — they're not connected to anything until you Publish. |

## After your form is live

- **Watch subscriber growth on the connected list.** Open the list, look at the *Overview* tab — *Subscribers* count should tick up after your first day live.
- **Trigger one welcome automation per list.** A signup form that just stuffs people into a list and never emails them is a wasted opportunity. Build a 3-step welcome series under **Automation** that fires *On new subscriber*.
- **A/B test the trigger.** Try *Immediately* vs *After delay 30s* vs *On exit intent* — different audiences respond differently to interruption.
- **Audit empty submissions.** If the form had an Address field and 90% of submissions left it blank, drop the field. Every field is a tax on conversion.

## Related articles

- [Import Contacts from CSV: A 3-Step Walkthrough](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Segments: Slice Your List Into Audiences That Convert](https://acellesend.com/kb/articles/advanced-segmentation-strategies)
- [Custom Fields and Personalization Tags](https://acellesend.com/kb/articles/how-to-add-custom-fields-to-your-list)
- [Building a Welcome Email Series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

# CASL Compliance in AcelleMail: What Canadian Senders Need

> Canada's Anti-Spam Legislation is one of the strictest in the world — fines reach CAD $10 million per violation. This guide covers express vs implied consent, what every Canadian CEM must contain, and how to record consent inside AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/casl-canadian-anti-spam-requirements
- Author: Daniel Reyes, Technical Editor
- Category: Security & Compliance — GDPR, CAN-SPAM, CASL, SSL/TLS, encryption, data privacy, audit
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-29 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Canada's Anti-Spam Legislation (CASL, in force since 2014) is the strictest commercial-email law in the world. It covers any "commercial electronic message" (CEM) sent **to or from** a Canadian recipient — so even non-Canadian businesses owe compliance the moment they email someone in Canada. Penalties go to **CAD $10 million per violation** for businesses and **CAD $1 million** for individuals.

CASL differs from CAN-SPAM in one big way: **CASL is opt-in by default**. You need consent before you send. CAN-SPAM is opt-out — you can send first, must allow unsubscribe. If you're emailing both audiences, CASL is the higher bar — meet it and you've automatically met CAN-SPAM.

## Express vs implied consent

Every Canadian subscriber on your list needs **either** express consent or a valid implied-consent category.

| Type | Definition | Expires |
|---|---|---|
| **Express** | Subscriber actively opted in — ticked an un-pre-ticked checkbox, filled a signup form, confirmed via double opt-in | Never (until they withdraw) |
| **Implied — existing business relationship** | They bought from you, made an inquiry, or had a contract with you | **2 years** after the transaction |
| **Implied — non-business relationship** | They donated to your charity or volunteered | **2 years** |
| **Implied — membership** | They are an active member of your club, association, or political party | While membership is active |
| **Implied — conspicuous publication** | They published their business email publicly without a "no spam" notice | Only for messages relevant to their role |

**Key practical rule:** the burden of proof is on **you**, not the regulator. If a Canadian subscriber files a complaint, you must produce **when, where, and how** their consent was obtained. If you can't, the consent is treated as never having existed.

## What every CEM must contain

CASL §6 requires four things in the body of every commercial email:

1. **Your full legal name** (or operating name, if different and registered)
2. **A mailing address** plus **one** of: telephone, email, or website URL
3. **A clear, functioning unsubscribe mechanism** — link or reply-to instruction
4. **Unsubscribe must be honoured within 10 business days**

AcelleMail handles #3 (unsubscribe link is automatic) and #4 (suppression is instant — well under the 10-day ceiling). #1 and #2 you fill in once at **Account → Contact** — *Company info* (legal name) + *Address* card — and AcelleMail injects both into every campaign footer.

## Recording consent inside AcelleMail

CASL audits are paperwork-heavy. The fastest way to stay defensible is to **record consent origin at signup** as a list custom field.

1. Open **Lists → [your list] → Manage list fields**
2. Click **Create field** and add a text field called `consent_source`
3. (Optional) Add a second text field `consent_date` — type `date`
4. In your signup form (built in **Forms → [your form] → Edit**), include both fields as **hidden inputs** pre-populated with the source (e.g. `homepage-hero-2026-05`) and current date
5. Confirm by opening one new subscriber after signup — both fields should be populated under the subscriber's profile

That gives you a per-subscriber record of where and when consent was obtained, queryable from the subscribers table.

For higher-stakes audits — large lists, regulated industries — turn on **double opt-in** under **Lists → [your list] → Edit → Subscription settings**. Double opt-in produces a server-side log entry every time someone confirms, which is admissible as a record of express consent. See [Double Opt-In vs Single Opt-In](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose).

## The transition period is over

CASL gave a three-year window for "implied consent from before July 2014" to ride out without action — that window **closed July 1, 2017**. Any consent that was implied-only on July 1, 2014, has long since expired. If you haven't audited your Canadian segment since then, do it now: anyone with neither express consent nor an active implied-consent category must be removed.

The fastest way to surface this in AcelleMail: build a segment filtered by `country = Canada` AND `consent_source IS NULL`, export to CSV, and decide row-by-row. See [Advanced Segmentation Strategies](https://acellesend.com/kb/articles/advanced-segmentation-strategies) for the segment builder.

## Common issues

| What you see | What to do |
|---|---|
| You inherited a list and have no record of how Canadian subscribers were collected | Segment them out and re-permission via a single confirmation email ("Do you still want to hear from us?"). Anyone who clicks confirm becomes express-consent. The rest must be removed. |
| Your form has a pre-ticked consent checkbox | Un-tick it. CASL (and GDPR) explicitly disallow pre-ticked boxes as express consent. |
| `consent_source` is empty for old subscribers | Backfill what you can ("pre-2024-migration") — partial provenance is better than none. Going forward, every new signup should populate it from a hidden form field. |

## What to do after

1. Verify **Account → Contact** has your **legal name** + **mailing address** filled in.
2. If you collect Canadian subscribers, turn on **double opt-in** on those lists.
3. Add `consent_source` + `consent_date` custom fields to every list that collects email.

## Related articles

- [CAN-SPAM Compliance in AcelleMail](https://acellesend.com/kb/articles/can-spam-compliance-checklist)
- [GDPR Compliance for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing)
- [Double Opt-In vs Single Opt-In: Which to Choose](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose)
- [Creating Effective Signup Forms and Landing Pages](https://acellesend.com/kb/articles/creating-effective-signup-forms-and-landing-pages)

---

# Server Requirements and Hosting Options for AcelleMail

> The right server for AcelleMail is dictated by send volume, not subscriber count. A list of 5M subscribers mailed monthly needs less compute than a list of 100k mailed daily. This article gives concrete specs at four volume tiers, a current 2026 provider comparison, and the operational considerations that determine your real cost.

- Canonical URL: https://acellesend.com/kb/articles/server-requirements-and-hosting-options
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: reference · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-03-25 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The right server for AcelleMail is dictated by **send volume**, not subscriber count. A list of 5 million subscribers that you mail once a month needs less compute than a list of 100,000 you mail daily. This article gives concrete specs at four volume tiers, a current 2026 provider comparison, and the operational considerations that determine your real cost.

## Pick a tier from your monthly send volume

| Tier | Monthly sends | Recommended specs | Notes |
|------|---------------|-------------------|-------|
| **Hobby** | < 50k | 1 vCPU, 2 GB RAM, 30 GB SSD | Single droplet. Queue uses database driver. Avoid 1 GB / 512 MB shapes — won't fit PHP-FPM + MySQL + Redis. |
| **Small (baseline)** | 50k - 500k | **2 vCPU, 4 GB RAM, 50 GB SSD** | MySQL on the same host. Redis for queue + cache. 2 supervisor workers. |
| **Medium** | 500k - 5M | 4 vCPU, 8 GB RAM, 100 GB SSD | DB on the same host or split to managed. 4 supervisor workers. |
| **Large** | 5M - 50M | 8 vCPU, 16 GB RAM, 200 GB SSD + separate DB | DB on its own host (managed RDS / DO Managed DB / self-managed). 4–8 queue workers. Consider 2+ application hosts behind a load balancer with Redis-backed sessions. |
| **XL** | 50M+ | Custom — multi-host, dedicated DB cluster, Redis cluster, separate sending-cluster MTAs | Talk to AcelleMail support; this tier is rare and bespoke. |

These figures assume 30-day rolling average. **Black Friday / Cyber Monday spikes need ~2× headroom** — provision against your peak day, not your mean.

## Software baseline (any tier)

Per Acelle's [`InstallController` system-compatibility check](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404#step-2--php-83-the-engine) (the source-of-truth for what the wizard accepts):

| Component | Minimum (wizard-enforced) | Recommended (2026) |
|-----------|---------------------------|---------------------|
| OS | Ubuntu 22.04 / Debian 11 / Rocky 8 | **Ubuntu 24.04 LTS** |
| PHP | **8.3.0** (wizard hard-fails below) | **PHP 8.3** (8.4 works informally; PHP 9 not yet) |
| MySQL / MariaDB | MySQL 5.7 / MariaDB 10.3 | **MySQL 8.0** or **MariaDB 11.4 LTS** |
| Redis | (optional) | **7.x** — strongly recommended for queue + cache |
| Web server | Apache 2.4 | **Nginx 1.22+** |
| Process manager | systemd | **Supervisor** (worker recovery is cleaner than systemd's restart on busy queues) |
| TLS | Let's Encrypt | **certbot --nginx** with auto-renewal |

**Required PHP extensions** (all wizard-enforced):

```
mysqli  openssl  mbstring  pdo  tokenizer  zip  imap  sqlite3
gd  fileinfo  curl  xml  bcmath  intl  redis  mailparse  gmp
```

The two operators most often forget: **`php8.3-imap`** (FBL bounce handler) and **`php8.3-sqlite3`** (internal data store). The wizard's System Check hard-fails on either — see the [Ubuntu install Step 2](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404#step-2--php-83-the-engine).

## Provider comparison (2026 list prices)

The table uses the **Small tier** (50k - 500k sends/month) as a like-for-like comparison. All prices are USD/month, before any committed-spend discount. Adjust by tier (~2× per step up).

| Provider | Plan | vCPU | RAM | SSD | Egress | $/mo | Notes |
|----------|------|------|-----|-----|--------|------|-------|
| **Hetzner** | CX22 | 2 | 4 GB | 40 GB | 20 TB | **~$5** | Cheapest globally for the spec; EU + US datacenters; AMD/Intel mixed |
| **Hetzner** | CPX21 | 3 (AMD) | 4 GB | 80 GB | 20 TB | ~$9 | Best CPU-to-$ ratio; faster than CX22 |
| **DigitalOcean** | Premium AMD 2vCPU | 2 (AMD) | 4 GB | 80 GB | 4 TB | ~$28 | Best ops UX — managed DB, snapshots, Reserved IPs, monitoring all built-in |
| **Linode** | Shared 4GB | 2 | 4 GB | 80 GB | 4 TB | ~$24 | Slightly cheaper than DO, slightly less polished UI; Akamai-owned |
| **Vultr** | High-Frequency 2vCPU | 2 | 4 GB | 64 GB | 3 TB | ~$24 | Strong CPU benchmark; better routes to Asia than DO |
| **AWS EC2** | t3.medium + 50 GB gp3 | 2 | 4 GB | 50 GB | 100 GB free, then $0.09/GB | ~$32 on-demand / ~$20 RI | Most flexible; egress costs add up unless sending via SES in-region |
| **AWS Lightsail** | $20 plan | 2 | 4 GB | 80 GB | 4 TB | ~$20 | EC2 with the bill-predictability of DO; outgrown at Medium tier |

**Top picks per use case:**

- **Side-project / hobby** (< 50k/month): Hetzner CX22 (~$5/mo) — best price-performance globally.
- **Production small business** (50k - 500k/month): Hetzner CPX21 (~$9) for cost, DigitalOcean Premium AMD (~$28) for ops quality.
- **Production medium business** (500k - 5M/month): DigitalOcean managed DB + Premium AMD droplet, ~$80/mo all-in. AWS if you already have AWS infra and can leverage SES in-region.
- **AWS-native shops:** EC2 t3.medium (or m6i.large at Medium tier) + Reserved Instance + SES in-region — see the [AWS EC2 install guide](https://acellesend.com/kb/articles/install-acellemail-on-aws-ec2) for the full cost breakdown.
- **Container-first teams:** any of the above + the [Docker deployment guide](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail).

## Cost beyond the server

The server is rarely the largest line item. The real recurring costs:

| Line item | Typical $/month at Small tier |
|-----------|-------------------------------|
| Server (above) | $5 – $32 |
| Sending API (Amazon SES at $0.10/1k) | $5 – $50 (50k – 500k sends) |
| Domain + DNS (Cloudflare free tier OK) | $0 – $5 |
| Backups (off-site, e.g. BorgBase or Restic to B2) | $2 – $10 |
| Monitoring (UptimeRobot free, Better Uptime $9, etc.) | $0 – $20 |
| **Subtotal** | **$12 – $117** |

Compare to Mailchimp's Standard tier at the same scale: $35-$185/month for 10k-100k *contacts* (not sends — Mailchimp's pricing is contact-tier, not send-volume). At 500k sends/month against a 50k contact list, AcelleMail's TCO is **~5× cheaper** — and the gap widens as your list grows, since AcelleMail's price is flat in contacts.

## Operations cost — the hidden expense

A self-hosted setup at 500k+ sends/month is **not zero-ops**. Plan for:

- **Backups.** Daily DB dump + weekly full filesystem snapshot. Test restore quarterly. See [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups).
- **OS patching.** `unattended-upgrades` for security; manual review of major upgrades. On Rocky, see the [dnf-automatic FAQ](https://acellesend.com/kb/articles/install-acellemail-on-rocky-linux-9#faq).
- **TLS renewal.** certbot auto-renew, but verify with a calendar reminder twice a year.
- **Bounce-handler maintenance.** Email log rotation, FBL handler health check.
- **Monitoring.** Uptime check on the public site, queue depth, disk space, MySQL slow-query log.
- **Incident response.** Plan for "site down" + "campaign stuck in sending" runbooks.

A reasonable estimate is **2–4 hours/month** of attention for a Small-tier site once steady state is reached. Compare to Mailchimp's "0 hours, but you pay $185" — the trade-off is real, not theoretical.

## When NOT to self-host

AcelleMail's value proposition assumes you have, or can build, basic Linux operations skill. If you don't and can't, choose a SaaS:

- You don't have anyone who can SSH into a Linux box.
- You can't write or read a `crontab` line.
- You have hard SOC 2 / HIPAA compliance requirements and no time to assemble the controls (AcelleMail can be made compliant, but the work is yours).
- Your sending pattern is peaky (zero for 25 days, blast on day 30) — SaaS handles bursts better than a $5 droplet.

For everyone else — agencies, SaaS founders, e-commerce stores, newsletter publishers, regulated-industry teams who want their data on-prem — AcelleMail's TCO and control wins.

## Sizing rules of thumb

If your workload doesn't fit neatly into a tier:

- **RAM = 2 × (peak concurrent queue workers) + 1 GB for MySQL + 256 MB for Redis + 512 MB OS overhead.** Each worker peaks ~256-512 MB on heavy campaigns.
- **vCPU = 1 + (queue workers / 2).** Workers are mostly I/O-bound (sending API calls), so they share well.
- **Disk = 20 GB OS + (subscriber count / 1k) MB for DB + 1 GB/month log retention + 5 GB image headroom.** A 100k-subscriber list with 12 months of logs needs ~50 GB.
- **Egress = (avg email size in KB) × (sends/month) / 1024 / 1024 GB.** A 50 KB email × 500k sends = 25 GB/month. Free on Hetzner + DO; charged on AWS unless you use SES in-region.

If RAM is the bottleneck, **memory-limit per FPM child** in `/etc/php/8.3/fpm/pool.d/www.conf` is what to tune — `pm.max_children` × `memory_limit` is your worst-case FPM RAM.

## Choosing between Ubuntu / Debian / Rocky

Per the [install guides](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404):

- **Ubuntu 24.04 LTS** — the canonical recommendation. Best documentation, most copy-paste-friendly. Pick this unless you have a reason to pick something else.
- **Debian 12 (Bookworm)** — slightly lighter weight, longer security cycle (Freexian ELTS through 2033). Use if you're already a Debian shop or want minimal pre-installed surprises. See the [Debian install guide](https://acellesend.com/kb/articles/install-acellemail-on-debian-12).
- **Rocky Linux 9** — required if your operations policy is RHEL-family. Same setup applies to AlmaLinux 9 and CentOS Stream 9. Has SELinux configuration overhead — see the [Rocky install guide](https://acellesend.com/kb/articles/install-acellemail-on-rocky-linux-9).

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Wizard System Check red on `IMAP` or `SQLite3` | Forgot `php8.3-imap` / `php8.3-sqlite3` | `sudo apt install -y php8.3-imap php8.3-sqlite3 && sudo systemctl restart php8.3-fpm` |
| MySQL choked at 100k subscribers | Hobby tier (2 GB RAM) too small | Bump to Small tier (4 GB) and tune `innodb_buffer_pool_size = 1G` |
| Queue worker OOM-killed during big campaign | `memory_limit` too low for `send-campaign` job | `memory_limit = 512M` in `/etc/php/8.3/fpm/php.ini` AND in `/etc/php/8.3/cli/php.ini` (workers use CLI) |
| Disk full after 6 months | Logs + `failed_jobs` table runaway | Add `find /var/www/acellemail/storage/logs -name "*.log" -mtime +14 -delete` to cron; `php artisan queue:flush` failed jobs |
| Sends slow on a 1 vCPU droplet | CPU starvation under burst | Move to 2 vCPU; worker count = floor(vCPU × 1.5) is a good starting point |
| AWS bill spike after first big campaign | Egress to a non-AWS sending API | Use SES in-region (free egress) — see the [AWS install Step 7](https://acellesend.com/kb/articles/install-acellemail-on-aws-ec2#step-7--ses-integration-highly-recommended) |

## FAQ

**Can I run AcelleMail on shared hosting?** Technically yes (cPanel + PHP 8.3+ + MySQL is enough), realistically no — the queue worker needs a long-running process and the cron needs reliable execution, both of which most shared hosts kill. Use a $5 VPS instead.

**ARM vs x86?** AWS Graviton (t4g/m6g), Hetzner ARM (CAX), and Ampere-based offerings all work — they're typically ~20% cheaper. PHP 8.3 + MySQL + Redis all run fine on ARM. Caveat: a small fraction of third-party PHP extensions (especially PECL ones) may need building from source. For a stock AcelleMail install with only the wizard-required extensions, ARM is fine and saves money.

**Why MariaDB and not MySQL?** Both work; both pass AcelleMail's tests. MariaDB has the cleaner install path on Debian and Rocky (no Oracle repo needed); MySQL is the slightly safer choice if you want maximum compatibility with MySQL-specific tooling. Pick what your team already runs.

**Do I need IPv6?** Not for AcelleMail itself. Modern receivers (Gmail, Outlook) accept connections from IPv4-only senders without penalty. Adding IPv6 doesn't hurt either, but it's not a deliverability requirement.

**Can I share a server with other apps?** Technically yes (separate PHP-FPM pools, separate MySQL databases, separate nginx server blocks). Operationally messy — campaign blasts will starve the other apps of CPU, and the other apps' MySQL queries will degrade campaign performance. Better to dedicate the box.

**What about Hetzner Cloud vs Hetzner Dedicated?** Hetzner Cloud (CX/CPX SKUs) is what's tabulated above — virtual servers, billed monthly, scale up/down freely. Hetzner Dedicated (AX/EX SKUs) is bare-metal — same datacenter, single tenant, much more compute per dollar at scale, but month-on/month-off bursting isn't possible. Use Cloud for everything up to Large tier; consider Dedicated at XL tier if you can commit.

**Should I co-locate AcelleMail with my main app?** No — sending workloads have unpredictable spikes that can starve your main app's resources. Dedicate a small droplet for AcelleMail and a separate larger one for your main app. The $5–$30/month for the AcelleMail droplet is much cheaper than the consequences of a campaign blast taking your main app offline.

## Related articles

- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — the canonical install
- [Install AcelleMail on Debian 12](https://acellesend.com/kb/articles/install-acellemail-on-debian-12) — Debian variant
- [Install AcelleMail on Rocky Linux 9](https://acellesend.com/kb/articles/install-acellemail-on-rocky-linux-9) — RHEL-family variant
- [Install AcelleMail on a DigitalOcean Droplet](https://acellesend.com/kb/articles/install-acellemail-on-digitalocean) — provider-specific guide
- [Install AcelleMail on AWS EC2](https://acellesend.com/kb/articles/install-acellemail-on-aws-ec2) — AWS-specific guide with TCO
- [Docker Deployment Guide for AcelleMail](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) — for container-first teams
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — what to run after the install
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — production-readiness for the queue side
- [Redis for Queue Processing](https://acellesend.com/kb/articles/redis-for-queue-processing) — Redis tuning
- [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) — when you outgrow Small tier
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — backup strategy

---

# Dark Mode Email Design

> When a recipient reads your email in dark mode, mail clients silently invert your colors — sometimes correctly, sometimes destructively. White logos disappear; black logos become white-on-light-grey; carefully-chosen brand colors get auto-shifted. This guide walks the 4 patterns that survive dark mode (color-scheme meta, prefers-color-scheme media query, transparent PNGs with dark-friendly fills, branded dark fallback) plus the testing protocol.

- Canonical URL: https://acellesend.com/kb/articles/dark-mode-email-design
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Design & Content — HTML email, responsive design, dark mode, accessibility, content strategy
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-24 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

When a recipient reads your email in **dark mode** (their OS or their mail client set to dark theme), mail clients silently invert your colors — sometimes correctly, sometimes destructively:

- **White logos disappear** on dark backgrounds
- **Black text in colored cards** becomes white-on-similar-color = unreadable
- **Brand colors get auto-shifted** by Gmail Android's "color extraction" algorithm
- **Transparent-background PNGs** show black halos against light fill

This guide walks the 4 patterns that survive dark mode + the per-client behavior + the testing protocol.

## Per-client dark mode behavior (2026)

| Client | Dark mode behavior |
|---|---|
| **Apple Mail (iOS, macOS)** | Respects `color-scheme` meta; doesn't aggressively re-color; honors `@media (prefers-color-scheme: dark)`. Best behavior. |
| **Gmail (iOS app)** | Respects `color-scheme`; doesn't aggressively re-color. Good behavior. |
| **Gmail (Android app)** | **AGGRESSIVE color inversion** — re-colors backgrounds; doesn't respect `color-scheme` consistently. Worst behavior. |
| **Gmail (web)** | Doesn't aggressively re-color; honors light/dark via the user's Gmail theme setting. |
| **Outlook (Windows desktop)** | Partial dark mode; behavior varies by version. Some Microsoft Word renderer quirks. |
| **Outlook.com (web)** | Honors `prefers-color-scheme` reasonably. |
| **Yahoo Mail** | Aggressive — similar to Gmail Android. |
| **Newer apps (Hey, Spark, Newton)** | Mostly Apple-Mail-like behavior. |

The takeaway: design for Apple Mail's behavior; test against Gmail Android.

## The 4 patterns that work

### Pattern 1 — Declare your color-scheme support

Add to the email's `<head>`:

```html
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
```

This tells the client: "I have explicit dark-mode styles — don't auto-invert." Most modern clients respect it; Gmail Android often ignores it (but no harm).

### Pattern 2 — `prefers-color-scheme` media query

In your `<style>` block (which most clients honor for `@media` even if they strip individual style rules):

```html
<style>
  @media (prefers-color-scheme: dark) {
    .dark-bg { background-color: #1a1a1a !important; }
    .dark-text { color: #f4f4f4 !important; }
    .dark-border { border-color: #444 !important; }

    /* Swap logo */
    .logo-light { display: none !important; }
    .logo-dark { display: inline-block !important; }
  }
</style>
```

Apply via class names on your HTML elements:

```html
<td class="dark-bg" style="background-color: #ffffff;">
  <h1 class="dark-text" style="color: #333333;">Hi {FIRST_NAME}</h1>
  ...
</td>
```

`!important` is essential — without it, the inline `style="..."` wins over the `@media` rule.

### Pattern 3 — Dual-logo swap

Ship both a light-mode and a dark-mode logo. Show one or the other based on the active mode:

```html
<!-- Light-mode logo: visible by default; hidden in dark mode -->
<img src="https://your-cdn.com/logo-light.png"
     alt="Acme Logo"
     width="180" height="40"
     class="logo-light"
     style="display: inline-block;" />

<!-- Dark-mode logo: hidden by default; visible in dark mode -->
<img src="https://your-cdn.com/logo-dark.png"
     alt="Acme Logo"
     width="180" height="40"
     class="logo-dark"
     style="display: none;" />
```

Combined with the `@media (prefers-color-scheme: dark)` in Pattern 2:

```css
@media (prefers-color-scheme: dark) {
  .logo-light { display: none !important; }
  .logo-dark { display: inline-block !important; }
}
```

In clients that respect the media query: only one logo renders. In clients that ignore it: both render (uncommon, looks slightly awkward but not broken).

### Pattern 4 — Avoid pure-white logos with transparency

Why: a white-fill PNG with transparent background renders fine on white. On dark mode, it becomes "white shape on dark background" — readable. But if the client auto-inverts (Gmail Android), the white fill becomes black-on-dark = invisible.

**Three safer alternatives:**

1. **Dual-logo swap** (Pattern 3) — explicit + reliable
2. **Solid-color background behind the logo** — wrap the IMG in a `<td bgcolor="#ffffff">` so the logo always sits on white regardless of email mode
3. **Two-tone logo design** — a logo that reads correctly on either light OR dark background (most modern brand logos meet this bar)

For body imagery (product shots, illustrations), prefer photos / colorful illustrations over single-color silhouettes — they look fine in either mode.

## Worked example — dark-mode-compatible template fragment

```html
<head>
  <meta name="color-scheme" content="light dark">
  <meta name="supported-color-schemes" content="light dark">
  <style>
    @media (prefers-color-scheme: dark) {
      .card-bg     { background-color: #1f2937 !important; }
      .card-text   { color: #f3f4f6 !important; }
      .card-muted  { color: #9ca3af !important; }
      .card-border { border-color: #374151 !important; }

      .logo-light  { display: none !important; }
      .logo-dark   { display: inline-block !important; }

      .cta-bg      { background-color: #3b82f6 !important; }
    }
  </style>
</head>
<body style="background-color: #f4f4f4; margin: 0;">
  <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
    <tr>
      <td align="center" class="card-bg" style="background-color: #f4f4f4; padding: 20px;">
        <table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="card-bg" style="background-color: #ffffff;">
          <tr>
            <td style="padding: 40px;">
              <!-- Logo (light + dark variants) -->
              <img class="logo-light" src="https://cdn.example.com/logo-light.png" width="180" height="40" alt="Acme" style="display: inline-block;">
              <img class="logo-dark" src="https://cdn.example.com/logo-dark.png" width="180" height="40" alt="Acme" style="display: none;">

              <h1 class="card-text" style="color: #111827; font-family: Arial, sans-serif;">
                Hi {FIRST_NAME}
              </h1>
              <p class="card-text" style="color: #374151; font-family: Arial, sans-serif; line-height: 1.5;">
                Body content here.
              </p>
              <p class="card-muted" style="color: #6b7280; font-size: 14px;">
                Smaller secondary text.
              </p>
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
```

## Testing protocol

1. **Send a test to yourself** in each of: Gmail (Android app), Gmail (iOS app), Apple Mail (iOS), Apple Mail (macOS), Outlook (Windows), Outlook (web). Set each client to dark mode before sending. Compare rendering.
2. **Use Litmus or Email on Acid** — both have dark-mode preview rendering across clients
3. **Re-test after every template change** — small CSS adjustments can break dark-mode behavior in non-obvious ways

The combination of "Gmail Android (dark mode)" + "Apple Mail (light mode)" is the 90% test — if both look right, you're probably fine elsewhere.

## Common failures

| What you see | Likely cause | Fix |
|---|---|---|
| Logo disappears in dark mode | White-fill logo on now-dark background | Switch to dual-logo swap (Pattern 3) or solid-color background wrapper |
| Background card stays light, text becomes hard to read | `prefers-color-scheme` styles not applied | Add `!important` on every dark-mode rule |
| Gmail Android colors look weird (different from desktop) | Gmail Android's auto-inversion algorithm | Try declaring `color-scheme: light dark` — sometimes Gmail Android backs off; otherwise accept the variance |
| Two logos both visible | Client ignored `@media` query | Acceptable degradation in dark mode-unsupporting clients; doesn't break readability |
| Outlook desktop ignores dark-mode CSS entirely | Outlook desktop has weak `prefers-color-scheme` support | Design "good enough in light mode + acceptable in dark mode" — don't try to force a specific Outlook dark experience |
| Tested in Apple Mail looks great; user reports terrible dark in Gmail Android | Forgot Gmail Android's color inversion | Test in Gmail Android explicitly during design phase, not after launch |

## FAQ

**Should I just send everything as plain text and skip dark-mode design?** Plain-text emails inherit the system theme automatically and never break. For transactional + newsletter emails, plain text can be the right answer. For marketing campaigns with rich content + CTAs, dark-mode-friendly HTML is worth the investment.

**Do I need dark mode if my audience is mostly desktop Outlook users?** Less urgent. Outlook desktop's dark mode support is partial; many users disable it. Audit your open data (most email platforms can break out "open in dark mode") to prioritize.

**What about hi-contrast / forced-colors mode?** Even further niche. `forced-colors: active` media query lets you target Windows High Contrast mode. Not worth the effort for most senders.

**Does dark mode affect deliverability?** No — receivers don't penalize dark-mode-friendly emails. The rendering happens client-side.

**Can AcelleMail's drag-and-drop editor generate dark-mode-friendly HTML?** Partially in recent versions. For full control, edit the HTML directly after building the structure.

## Related articles

- [Understanding Email Client Rendering Differences](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences) — full per-client rendering guide
- [Email Template Design Best Practices](https://acellesend.com/kb/articles/email-template-design-best-practices) — umbrella design rules
- [Mobile-First Email Principles](https://acellesend.com/kb/articles/mobile-first-email-principles) — most dark-mode reading happens on mobile
- [Click Map Analysis](https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout) — measure if dark-mode users click less (signal of broken layout)
- [Email Preheader Text](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster) — preheader rendering varies in dark mode too

---

# Redis for Queue Processing

> AcelleMail's default queue driver is the database — fine for hobby installs, terrible for production. Switching to Redis cuts queue-dispatch latency by 10×, eliminates MySQL's `jobs`-table contention under load, and unlocks better worker auto-scaling. This guide walks the install, the Acelle config, the tuning knobs that matter, and the monitoring hooks.

- Canonical URL: https://acellesend.com/kb/articles/redis-for-queue-processing
- Author: Daniel Reyes, Technical Editor
- Category: Server Management — Cron jobs, queues, Supervisor, Redis, Nginx, performance tuning
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-24 · Last updated: 2026-08-15
- Reading time: 10 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail uses Laravel queues to dispatch campaign sends, automation triggers, list imports, and bounce processing asynchronously. The default queue driver is the database (`jobs` table in MySQL) — fine for the smallest hobby installs, but it bottlenecks fast:

- **Polling cost:** each worker polls the `jobs` table every `--sleep=3` seconds with a SELECT FOR UPDATE that briefly locks the table
- **Contention under load:** 15 workers all polling + 1 web request inserting jobs into the same table is a hot lock
- **No native priority:** the database driver iterates jobs in `id` order; switching queues = a separate scan

Redis as the queue driver fixes all three: O(1) push/pop via Redis LIST primitives, no MySQL contention, native per-queue ordering. **At any volume above ~5k sends/day, Redis is the right choice.**

This guide walks the install, the Acelle config, the tuning knobs that matter, and the monitoring hooks.

**Prerequisite:** working AcelleMail install per the [Ubuntu 24.04 canonical guide](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — Redis is Step 4 of that guide.

## Step 1 — Install Redis 7

```bash
# Ubuntu / Debian
sudo apt update && sudo apt install -y redis-server
sudo systemctl enable --now redis-server

# Rocky / RHEL
sudo dnf module reset redis
sudo dnf module install -y redis:remi-7.2
sudo systemctl enable --now redis
```

Verify the version + that it responds:

```bash
redis-server --version | head -1     # Expect 7.x
redis-cli ping                       # Expect PONG
```

Don't use anything older than Redis 7 in 2026. Redis 6 is end-of-life; Redis 5 has long been EOL.

## Step 2 — Tell systemd to manage Redis properly (Ubuntu/Debian)

```bash
sudo sed -i 's/^supervised .*/supervised systemd/' /etc/redis/redis.conf
sudo systemctl restart redis-server
```

This lets systemd correctly track Redis health for restart-on-failure semantics. Skip on Rocky — Remi's package is already systemd-supervised.

## Step 3 — Configure AcelleMail to use Redis

Edit `/var/www/acellemail/.env`:

```env
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null
REDIS_DB=0
REDIS_CACHE_DB=1
CACHE_DRIVER=redis
SESSION_DRIVER=redis
```

> **Use `127.0.0.1`, not `localhost`.** PHP's Redis client treats `localhost` as a Unix-socket hint on some systems; using the IP forces TCP, which is what Redis is configured to accept.

Clear the cached config and restart:

```bash
cd /var/www/acellemail
sudo -u www-data php artisan config:clear
sudo supervisorctl restart acellemail-master:* acellemail-worker:*
```

After the workers restart, queue operations route through Redis. **The web UI should show no difference** — Redis is a drop-in replacement.

## Step 4 — Understand AcelleMail's queue layout

AcelleMail's `routes/console.php` + the shipped supervisor templates show the actual queue names in use:

| Queue | Used for | Throughput |
|---|---|---|
| `default` | Misc Laravel jobs + low-priority Acelle jobs | Low (~1-10/s) |
| `import` | List imports — long-running, single-job-per-import | Very low (1-2 concurrent) |
| `high` | Campaign send jobs from "Priority" campaigns | High (10-100/s) |
| `batch` | Bulk send jobs from normal campaigns | Very high (50-500/s) |
| `single` | Transactional + single-recipient sends | Medium (5-50/s) |
| `automation` | Automation-flow steps for individual subscribers | Medium |
| `automation-dispatch` | Bulk automation trigger evaluation | Medium |

The [supervisor setup](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs#step-2--supervisor-the-two-tier-split) splits these into two pools: master handles `import,default`; worker handles `high,batch,single,automation,automation-dispatch`. Don't change the split unless you understand the underlying contention model.

## Step 5 — Redis tuning for the AcelleMail workload

Edit `/etc/redis/redis.conf` (Ubuntu) or `/etc/redis/redis.conf` + `/etc/redis.conf` on Rocky:

### Memory cap

```conf
# Set to ~25% of system RAM as a hard ceiling
maxmemory 2gb

# CRITICAL for queues: never evict queue keys
maxmemory-policy noeviction
```

> **`noeviction` is non-negotiable for queues.** Any other policy (`allkeys-lru`, `volatile-ttl`, etc.) means Redis can silently delete pending jobs when memory pressure rises — campaigns will appear sent but recipients receive nothing. With `noeviction`, Redis returns an error to the producer (the web request) instead, which Acelle handles correctly.
>
> If you also use Redis for cache, **use a separate logical DB** (`REDIS_CACHE_DB=1`) and configure per-DB eviction via `SELECT 1; CONFIG SET maxmemory-policy allkeys-lru` — but the global default must stay `noeviction`.

### Persistence

Redis has two persistence modes — pick based on your tolerance for data loss:

```conf
# RDB snapshots (default) — saves a binary dump every N seconds with M+ changes
save 900 1
save 300 10
save 60 10000

# AOF (Append-Only File) — writes every change to disk
appendonly no
```

For an AcelleMail queue:

- **RDB only** (default) is acceptable. Worst case: lose up to 60 seconds of queue data on power loss = at most a minute of un-sent emails for affected jobs. Acelle's retry logic handles this gracefully (jobs that were enqueued but lost just don't run; the campaign-level state ensures they get re-enqueued on `campaign:rerun`).
- **AOF on** halves throughput but loses at most 1 second on power loss. Only worth it if you cannot tolerate any job loss (rare for email sending).

Don't enable AOF "just in case" — it doubles disk I/O and is rarely needed for queues.

### TCP + I/O

```conf
tcp-keepalive 300
io-threads 4         # If you have ≥ 4 cores
```

`io-threads 4` parallelizes socket reads (Redis 6+ feature). On a 4-vCPU server it gives ~30% throughput gain at high concurrency. Don't set above your core count.

Reload Redis after each change:

```bash
sudo systemctl restart redis-server
```

## Step 6 — Monitoring

The five Redis metrics worth watching:

```bash
# 1. Queue depths — should be 0-100 steady-state
for q in default import high batch single automation automation-dispatch; do
  printf "%-22s %s\n" "$q" "$(redis-cli llen queues:$q)"
done

# 2. Memory usage vs maxmemory
redis-cli info memory | grep -E "used_memory_human|maxmemory_human"

# 3. Commands per second (throughput)
redis-cli info stats | grep total_commands_processed
# Run twice with a 5-second gap, divide the difference by 5

# 4. Slow log — any command > 10ms is suspect
redis-cli slowlog get 10

# 5. Persistence health
redis-cli info persistence | grep -E "rdb_changes_since|aof_enabled"
```

For production, push these to your monitoring stack:

- `redis_exporter` (Prometheus) — runs as a separate process, scrapes Redis INFO every 15s
- Grafana dashboard `redis-exporter quickstart` — drop-in
- DataDog Redis integration — same metrics, fewer moving parts if you're already on DataDog

**Alert thresholds:**

| Metric | Warn | Critical |
|---|---|---|
| Memory usage | > 70% of maxmemory | > 90% |
| Queue depth (any single queue) | > 5,000 sustained 5 min | > 50,000 |
| Slow commands per minute | > 5 | > 50 |
| Connected clients | > 50 (usually ~20 on AcelleMail) | > 200 (indicates a leak) |

## Step 7 — When to add Laravel Horizon (optional)

[Laravel Horizon](https://laravel.com/docs/horizon) is a web dashboard for Redis-backed queues. It's not bundled with AcelleMail, but installs cleanly as a Composer dependency:

```bash
cd /var/www/acellemail
sudo -u www-data composer require laravel/horizon
sudo -u www-data php artisan horizon:install
```

What you get:

- Real-time dashboard at `/horizon` showing per-queue throughput, failed jobs, recent jobs
- Auto-balancing (`balance: auto` in `config/horizon.php`) — dynamically reallocates workers across queues based on load
- Tags + filters — search "all failed jobs for customer X in the last hour"

Trade-off: Horizon's `horizon` daemon **replaces** your supervisor `queue:work` lines. You run a single supervisor entry for `php artisan horizon` and Horizon manages the worker pool internally. If you go this route, delete the Acelle two-tier supervisor configs and put a single Horizon supervisor config in their place. **Don't run both** — they'll fight over jobs.

Recommended if you operate 3+ AcelleMail installs or you need finer per-queue observability. For a single install, the bare supervisor setup is simpler and works.

## Step 8 — When to add a separate Redis box

At Large tier (5M+ sends/month) you start to want Redis on its own host:

- Application server and Redis on separate boxes → web requests don't compete with workers for the Redis socket
- A dedicated Redis box can use 100% of its RAM for cache+queue (vs sharing with PHP-FPM + MySQL)
- Easier to add HA via Redis Sentinel (1 primary + 2 replicas + Sentinel for automatic failover)
- Hosting providers' managed Redis (DigitalOcean Managed Redis, AWS ElastiCache, Hetzner Cloud Redis) is the lowest-ops route

Update the AcelleMail `.env`:

```env
REDIS_HOST=10.0.0.5      # private network IP of your Redis box
REDIS_PORT=6379
REDIS_PASSWORD=...       # set this for any non-loopback Redis!
```

> **If you expose Redis on a public network or even a shared private network, set `requirepass` in `redis.conf` and use `REDIS_PASSWORD` in `.env`.** A passwordless Redis on a public IP is one of the most-scanned exploits on the internet.

See [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) for the full multi-box architecture.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Workers report "Connection refused" to Redis | Redis not running or wrong host/port in `.env` | `sudo systemctl status redis-server`; verify `.env` values; `php artisan config:clear` |
| Queue depth keeps growing despite running workers | Worker pool too small for load | Increase supervisor `numprocs`; consider Horizon `balance: auto` |
| "Jobs being run twice" (recipients get duplicate emails) | Worker `--tries` was changed from 1 to ≥ 2 | Restore `--tries=1` in supervisor config; jobs that fail under tries=2 risk duplicate sends on retry |
| Out of memory error from Redis | `maxmemory` too low, or queue backlog grew | Increase `maxmemory`; verify `maxmemory-policy=noeviction` (so jobs aren't silently lost); diagnose backlog source |
| `redis-cli` works but app can't connect | Redis bound to `127.0.0.1` but app uses different network namespace | Verify `bind 127.0.0.1` matches what app uses; for Docker, change to `bind 0.0.0.0` + use internal network |
| AOF growing huge | AOF rewrite cron not running | `redis-cli BGREWRITEAOF`; or disable AOF if not needed |
| All queues empty but campaigns stuck | Cron stopped firing (campaign:schedule isn't dispatching) | Check cron is running per [supervisor + cron guide](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs#step-1--system-cron) |

## FAQ

**Can I use Memcached instead of Redis?** Technically — for cache yes; for queues no. Laravel's queue system supports Redis + database + SQS + Beanstalkd, not Memcached. Stick with Redis.

**What about KeyDB / Dragonfly / Garnet?** All three are Redis-API-compatible alternatives that claim higher throughput. They work with AcelleMail (since Laravel just speaks the Redis protocol), but the community is smaller and edge-case compatibility isn't perfect. For a small-to-medium AcelleMail install, vanilla Redis is the safer choice; consider alternatives only if you hit a real throughput wall.

**Redis on the same host as MySQL — is that bad?** Fine at Small + Medium tier. At Large tier, separate them — Redis's tcp keepalives + MySQL's connection pool can compete for file descriptors at high concurrency.

**How do I clear all queues without restarting?** `redis-cli FLUSHDB` clears the current DB. Be very careful — this deletes all pending jobs (you'll lose any unsent emails currently queued). Better: use `php artisan queue:flush` which only clears failed jobs.

**Redis 6 vs 7 vs 8?** Use 7.x. Redis 8 was released June 2024 with significant changes (AGPL licence, JSON/Search/TimeSeries built in); it works but the ecosystem hasn't caught up. Most Redis-as-a-service offerings as of 2026 are still on 7.

**Why does AcelleMail use `--sleep=3`?** Without `--sleep`, an empty-queue worker spins at 100% CPU polling for jobs. `--sleep=3` makes it wait 3 seconds between polls when the queue is empty, dropping CPU to ~0% during idle periods. With Redis + Lua-based BLPOP this is less critical than with database polling, but the 3-second sleep is harmless and consistent with Acelle's templates.

## Related articles

- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — the supervisor setup that uses Redis (this article's main consumer)
- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — Step 4 of the install
- [Scaling for 100K+ Emails Per Day](https://acellesend.com/kb/articles/scaling-for-100k-emails-per-day) — when to split Redis to its own box
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — separate concern but same ops cadence
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — securing Redis (firewall + auth)
- [Docker Deployment Guide for AcelleMail](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) — Redis as a compose service
- [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) — when "Redis on the same box" stops scaling

---

# Email Client Rendering — What Breaks and How AcelleMail Helps You Catch It

> Gmail strips your CSS, Outlook ignores rounded corners, Apple Mail dark-mode inverts your logo. This guide is the practical "what to check in AcelleMail before launching" — Preview tool, plain-text fallback, A/B testing — and the technical why-it-happens reference in the collapsible section.

- Canonical URL: https://acellesend.com/kb/articles/understanding-email-client-rendering-differences
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-16 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why this matters before you Send

Every email client renders HTML with its own engine, with its own quirks. The same campaign that looks pristine in Gmail web can have collapsed columns in Outlook 2016, an inverted logo in Apple Mail dark mode, and stripped link styling in Yahoo. You don't need to memorize every quirk — you need a verification routine that catches breakage *before* the campaign launches.

This article walks the three checks AcelleMail's UI surfaces for you.

## Check 1: AcelleMail's built-in Preview

Open any campaign → **Preview** button. The Preview pane renders the email with AcelleMail's own engine and your merge-tag substitutions:

![Preview — desktop view, computed merge tags](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

Toggle to mobile:

![Mobile preview — 320px viewport](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

What Preview tells you:

- **Layout integrity** at 600px desktop and 320px mobile
- **Merge-tag computed values** using a sample subscriber from the list
- **Image loading** at the URLs you've used (won't catch hotlink-block at the *recipient*'s firewall, but catches dead public URLs)

What Preview can NOT tell you:

- How Outlook 2016 desktop renders this specific HTML (different engine entirely — Word's rendering engine)
- How Apple Mail's dark mode inverts your colors
- How Gmail's clipping behaviour ("[Message clipped]") behaves at 102KB

For those, use Check 2 + 3.

## Check 2: Send-test to real client accounts

Every campaign settings panel has a **Send test** button. Send to addresses you control across multiple clients:

- One Gmail address (open in browser AND in the Gmail mobile app)
- One Outlook.com address (open in browser AND in the Windows Outlook app if available)
- One iCloud/Apple Mail address (open on iPhone or in macOS Mail with dark mode toggled)
- One Yahoo address (browser)

Take ~5 minutes to open each one. Note what's different from what Preview showed. The most common surprises:

| Client | Common surprise | Fix |
|---|---|---|
| Outlook desktop (2007-2019) | Multi-column blocks collapse to single column; rounded corners drop; some shadows missing | Use tables for layout (AcelleMail builder does this); accept that some visual polish drops in Outlook |
| Gmail mobile app | `<style>` blocks in `<head>` removed | AcelleMail inlines critical styles automatically — for hand-coded HTML, run an inliner |
| Apple Mail iOS dark mode | Logo on transparent background inverts to white-on-dark, looks broken | Use solid-background PNG, or version-specific `<picture>` with dark-mode variant |
| Gmail web (long emails) | "Message clipped" cut-off above ~102KB | Keep total HTML under 100KB; reduce inline image data-URIs |
| Yahoo Mail | Some `<style>` rules silently stripped | Inline styles on every block (AcelleMail builder defaults to this) |

## Check 3: A/B test the riskiest variant

If you're launching a campaign with a layout you're unsure about (new template, heavy redesign), use AcelleMail's A/B testing to ship the conservative version to 80% and the experimental version to 20% — verify engagement metrics across both before committing:

![A/B variants tab](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/03-variants-tab.png)

For purely-visual A/B (vs. subject-line A/B), the open rate won't differ — but the **click-through rate** will, and that's the signal you want. If the experimental variant has materially lower CTR, the rendering broke in some client and recipients couldn't or didn't engage.

The post-send A/B report shows the delta:

![A/B test results report](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/05-ab-report.png)

## Common UI patterns that survive everywhere

| Goal | Survives-everywhere pattern |
|---|---|
| Two columns of content | Use AcelleMail's 2-col block — auto-stacks on mobile |
| Rounded buttons | Solid-color rectangular buttons; rounded shows in most, not Outlook desktop — acceptable degrade |
| Custom font | System font stack fallback first; web font second — `font-family: 'Inter', -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif` |
| Dark-mode safe logo | Solid-background PNG, OR `<picture>` with `prefers-color-scheme` source |
| Bold callout box | Background-color on a table cell with padding; works everywhere |
| Embedded video | Use a video poster image + linked play button → goes to a landing page (NEVER embed HTML5 `<video>` in email — only ~30% of clients render it) |

## Common UI signals + fixes

| Symptom from test send | Likely cause | UI fix in AcelleMail |
|---|---|---|
| Columns side-by-side on mobile (Outlook iOS) | Hard-coded fixed widths in HTML | Use the builder's 2-col block (responsive by default) — don't paste raw HTML with `width="300"` columns |
| Plain text version reads "&nbsp;&nbsp;Click here" garbage | Auto-extracted from HTML, no manual plain-text | Builder → toggle **Plain text version** → write a real plain version |
| Email is clipped in Gmail | Total HTML > 102KB | Builder → check size in *Code view* footer; reduce inline images (host externally) |
| Logo background turns navy in dark mode | Transparent PNG against light bg | Re-export PNG with solid white background |
| Some recipients see broken layout, others fine | Multiple Outlook versions render differently | Test send to an Outlook 2016 + Outlook 365 web — confirm both; if 2016 breaks but 365 is fine, accept the degrade (declining install base) |
| Tracking pixel doesn't fire | Recipient client blocks images (common in corporate) | Open rate underreports; focus on click-rate as the real engagement signal |

<details>
<summary><strong>Advanced: rendering engines, MSO conditional comments, CSS support matrix</strong></summary>

The reason email rendering varies so much is that every major client uses a different engine:

| Client | Rendering engine | Notable quirks |
|---|---|---|
| Apple Mail (macOS / iOS) | WebKit | Best CSS support, full dark mode inversion |
| Gmail web | WebKit-based Blink + Gmail rewriter | Strips `<style>` in `<head>` if size > limit; removes `position`; rewrites image URLs through googleusercontent CDN |
| Gmail mobile app | Same engine, stricter rewriting | Often inlines `<style>` differently than web |
| Outlook desktop 2007-2019 | **Microsoft Word's rendering engine** | No CSS3, no `border-radius`, no `background-image` on most elements |
| Outlook 365 web / desktop new | Edge / Chromium | Modern, similar to web rendering |
| Outlook.com web | Webkit + Microsoft rewriter | Modern, with quirks |
| Yahoo Mail | Custom Yahoo engine | Strips some `<style>` rules silently |

**MSO conditional comments** target Outlook desktop specifically:

```html
<!--[if mso]>
<table role="presentation" width="600" cellpadding="0" cellspacing="0">
  <tr><td>
<![endif]-->

  <!-- Modern clients render this; Outlook desktop renders the MSO block above + below -->
  <div style="max-width: 600px;">Your content</div>

<!--[if mso]>
  </td></tr>
</table>
<![endif]-->
```

This pattern wraps modern responsive markup with Outlook-only table fallback. The AcelleMail builder generates this pattern automatically for column blocks.

**CSS property support matrix** (from caniemail.com, summarized):

| Property | Apple Mail | Gmail web | Gmail iOS | Outlook desktop | Yahoo |
|---|---|---|---|---|---|
| `display: flex` | ✅ | ✅ | Partial | ❌ | Partial |
| `border-radius` | ✅ | ✅ | ✅ | ❌ | ✅ |
| `box-shadow` | ✅ | ✅ | ✅ | ❌ | ✅ |
| `background-image` (on body) | ✅ | ✅ | ✅ | Partial | Partial |
| `@media` queries | ✅ | ✅ | ✅ | ❌ (uses MSO) | ✅ |
| `prefers-color-scheme` (dark mode) | ✅ | Limited | ✅ | ❌ | ❌ |
| Web fonts | ✅ | ✅ | ✅ | ❌ | Limited |

**Litmus / Email on Acid** for paid client testing. Both offer:

- 90+ real client/device screenshots in 30-60 seconds
- Spam filter testing across Gmail/Outlook/Yahoo
- Code analyzer flagging known anti-patterns
- API + Slack integration for CI

For high-volume senders the cost (~$80-$200/month) typically pays back within one campaign — catching a layout break across 100k recipients before send saves more than the subscription.

**MJML compiler** outputs Outlook-safe HTML automatically. If you're maintaining a hand-coded email template library, switching to MJML eliminates ~80% of cross-client breakage:

```bash
mjml my-template.mjml -o compiled.html
# compiled.html is ready for AcelleMail Source mode
```

**ESP-side rendering** — AcelleMail's PHP send pipeline doesn't transform your HTML beyond merge-tag substitution + DKIM signing. The HTML that arrives at the receiver is the HTML you authored. Any rendering surprise is the receiving client's engine, not AcelleMail's transmission.

</details>

## Related articles

- [Email template design — best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)
- [Personalization beyond first name — advanced merge tags](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags)
- [A/B testing email campaigns — best practices](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Why are my emails going to spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder)

---

# How Email Delivery Actually Works (What Happens After You Click Send)

> When you press Send Campaign in AcelleMail, your message travels through five stages before it lands in someone's inbox. Knowing each stage tells you exactly where to look when a campaign goes sideways — and why some recipients see the email and others don't.

- Canonical URL: https://acellesend.com/kb/articles/how-email-delivery-actually-works
- Author: Lena Hart, Email Deliverability Editor
- Category: Email Infrastructure — Mail servers, MTA configuration, relay setup, IP management, monitoring
- Content type: guide · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-03-14 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

You press **Send Campaign** in AcelleMail. The status badge turns from *Draft* to *Queuing*, then *Queued*, then *Sending*, then *Done*. Between *Sending* and the recipient's inbox, your message travels through five distinct stages run by other people's servers. This guide walks each stage in plain English — so when something goes wrong, you know which stage to investigate instead of guessing.

You don't need to run anything. You just need to know the path.

## The 5-stage journey

### Stage 1 — AcelleMail queues every recipient

When you click **Send Campaign**, AcelleMail doesn't try to dispatch all 50,000 emails at once. It queues each recipient as a separate job. A pool of background workers (your sending workers) picks jobs off the queue one by one. This is why the campaign status moves through *Queuing* (building the list) → *Queued* (waiting) → *Sending* (workers are picking up jobs).

**Where you watch it:** the campaign **Overview** page. The progress badge ticks from *Queuing* to *Sending* to *Done* as workers drain the queue.

### Stage 2 — Your sending server looks up the recipient's mail server

For every email, your sending server has to find *where* to deliver it. It asks DNS: "What's the mail server for `gmail.com`?" DNS returns the recipient's **MX record** — a hostname like `gmail-smtp-in.l.google.com` for Gmail, or `mail-protection.outlook.com` for Outlook 365.

If the recipient's domain has no MX record, your server gets back a delivery failure here — that becomes a **hard bounce**, and AcelleMail flags the subscriber so future campaigns skip them.

### Stage 3 — The SMTP handshake

Your server opens a connection to the recipient's mail server and they talk over the SMTP protocol — a short, formalized exchange that goes: hello, who are you, who's the message for, here's the message, goodbye. It takes a few hundred milliseconds when both sides are healthy. It can take 30+ seconds if the recipient server is slow or rate-limiting you (see [Email Throttling and Rate Limits Explained](https://acellesend.com/kb/articles/email-throttling-and-rate-limits-explained)).

Failures at this stage usually look like a **soft bounce** — the recipient server said "not right now," and your server can try again. AcelleMail will retry soft bounces a configurable number of times before giving up.

### Stage 4 — Encryption (TLS)

Modern receiving servers expect the SMTP conversation to be encrypted with TLS. This stops anyone in the middle of the internet from reading your message in transit. Gmail, Outlook, Yahoo all require it. If your sending server doesn't support TLS, big providers either reject the message outright or quietly drop it into spam.

This is the rare stage you can't see from inside AcelleMail — it's a property of your SMTP setup. If you use AWS SES, SendGrid, or any modern provider, TLS is on automatically. If you use a homemade Postfix install, your operator needs to confirm STARTTLS is configured.

### Stage 5 — Authentication checks (SPF, DKIM, DMARC)

Even after the message is delivered to the recipient server, the recipient's spam filter runs three identity checks before placing it in the inbox:

| Check | What it asks |
|---|---|
| **SPF** | Is the IP that sent this message on the list of IPs the From-domain says are allowed to send for it? |
| **DKIM** | Did the From-domain cryptographically sign this message? Does the signature verify? |
| **DMARC** | Do the SPF or DKIM domain names *line up* with the visible From address? |

All three need to pass for best inbox placement. If they don't, the message often lands in spam — or, with strict DMARC enforcement, gets rejected outright. This is the #1 cause of "I sent the email but my recipient says they never got it." Set up all three on day one — see [Complete DNS Setup for Email Sending: SPF, DKIM, DMARC](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).

## Where each stage shows up in AcelleMail

| Stage | AcelleMail signal | What "failure" looks like |
|---|---|---|
| Queue | Status badge: *Queuing* → *Queued* → *Sending* → *Done* | Status stuck on *Queuing* for >5 min → your queue worker is not running |
| MX lookup | Recipient is marked **Hard bounced** | Subscriber moves to `bounced` status on the list |
| SMTP handshake | Recipient is marked **Soft bounced** | Subscriber stays subscribed but counts toward bounce rate |
| TLS | Bulk drop to spam from one provider | All Gmail recipients show 0% open suddenly — check your SMTP provider's TLS config |
| Authentication | Spam-folder placement | See your domain in Google Postmaster Tools / Microsoft SNDS |

## Common issues

| What you see | What to do |
|---|---|
| Status badge stuck on *Queuing* | Your queue worker process is not running. Have your operator restart it (see [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs)). |
| Lots of *hard bounces* on a freshly-imported list | The list is dirty — too many addresses with no valid mail server. Use a verification service before importing. See [Email List Hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability). |
| Lots of *soft bounces* concentrated at one ISP | You're being rate-limited by that ISP. Slow the send by configuring throttling on the sending server. |
| Recipient says "I never got it" but AcelleMail says delivered | It's in their spam folder. Almost always an SPF / DKIM / DMARC misconfiguration — verify your DNS setup. |

## What to do after

1. Set up SPF, DKIM, and DMARC if you haven't — this single step eliminates most "didn't arrive" mysteries.
2. Bookmark the campaign **Overview** page during your next send and watch the status badge tick through the stages.
3. If you operate your own server, read [Email Queue Architecture in AcelleMail](https://acellesend.com/kb/articles/email-queue-architecture-in-acellemail) for the queue side.

## Related articles

- [Email Queue Architecture in AcelleMail](https://acellesend.com/kb/articles/email-queue-architecture-in-acellemail)
- [Complete DNS Setup: SPF, DKIM, DMARC](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Email Throttling and Rate Limits Explained](https://acellesend.com/kb/articles/email-throttling-and-rate-limits-explained)
- [Bounce Types Deep Dive](https://acellesend.com/kb/articles/understanding-email-bounce-types)

---

# WordPress Subscriber Sync

> Wire up the WordPress user_register hook to push every new account into an AcelleMail list. Twenty lines of PHP, no plugin dependency, real-time. This guide covers the production-ready snippet (with timeout / async / error handling) and the four common gotchas — field-name case, secrets in version control, slow page loads, duplicate subscribers.

- Canonical URL: https://acellesend.com/kb/articles/wordpress-subscriber-sync
- Author: Tom Becker, Developer Editor
- Category: Integrations — Stripe, PayPal, WordPress, WooCommerce, Zapier, webhooks, API
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-12 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The simplest WordPress ↔ AcelleMail integration: **when someone registers on your WordPress site, add them to an AcelleMail list.** Twenty lines of PHP, no plugin dependency, real-time.

This article is Pattern 1 from the [WordPress + WooCommerce integration overview](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce). If you're connecting WooCommerce purchases (not just user registrations), see [WooCommerce Post-Purchase Emails](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails) instead.

## Prerequisites

- A working AcelleMail install
- An [AcelleMail API token](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api#step-1--get-your-api-token)
- The UID of the list you want to add subscribers to (in AcelleMail: **Lists → Overview → click the list → URL contains the UID**)
- WordPress 5.5+ (we'll use `wp_remote_post` + `wp_json_encode`)

## Step 1 — Store credentials in `wp-config.php`

Never put API tokens in your theme. Put them in `wp-config.php` so they're outside the version-controlled theme code:

```php
// /path/to/wp-config.php
define('ACELLEMAIL_BASE_URL',  'https://mail.example.com');
define('ACELLEMAIL_API_TOKEN', 'your-token-here');
define('ACELLEMAIL_LIST_UID',  'your-list-uid-here');
```

## Step 2 — Create a must-use plugin

Don't paste into `functions.php` — it'll vanish on theme switch. Create a must-use plugin instead (`wp-content/mu-plugins/` auto-loads every plugin in that directory regardless of theme):

```bash
# WordPress doesn't create mu-plugins/ by default; mkdir it if needed
sudo mkdir -p /path/to/wp-content/mu-plugins
sudo nano /path/to/wp-content/mu-plugins/acellemail-subscriber-sync.php
```

```php
<?php
/**
 * Plugin Name: AcelleMail Subscriber Sync
 * Description: Push new WP user_register events to AcelleMail
 * Version:     1.0.0
 */

if (!defined('ACELLEMAIL_API_TOKEN') ||
    !defined('ACELLEMAIL_LIST_UID') ||
    !defined('ACELLEMAIL_BASE_URL')) {
    return; // Constants not set in wp-config.php; skip silently
}

add_action('user_register', function ($user_id) {
    $user = get_user_by('id', $user_id);
    if (!$user || !is_email($user->user_email)) return;

    $response = wp_remote_post(ACELLEMAIL_BASE_URL . '/api/v1/subscribers', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode([
            'MAIL_LIST_UID' => ACELLEMAIL_LIST_UID,
            'EMAIL'         => $user->user_email,
            'FIRST_NAME'    => $user->first_name ?: '',
            'LAST_NAME'     => $user->last_name ?: '',
        ]),
        'timeout'  => 10,
        'blocking' => false,   // fire and forget — don't slow down user signup
    ]);

    // With blocking => false, $response is always WP_Error-ish + we never see status.
    // Use blocking => true + WP_DEBUG_LOG for first-time setup, then flip back.
});
```

## Step 3 — Test it

Register a test user via **WordPress Admin → Users → Add New**. Check AcelleMail:

- **Lists → your list → Subscribers** should show the test user within 5 seconds
- If WP_DEBUG_LOG is on, `wp-content/debug.log` should not show any PHP errors
- AcelleMail's `storage/logs/laravel.log` should not show any 422/500 errors

If the subscriber doesn't appear:

```php
// Temporarily flip 'blocking' => true and 'timeout' => 30, then log the response
$body = wp_remote_retrieve_body($response);
error_log("[AcelleMail sync] HTTP " . wp_remote_retrieve_response_code($response) . " — $body");
```

Common debug results:

- `401 Unauthenticated` → wrong API token in `wp-config.php`
- `422 The MAIL_LIST_UID is invalid` → wrong list UID
- `cURL error 60: SSL` → self-signed cert on AcelleMail; either fix the cert or temporarily `'sslverify' => false`

## Step 4 — Field mapping

Acelle subscriber fields are stored per-list. The default fields every list has:

| AcelleMail field | WordPress source |
|---|---|
| `EMAIL` | `$user->user_email` |
| `FIRST_NAME` | `$user->first_name` (from user_meta) |
| `LAST_NAME` | `$user->last_name` (from user_meta) |

If your AcelleMail list has additional custom fields, add them to the body:

```php
'COMPANY'        => get_user_meta($user_id, 'company_name', true),
'SIGNUP_SOURCE'  => 'wordpress',
'WP_ROLE'        => implode(',', $user->roles),
```

Custom field keys are **UPPERCASE** matching what you defined in AcelleMail (**Lists → your list → Fields**).

## Step 5 — Differentiate by role / signup source

A common refinement: subscribers from different sources go to different lists.

```php
add_action('user_register', function ($user_id) {
    $user = get_user_by('id', $user_id);
    if (!$user) return;

    // Customers go to "Newsletter — Customers"; everyone else to "Newsletter — General"
    $list_uid = in_array('customer', $user->roles, true)
        ? CUSTOMER_LIST_UID
        : GENERAL_LIST_UID;

    wp_remote_post(/* ... use $list_uid instead of constant ... */);
});
```

## Optional — opt-in checkbox at registration

Don't auto-subscribe everyone — many compliance regimes (GDPR, CASL, etc.) require explicit consent. Add a checkbox to the WP registration form:

```php
add_action('register_form', function () {
    ?>
    <p>
        <label>
            <input type="checkbox" name="newsletter_optin" value="1" />
            Subscribe to our newsletter
        </label>
    </p>
    <?php
});

add_action('user_register', function ($user_id) {
    if (empty($_POST['newsletter_optin'])) {
        // User didn't opt in — skip
        return;
    }
    // ... your wp_remote_post call ...
});
```

Combine with [double opt-in](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose) at the AcelleMail list level for maximum compliance — Acelle then sends its own confirmation email after the WP signup.

## Optional — handle subscription updates

When a user updates their email in WordPress, push the change to AcelleMail:

```php
add_action('profile_update', function ($user_id, $old_user_data) {
    $user = get_user_by('id', $user_id);
    if ($user->user_email === $old_user_data->user_email) return; // no email change

    // Update AcelleMail subscriber's email
    // First, find by old email:
    $find = wp_remote_get(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/email/' . urlencode($old_user_data->user_email),
        ['headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json']]
    );

    $sub = json_decode(wp_remote_retrieve_body($find), true);
    if (!$sub || empty($sub['id'])) return;

    wp_remote_request(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/' . $sub['id'],
        [
            'method' => 'PATCH',
            'headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json', 'Content-Type' => 'application/json'],
            'body' => wp_json_encode(['EMAIL' => $user->user_email]),
        ]
    );
}, 10, 2);
```

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Subscriber created but FIRST_NAME / LAST_NAME empty | WP doesn't auto-fill these on registration — they're empty meta | Either skip the fields, or fetch from `$_POST['first_name']` if your registration form collects them |
| Page load slow after adding the hook | `'blocking' => true` (default) waits for the API response | Add `'blocking' => false` to make it fire-and-forget |
| Subscriber missing after deploying | Theme switch removed `functions.php` snippet | Use mu-plugin (Step 2) instead of functions.php |
| API token visible in error log | `error_log()` dumped the full request headers | Sanitize before logging; never log the full Authorization header |
| Double subscriptions during retry | Plugin runs again after a transient failure | API is idempotent per email — duplicates are de-duped automatically |
| User confirmed email but AcelleMail shows "pending" | List has double opt-in enabled | Either disable double opt-in on the list, or accept the confirmation email flow |
| `wp_remote_post` returns "blocked by firewall" | Server's outbound firewall blocks the AcelleMail URL | Whitelist the AcelleMail hostname in your hosting provider's firewall |

## FAQ

**Will this work with BuddyPress / bbPress / membership plugins?** Yes — their account-creation hooks fire `user_register` automatically. Test against your specific plugin to confirm.

**Can I delete subscribers from AcelleMail when a WP user is deleted?** Yes — hook `delete_user` and POST to `DELETE /api/v1/subscribers/{id}` (or use the `/by-email` lookup variant). For GDPR right-to-erasure compliance, this is recommended.

**What if a user already exists in AcelleMail?** The API treats the request as upsert — the existing subscriber is updated with the new field values. No duplicate created.

**Performance at scale?** Each `wp_remote_post('blocking' => false)` returns in < 1ms. Even 10,000 registrations per hour is negligible WP overhead. The AcelleMail side handles the writes via its queue.

**HTTPS-only?** Yes, always. Plain HTTP for the API call exposes your token. AcelleMail should be on HTTPS anyway per [hardening checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist).

## Related articles

- [Integrating AcelleMail with WordPress and WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) — the master overview (this is Pattern 1)
- [WooCommerce Post-Purchase Emails](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails) — for purchase events instead of user registration
- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — token + API fundamentals
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full subscriber API reference
- [Double Opt-In vs Single Opt-In](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose) — compliance decision
- [Importing Contacts](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping) — for bulk-importing existing WP users
- [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) — no-code alternative
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — consent requirements

---

# Installing AcelleMail on DigitalOcean

> This quickstart has moved. The install walkthrough for any server, a DigitalOcean droplet included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/installing-acellemail-on-digitalocean
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-03-11 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, a DigitalOcean droplet included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# Your First Email Campaign in AcelleMail — The 7-Step Walkthrough

> A guided walk through AcelleMail's campaign builder from "New campaign" button to "Launch" button. Subject + recipients + template + schedule + confirm — visual flow + the decisions at each step.

- Canonical URL: https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-03-11 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- An AcelleMail account (any plan)
- At least one list with subscribers (`Audience → Lists → New list` + import a CSV or sign up via a form)
- A template (use a starter from the Gallery, or skip — you can build from scratch)
- A sending domain configured (SPF/DKIM/DMARC green — see [Complete DNS setup](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending))

If you don't have a list yet, see [Importing contacts CSV](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping) for the import flow. Then come back here.

## Open the campaigns screen

In AcelleMail's sidebar, click **Campaigns**. The index shows every campaign in this account with status chips:

![Campaigns index](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/01-campaigns-index.png)

Click **New campaign** in the top-right toolbar to start the wizard.
### The 7 steps of campaign creation

In AcelleMail's sidebar, click **Campaigns** → **New campaign**. The 7-step wizard:

**1. Pick the campaign type**

![Select campaign type](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/02-select-type.png)

Standard campaign (one-shot send), A/B test campaign, RSS-driven, automation-triggered. Standard is your default.

**2. Pick recipients (list + segment)**

![Recipients step](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/03-recipients.png)

Choose the list to send to. Optional: scope to a segment (e.g. "engaged-last-30-days") to send only to your most active subscribers.

**3. Setup — subject + from**

![Setup — subject line + from](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/04-setup.png)

Subject line, preheader, From name + email, Reply-to. Merge tags work here — type `{{` and the autosuggest picker opens.

**4. Pick a template**

![Template selection](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/05-template.png)

Start from a saved template, a Gallery template, or build from scratch in the drag-and-drop builder.

**5. Schedule the send**

![Schedule step](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/06-schedule.png)

Send immediately, or schedule for a specific date+time. Timezone-aware (uses your account timezone by default).

**6. Confirm + launch**

![Final confirmation](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/07-confirm.png)

Review the summary — subject, recipient count, schedule, template name. Click **Launch** to commit.

## Decisions at each step

| Step | Decision | Default if unsure |
|---|---|---|
| Type | Standard / A/B test / RSS / Automation-driven | Standard |
| Recipients | Full list or segment? | Start with full list; segment after first send shows engagement patterns |
| Subject | Direct ("Sale ends Friday") vs Curiosity ("You won't believe what we just launched") | Direct — proven higher click-through long-term |
| Preheader | What goes after the subject? | Restate the key benefit; never leave blank (inboxes auto-grab garbage if empty) |
| From name + email | Brand name OR personal name? | Personal "Maya from Brand" outperforms generic "Brand Team" by 8-15% |
| Reply-to | Same as From, or a support inbox? | Use a real-inbox address; recipients DO reply |
| Template | Drag-and-drop from Gallery, OR raw HTML? | Gallery — faster + battle-tested layouts |
| Schedule | Send now, or schedule? | First few sends: schedule mid-morning recipient time, weekday |

## After launch — monitor

The campaign report tab activates immediately after launch. Watch:

- **Tracking log** — per-message audit; first 5-10 minutes show dispatching messages
- **Bounce log** — should stay <2% bounce rate
- **Opens** — populate gradually over 24-72 hours (most opens within 4 hours of send)
- **Clicks** — same as opens, with peak click-times slightly later

Open rate is the leading signal. <15% suggests subject/list problem; >25% is healthy; >40% is excellent.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Subject field is empty after typing | Browser autocomplete cleared it | Manually re-type; AcelleMail saves on blur |
| Recipients step shows "0 subscribers" | No subscribers in selected list | Audience → list → Import subscribers first |
| Template step crashes mid-edit | Stale browser cache | Hard refresh (Cmd-Shift-R), re-open builder |
| Schedule rejects "tomorrow 9am" | Account timezone mismatch | Settings → Account → Timezone → set correctly |
| Confirm step shows wrong recipient count | Filter / segment included subscribers you didn't expect | Recipients → re-scope segment |
| Launch button disabled after final review | Required field missing (subject usually) | Scroll up; field is highlighted in red |

## Common first-launch mistakes (avoid)

| Mistake | Why it hurts |
|---|---|
| Sending to your full list on first try | Without warmup, even legitimate sends can spike bounces — start with a subset |
| Setting From: noreply@brand.com | Recipients can't reply; complaints rise; deliverability drops |
| Skipping preheader | Inbox preview shows random first-line text — usually trash |
| Using "Click here" as button text | Specific action text ("Get my 20% discount") outperforms 3-5× |
| Launching at 3am | Open rates plummet outside recipient business hours |
| Forgetting unsubscribe link | Mandatory by CAN-SPAM, CASL, GDPR — most modern templates include it; verify |

## What to do after the first send

1. **Wait 4 hours** — let opens populate
2. **Check the bounce log** — flag any persistent failures
3. **Review the tracking log** — anything stuck in "Sending" >30min suggests queue worker issues
4. **Calibrate your next send** — based on what worked + what didn't (subject testing pattern)
5. **Schedule the next campaign** — keep cadence consistent (weekly is the floor for most)

<details>
<summary><strong>Advanced: A/B testing the first campaign + segment-based first sends + API-driven launches</strong></summary>

**A/B testing your first campaign:**

If you have >5,000 subscribers, A/B test the subject from day one:

1. New campaign → A/B test type
2. Variant A: direct subject ("Spring sale: 20% off this week")
3. Variant B: curiosity subject ("This is the email I should have sent last month")
4. Winner rule: highest open rate at 4 hours, 20% sample
5. Launch

The winning subject gets sent to the remaining 80%. Learn what works for YOUR audience before committing full volume.

See [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) for the full pattern.

**Segment-based first sends:**

Even with a long-trusted list, segment for the first send:

```
Send 1: Top 10% most-engaged subscribers (segment: opens-last-30d-3+)
Send 2: Next 30% (engaged-last-30d-1+)
Send 3: Rest (after confirming first 2 sends had clean bounce + complaint rates)
```

Builds reputation gradually. Catches list-quality issues before they spike across the full audience.

**API-driven campaign launch:**

For programmatic campaign creation (CMS integrations, scheduled blog-to-email):

```bash
curl -X POST "https://acellemail.com/api/v1/campaigns" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Weekly digest 2026-05-20",
    "list_uid": "...",
    "template_uid": "...",
    "subject": "Weekly digest — your 5-minute read",
    "from_email": "weekly@brand.com",
    "from_name": "Brand Weekly",
    "reply_to": "support@brand.com",
    "schedule": "2026-05-20T09:00:00Z"
  }'
```

Returns the new campaign UID. Edit further via API or in the UI; launch when ready.

**Cadence guidance:**

| Frequency | Use case |
|---|---|
| Daily | News / financial / day-pass services |
| Weekly | Newsletters, B2B SaaS, e-commerce digest |
| Monthly | Slow-news brands (legal, real estate, B2B services) |
| Triggered only | Transactional + automation-driven |

Pick a cadence + commit. Inconsistent send rhythm hurts engagement more than over-sending does.

</details>

## Related articles

- [Email template design best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)
- [Email preheader text — the hidden conversion booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster)
- [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Campaign scheduling and timezone optimization](https://acellesend.com/kb/articles/campaign-scheduling-and-timezone-optimization)
- [Personalization beyond first name](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags)

---

# Multiple Sending Servers in AcelleMail — Add, Mix, Route

> Spread your volume across several SMTP and API senders for failover, reputation isolation, and headroom past any single vendor's cap. This guide walks the real screens: the type picker, the connect form, the per-campaign "Use all servers" toggle, and the admin pool view.

- Canonical URL: https://acellesend.com/kb/articles/configuring-multiple-sending-servers
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-10 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why more than one sending server

A single sending server is a single point of failure: one vendor outage halts your campaigns, one reputation hit poisons everything you send. Running 2-4 servers from different vendors buys you breathing room. Concrete reasons:

| Goal | What multi-server gives you |
|---|---|
| **Survive a vendor outage** | When one server is paused or rejected, the rest of the pool keeps sending. |
| **Isolate marketing from transactional** | Put marketing on one server and one-off/notification mail on another, so a blocked marketing IP can't drag everything else down. |
| **Grow past a per-vendor cap** | New SES and similar accounts start with a low daily ceiling. Add a second vendor and your combined headroom goes up immediately. |
| **Build reputation in parallel** | Warm a new vendor's IPs at low volume while an established server carries the bulk of the load. |

## Add a second server (customer side)

### Open the Sending servers screen

In the sidebar, go to **Sending → Sending servers**. The page is titled **Sending Servers** with the subtitle *"Manage SMTP and API servers that power your email delivery."* Your current servers show in the table, with a search box and **Active / Inactive** + type filters above it.

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

The right rail shows your vendor mix, total hourly throughput, and warmup status at a glance — a quick way to see how concentrated your sending is on a single vendor.

> The green **Add server** button only appears if your plan allows you to run your own sending servers. On a plan where the administrator manages the pool for you, you won't see it — skip to *Choosing which server a campaign uses* below.

### Click "Add server" → pick a type

The top-right **Add server** button opens the type picker, titled **Choose a server type**. It's a card grid of every provider this install supports — generic SMTP, Amazon SES (API or SMTP), SendGrid, Mailgun, SparkPost, Elastic Email, Sendmail, Gmail OAuth, and any provider added by a plugin (those appear in a separate section).

![Sending-server type picker — "Choose a server type"](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/06-type-selector.png)

Click the provider and integration type you want. The connect form opens with fields specific to that type.

### Fill in the connection and save

For generic **SMTP**, the form (**Connect sending server** → *Connection settings*) asks for host, port (defaults to 587), encryption (TLS / SSL / None), username, and password:

![SMTP connect form](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/07-create-form-smtp.png)

Pick an **API** type instead and the fields change to match. Amazon SES (API), for example, asks for an AWS access key ID, AWS secret access key, and an AWS region:

![Amazon SES API connect form](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/12-create-form-amazon-api.png)

Click **Add server**. AcelleMail saves the credentials and drops you on the new server's detail page. The server isn't probed automatically on save — you confirm it works in the next step.

### Test the connection

On the server detail page, use **Test connection**. AcelleMail tries to connect with the credentials you entered and reports back inline — *"Connection successful."* or *"Connection failed."* There's also a test-send option to push a single message through and check your own inbox (*"Test email sent — check your inbox."*).

![Server test modal](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/05-server-test-modal.png)

If the test fails, click into the server, fix the host / port / credentials, save, and test again. A server stays **Inactive** until you've got a clean connection and enabled it.

## Choosing which server a campaign uses

By default a campaign uses **all** of the sending servers available to you, picked at random per message — that's the redundancy you want most of the time. You control this per campaign in the **Sending servers** section of the campaign setup step (under *Show advanced settings*).

- The **Use all servers randomly** toggle is ON by default. Leave it on and every message goes through a random pick from your whole pool.
- Turn it OFF to **narrow** the campaign to specific servers. Flip the include switch on the servers you want this campaign to use.
- With a narrowed selection, **Adjust weights** lets you set how much traffic each chosen server carries relative to the others (drag a slider per server).

This is how you get deterministic-ish routing without leaving the app: a "newsletter" campaign narrowed to your bulk vendor, a "receipt confirmation" campaign narrowed to a clean transactional server. If every server you picked happens to be inactive at send time, AcelleMail stops the campaign with an error rather than silently falling back to the full pool — so a narrow always means what you said.

> On a plan where the administrator manages the pool, this section is read-only: it lists the servers your plan grants and tells you they're shared and picked at random per message. You can't narrow it from the campaign — that's an admin-side decision (see below).

## The admin pool view (self-hosted operators)

If you run a multi-tenant install, **Admin → Sending Servers** is the full pool across all customers. The top of the page shows three stat cards — **Total**, **Active**, and **Inactive** — over a searchable, filterable table of every server.

![Admin pool — sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/08-admin-pool-list.png)

The admin-side server detail has more knobs than the customer one — connection, configuration (including the sending limit), identity / DKIM, warmup strategy, and bounce/feedback handling:

![Admin server detail](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/09-admin-server-detail.png)

To decide which customers can send through which servers, attach servers to plans under **Admin → Plans → [plan] → Sending servers**. Customers on that plan then send through the servers their plan grants — that's how you give a higher tier premium sending IPs while a basic tier shares a default pool.

## Common UI signals and fixes

| What you see | What to do |
|---|---|
| New server stays **Inactive** | Open the server → **Test connection**. Fix host / port / credentials based on the failure message, save, and test again. |
| **Add server** button is missing | Your plan doesn't allow your own servers (or you've hit your server quota). The pool is managed for you under your plan. |
| A campaign sent through a server you didn't expect | With **Use all servers randomly** on, picks are random per message. Turn it off in the campaign's **Sending servers** section and select only the servers you want. |
| Customer says they only see one server | The other server isn't attached to their plan. Go to **Admin → Plans → [plan] → Sending servers** and add it. |
| Campaign won't send at all | Either the customer's plan grants no active servers, or you narrowed the campaign to servers that are now all inactive. Re-check the plan's attached servers and each server's status. |

## What to do after

- Run **Test connection** on every new server before you point a real campaign at it.
- If you added a brand-new vendor or IP, start it at low volume — see [IP warmup schedule for new sending servers](https://acellesend.com/kb/articles/multi-server-rotation-pattern).
- Watch bounce and complaint rates after the first send from a new server — see the related article below.

## Related articles

- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Multi-server rotation pattern](https://acellesend.com/kb/articles/multi-server-rotation-pattern)
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail)
- [Deliverability incident runbook](https://acellesend.com/kb/articles/deliverability-incident-runbook)

---

# RSS-to-Email Campaigns in AcelleMail — Auto-Send Your Blog Posts

> New blog post published? AcelleMail can detect it via RSS and auto-send to your list. This guide walks the RSS campaign type, the feed URL setup, and the template that maps RSS items into email blocks.

- Canonical URL: https://acellesend.com/kb/articles/setting-up-rss-to-email-campaigns
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-10 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What RSS-to-email does

A new blog post publishes → AcelleMail detects via RSS feed → sends an email summary to your list automatically. Zero manual scheduling per post.

Use when:
- You publish content (blog, podcast, video) regularly
- You want a "new content" notification email per publication
- Manual campaign creation per post is tedious + error-prone

Skip when:
- Publication is irregular (every 3-6 weeks); manual is fine
- You want each post to have a hand-curated email (different angle, different subject) — RSS sends a templated summary

## The setup in 3 phases

```
Phase 1: Pick RSS campaign type
Phase 2: Configure the RSS feed URL + frequency
Phase 3: Build the email template with RSS blocks
```

## Phase 1: Create an RSS campaign

In the campaign builder, type-selector shows the RSS option:

![RSS campaign type](https://acellesend.com/plugins/acelle/landing/images/help/kb/rss-widget/01-no-rss-type.png)

Pick **RSS-driven**. Continue to recipients + setup as usual.

## Phase 2: Configure the RSS feed

In the setup step (RSS campaign type adds an RSS Feed URL field):

```
RSS Feed URL:  https://yourblog.com/feed.xml  (or https://yourblog.com/rss/)
Check frequency: every 1 hour / every 6 hours / daily
```

AcelleMail polls this URL at the chosen interval. When new items appear (newer than the last send), AcelleMail queues an email.

Most blogs publish their RSS at `/feed/`, `/feed.xml`, `/rss/`, or `/?feed=rss2`. Test the URL in a browser; you should see XML output.

## Phase 3: Build the template

In the template step, the builder includes an **RSS block** type. The Templates index lets you choose a template that already has RSS blocks, or build from scratch:

![Templates index](https://acellesend.com/plugins/acelle/landing/images/help/kb/rss-widget/02-templates-index.png)

Drag the RSS block into the builder canvas:

![Builder with RSS block](https://acellesend.com/plugins/acelle/landing/images/help/kb/rss-widget/03-builder-canvas.png)

The RSS block expands to:

```html
<!-- Auto-populated at send time from RSS feed -->
{{ rss_items_loop }}
  <h3>{{ item.title }}</h3>
  <p>{{ item.summary }}</p>
  <a href="{{ item.url }}">Read more</a>
{{ end_loop }}
```

Customize the markup. Each RSS item becomes one block in the rendered email.

Available RSS variables:

- `{{ item.title }}` — post title
- `{{ item.summary }}` — post excerpt
- `{{ item.url }}` — post URL
- `{{ item.image }}` — featured image URL (if RSS includes it)
- `{{ item.published_at }}` — publication date
- `{{ item.author }}` — author (if RSS includes it)

## Common setup decisions

| Decision | Recommendation |
|---|---|
| Check frequency | Daily for slow blogs; every 6h for moderate; every 1h for news-style |
| Max items per send | 1-3 for daily; 5-10 for weekly digest |
| Send mode | Per-item (one email per new post) OR digest (one email per period covering multiple posts) |
| Schedule | Aligned to publish-window (e.g. weekly digest sent Mondays after the weekend's publication window) |
| Template style | Single-item layout for individual posts; multi-card layout for digests |

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| RSS feed URL invalid | Wrong path or no RSS published | Test in browser — should return XML |
| No RSS items appearing in test send | Items already-sent (cached); OR feed has 0 items | Force feed re-poll OR publish a new post |
| RSS template renders blank | RSS variables not in correct syntax | Check `{{ item.X }}` (correct) vs `{rss.X}` (wrong) |
| Items appear but missing images | RSS feed doesn't include `<image>` or `<enclosure>` tags | Configure your CMS to publish images in RSS; or fall back to a default image |
| Send fires immediately after setup | RSS already had unsent items at config time | Mark current items as "sent" in admin; OR delete and re-set up after most recent post |
| Recipients see truncated text | Summary length capped by RSS source | Extend `<description>` in RSS feed; OR replace with `{{ item.content }}` for full content |

## Common RSS feed sources by platform

| Platform | RSS path |
|---|---|
| WordPress | `https://yourblog.com/feed/` |
| Ghost | `https://yourblog.com/rss/` |
| Hugo | `https://yourblog.com/index.xml` |
| Jekyll | Plugin-dependent; typically `/feed.xml` |
| Substack | `https://yoursubstack.substack.com/feed` |
| Medium | `https://medium.com/feed/@username` |
| Custom CMS | Usually `/rss/` or `/feed/`; check your CMS docs |

## Anti-patterns

| Mistake | Fix |
|---|---|
| RSS-sending to a cold list | Build engagement first with hand-curated sends; introduce RSS after audience trust |
| Sending every single post | Most subscribers want digests; per-post sends drive unsubscribes |
| Generic "Latest post" subject | Use `{{ item.title }}` in the subject for specificity |
| RSS-only no other sends | Mix in hand-curated campaigns — pure-RSS audiences see you as a content firehose |

<details>
<summary><strong>Advanced: per-item conditional sending + per-author RSS + multi-feed digest patterns</strong></summary>

**Per-item conditional sending:**

If your RSS feed mixes content types (blog posts + announcements + product updates), you may want to send only certain types:

```
{% if item.tags contains 'newsletter' %}
  <!-- this item goes in the email -->
{% endif %}
```

In the RSS block builder, add the conditional. Only items matching the tag get rendered.

Some CMS platforms expose tags in RSS as `<category>` or `<tag>` elements; check your platform's RSS output.

**Per-author RSS:**

For multi-author blogs:

```
{% if item.author == 'Maria' %}
  <p>📝 By Maria</p>
{% elsif item.author == 'Tobias' %}
  <p>📝 By Tobias</p>
{% endif %}
```

Personalize the email when readers follow specific authors.

**Multi-feed digest pattern:**

Combine multiple RSS feeds into a single digest email:

```
Feed 1: https://blog.brand.com/feed
Feed 2: https://news.brand.com/feed
Feed 3: https://announcements.brand.com/feed
```

The campaign builder supports multiple RSS sources (some installs). Each feed contributes items; the template renders all in sections:

```
## From the blog
{{ rss_items_blog }} ...

## News
{{ rss_items_news }} ...

## Announcements
{{ rss_items_announcements }} ...
```

**Programmatic RSS-to-AcelleMail bridge:**

For full control over publish-to-email logic:

```bash
# Webhook from your CMS on publish → bridge service → AcelleMail
# Don't rely on RSS polling; push the post directly

curl -X POST "https://acellemail.com/api/v1/campaigns" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Blog: $POST_TITLE",
    "list_uid": "...",
    "template_uid": "...",
    "subject": "$POST_TITLE",
    "preheader": "$POST_EXCERPT",
    "merge_tags": {
      "post_title": "$POST_TITLE",
      "post_url": "$POST_URL",
      "post_image": "$POST_IMAGE"
    },
    "schedule": "in 5 minutes"
  }'
```

Faster than RSS polling; more flexibility for content gating + personalization.

**Re-sending RSS to engaged segment:**

After 7 days, re-send unread items to non-openers:

```
Day 0: RSS triggered, email sent to full list
Day 7: Resend to subscribers who didn't open in 7 days
       Subject: "ICYMI: [original subject]"
```

Captures readers who missed the original. Open rates typically rise 20-40% on the re-send.

**RSS digest scheduling:**

Weekly digest = best for most senders. Daily digest gets ignored; per-post sends are noisy. Middle ground:

```
Frequency: Weekly (Monday 9am ET)
Min items: 1 (skip the week if nothing published)
Max items: 5 (cap if you published heavily)
```

Set in the RSS campaign's advanced config.

</details>

## Related articles

- [Your first email campaign in AcelleMail](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail)
- [Email template design best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)
- [Campaign scheduling and timezone optimization](https://acellesend.com/kb/articles/campaign-scheduling-and-timezone-optimization)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

# Custom Tracking Domain for Click URLs — AcelleMail Setup

> By default every link in your emails routes through acellemail.com's tracking redirect. Replace with click.yourdomain.com — same redirect mechanics, your own brand. This guide walks the setup + the CNAME + the link-report verification.

- Canonical URL: https://acellesend.com/kb/articles/custom-tracking-domain-for-click-urls
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-07 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why a custom tracking domain matters

Every link in your AcelleMail campaign is auto-rewritten at send time to route through a tracking redirect. The default redirect lives at `acellemail.com/c/abc/...` — AcelleMail records the click, then forwards to your real URL.

Two problems with the default:

1. **Brand inconsistency** — recipient sees `acellemail.com` in the hover preview before clicking. For B2B audiences, that breaks trust ("why is this routing through a third party?").
2. **Deliverability signal** — Gmail's "show original" pane displays the rewritten URL. Some receivers cross-check sender domain vs link domain; mismatched values can downgrade reputation.

Custom tracking domain = `click.yourdomain.com` (or any subdomain you pick) handles the redirect instead. Same mechanics, your brand throughout.

## The setup in 4 steps

### Step 1: Pick your subdomain

Convention: `click.brand.com` or `track.brand.com` or `links.brand.com`. Pick a subdomain you don't already use for anything else (it'll be reserved for tracking redirects).

### Step 2: Publish the CNAME at your DNS host

At your DNS provider (Cloudflare, Route 53, Namecheap, etc.), add:

```
Type:  CNAME
Name:  click.yourdomain.com   (or whatever subdomain you picked)
Value: track.acellemail.com   (the AcelleMail tracking endpoint — varies per install)
TTL:   3600 (or default)
```

Wait for DNS propagation (typically 5-60 minutes; can take up to 24h for stubborn caches).

### Step 3: Register the tracking domain in AcelleMail

In AcelleMail, **Sending → Tracking domains → Add tracking domain**. Enter `click.yourdomain.com` + click **Verify**. AcelleMail dig-checks the CNAME, flips to *Verified* when propagation is complete.

### Step 4: Attach to your sending server

Open the sending-server detail → Configuration → **Tracking domain** dropdown → pick the newly verified domain. Save.

![Sending server detail — tracking domain field in Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

From the next campaign send onwards, every link uses your custom tracking domain.

## Verify it's working

After a send, open the campaign report's **Links** tab:

![Links report — click destinations](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/06-links-report.png)

Hover over any link — the rewritten URL should now show `click.yourdomain.com/...` not the AcelleMail default. If still showing the default, the sending server config didn't pick up the change (re-save the server config + send a fresh test).

The **Click map** visualization shows where clicks land — independent of which tracking domain rewrites them:

![Click map](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/02-click-map.png)

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Tracking domain stays *Pending* after 1 hour | CNAME not propagated | Wait up to 24h; if still pending, verify CNAME at your DNS host points to the correct target |
| Tracking domain *Verified* but new campaigns still use acellemail.com | Sending server's tracking-domain field not updated | Sending server detail → Configuration → Tracking domain → re-pick + save |
| Hover preview shows weird URL (mix of yours + acellemail) | Cached email body from before the change | Send a fresh test; old campaigns retain their snapshot of tracking URLs |
| Click reports show clicks but tracking domain says 0 traffic | DNS resolving differently for some recipients | Test with `dig click.yourdomain.com` from multiple regions |
| CNAME conflicts with existing record at subdomain | Duplicate subdomain reservation | Pick a different subdomain name; can't have CNAME + A record on same name |

## Common conventions

| Pattern | Used by |
|---|---|
| `click.brand.com` | Most common; explicit "this is a click-tracker" signal |
| `track.brand.com` | Same intent, slightly more technical wording |
| `links.brand.com` | Friendly; works for marketing-facing audiences |
| `t.brand.com` | Short for higher-engagement industries (e-commerce flash sales) — saves URL chars in SMS-too |

Stick with one convention across your account — don't have some campaigns route via `click.` and others via `track.` (recipients learn to trust the pattern; switching mid-stream creates confusion).

<details>
<summary><strong>Advanced: HTTPS-only redirects, multi-region tracking, and security headers</strong></summary>

**HTTPS-only redirects:**

By default AcelleMail's tracking domain serves the redirect over both HTTP + HTTPS. To enforce HTTPS-only (recipients clicking via HTTP get auto-upgraded to HTTPS first):

1. AcelleMail admin → Sending → Tracking domains → [your domain] → Settings
2. Toggle "Force HTTPS"
3. Ensure your DNS host's TLS certificate covers the subdomain (Let's Encrypt + Caddy on the AcelleMail host is the common setup)

For Cloudflare-fronted setups, the redirect honors Cloudflare's TLS settings — set Full/Strict mode to prevent downgrade attacks.

**Multi-region tracking:**

If your audience is global, latency on the redirect matters. A US-hosted AcelleMail receiving Asia-side clicks adds ~200ms per hop. Two patterns:

1. **CloudFront / Cloudflare CDN** in front of your tracking domain — most installs route via Cloudflare for free; the CDN's POP nearest the user handles the redirect
2. **Multi-region AcelleMail clusters** — separate AcelleMail instance per region, each with its own tracking subdomain (`click-us.brand.com`, `click-eu.brand.com`). Per-campaign router selects the right tracking subdomain based on recipient region

For most senders, Cloudflare front-end is sufficient + free.

**Security headers on the tracker:**

The tracking endpoint redirects external URLs. Best practices:

```
HTTP Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
```

Configure at your nginx / web-server layer. The AcelleMail tracking endpoint doesn't set these by default — you add them via your reverse-proxy config.

**Programmatic verification:**

```bash
# Check tracking domain is rewriting links
campaign_uid="..."
acelle_token="..."

# Fetch a campaign's links report
curl -sH "Authorization: Bearer $acelle_token" \
  "https://acellemail.com/api/v1/campaigns/$campaign_uid/links" \
  | jq '.data[0].url'

# Should show click.yourdomain.com/..., not acellemail.com/...
```

Run as a daily cron after sends — catches misconfiguration before customer complaints.

**Rotating tracking domains:**

For very large operations, multiple tracking domains can isolate reputation per campaign type:

```
click-marketing.brand.com  — marketing emails
click-txn.brand.com        — transactional (password resets)
click-ann.brand.com        — announcements
```

Different sending servers route through different tracking domains. If `click-marketing` ever gets blocked (rare), transactional is unaffected.

</details>

## Related articles

- [Custom tracking domain](https://acellesend.com/kb/articles/custom-tracking-domain)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)
- [DNS propagation explained](https://acellesend.com/kb/articles/dns-propagation-explained)
- [Sending throttling strategies](https://acellesend.com/kb/articles/sending-throttling-strategies)

---

# Connecting Stripe for Subscription Billing

> AcelleMail's Cashier ships Stripe as the first-class subscription gateway. This guide walks the connection end-to-end: API key configuration, the four webhook events that must be subscribed, the webhook URL location, and test-vs-live mode discipline. Covers both subscription billing (for SaaS operators) and one-off payment use cases.

- Canonical URL: https://acellesend.com/kb/articles/connecting-stripe-for-subscription-billing
- Author: Tom Becker, Developer Editor
- Category: Integrations — Stripe, PayPal, WordPress, WooCommerce, Zapier, webhooks, API
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-03-03 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail's Cashier (the subscription billing layer) supports **Stripe as the first-class payment gateway**. PayPal and other gateways were removed in the recent Cashier consolidation — Stripe + Offline (manual) are what's supported today.

This guide walks the connection end-to-end. If you're setting up a [full SaaS platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform), Stripe is one step in that larger setup; this article covers the Stripe-specific configuration in depth.

## Prerequisites

- An AcelleMail install with the **Extended License** (required for SaaS / paid-subscription use)
- A Stripe account (https://dashboard.stripe.com) — start in **test mode**, switch to live after validating
- Comfortable with the Stripe Dashboard's basic navigation

## Step 1 — Get your Stripe API keys

In Stripe Dashboard:

1. Click **Developers → API keys**
2. Note your **Publishable key** (`pk_test_...` in test mode, `pk_live_...` in live mode)
3. Click **Reveal test key** next to "Secret key" to see your **Secret key** (`sk_test_...` / `sk_live_...`)

> **Test mode first, always.** Set up the whole integration in test mode (test cards, test webhooks). Validate every flow end-to-end. THEN switch to live mode — never go straight to live with new gateway code.

## Step 2 — Configure Stripe in AcelleMail

In AcelleMail Admin:

1. **Payment Gateways → Stripe → Edit**
2. Paste:
   - **Publishable Key** — from Step 1
   - **Secret Key** — from Step 1
   - **Mode** — `test` (for now)
   - **Currency** — must match your Stripe account default currency
3. Save (leave **Webhook Secret** blank for now — we'll set it after creating the webhook in Step 3)
4. Click **Enable** on the Stripe gateway

> **Disable other gateways you're not using.** AcelleMail shows every enabled gateway in the customer's plan-checkout flow. Leaving inactive ones enabled creates confusing UX.

## Step 3 — Set up the Stripe webhook

This is the most-skipped step, and **without it, renewals + cancellations don't sync** back to AcelleMail.

1. AcelleMail Admin → Payment Gateways → Stripe → after saving, AcelleMail shows the webhook URL you'll use. Format: `https://your-acellemail-domain.com/stripe/webhook`. Copy it.
2. Stripe Dashboard → **Developers → Webhooks → Add endpoint**
3. **Endpoint URL** — paste the URL from step 1
4. **Events to send** — select these 6 events (each one matters):

| Stripe event | Why AcelleMail needs it |
|---|---|
| `customer.subscription.created` | Initial subscription activation — flips customer's plan status to active |
| `customer.subscription.updated` | Plan changes, status changes (trialing → active) |
| `customer.subscription.deleted` | Cancellation — downgrades customer to default free plan |
| `invoice.paid` | Successful renewal — extends customer's billing period |
| `invoice.payment_failed` | Failed renewal — triggers dunning (retry + customer notification) |
| `payment_intent.succeeded` | One-off payment confirmation (for non-subscription flows) |

5. After clicking "Add endpoint", Stripe shows you a **Signing Secret** (`whsec_...`). Copy it.
6. Back in AcelleMail → Payment Gateways → Stripe → **paste the Signing Secret** into the Webhook Secret field
7. Save

## Step 4 — Test the round-trip

In test mode:

1. **Acelle: Admin → Plans → Add new** — create a test plan, e.g. `Test Plan` at $1/month
2. Log in as a test customer (or use a different browser)
3. Customer Dashboard → Subscribe to the test plan
4. Stripe Checkout opens → use test card `4242 4242 4242 4242`, any future expiry, any CVC, any ZIP
5. Click Pay
6. You should be redirected back to AcelleMail with subscription active

Verify each side:

- **Stripe Dashboard → Customers** — new customer with active subscription
- **Stripe Dashboard → Developers → Webhooks → click your endpoint** — should show 3 successful POST attempts (`customer.subscription.created`, `invoice.paid`, `payment_intent.succeeded`) within seconds
- **AcelleMail → Customers → your test customer** — Plan = Test Plan, Status = Active

If all three are green, the integration is working. Make a refund in Stripe (`Test Customer → Refund`) to verify the cancellation flow as well.

## Step 5 — Switch to live mode

When test mode is fully validated:

1. Stripe Dashboard → toggle **Test mode** to **Live mode** (top-right)
2. Get your live keys (Developers → API keys)
3. Create a NEW webhook endpoint in live mode (test webhooks don't carry over) — same URL, same 6 events
4. Get the live webhook signing secret
5. AcelleMail → Payment Gateways → Stripe → swap to live keys + live webhook secret
6. Save + repeat the Step 4 test using a real card (smallest amount — refund it after)
7. Watch the live webhook deliveries on Stripe Dashboard for the first day to catch any issues

## Plan design considerations

In AcelleMail's plan editor:

- **Price + currency** — must match the currency of your Stripe account
- **Frequency** — `1 month`, `1 year`, `3 months`, etc. — Stripe creates the right recurring price internally
- **Trial period** — e.g. `14` + `day` — Stripe handles the trial; customers aren't charged until trial end
- **Plans visible** — toggle off to hide a plan from the public pricing page (useful for legacy / promo plans)

> **Tip:** create both monthly + annual variants of each plan, with annual at ~17% discount. Annual conversion meaningfully reduces churn — see the [Building Your SaaS guide](https://acellesend.com/kb/articles/building-your-email-marketing-saas#step-3--the-three-tier-ladder) for pricing-ladder advice.

## Plan migrations

When you change a plan's price or limits **in Stripe**:

- Existing subscribers stay on their old terms until their next renewal
- New subscribers get the new terms immediately
- Acelle's `subscription:monitor` daily task reconciles state — usually within 24 hours

When you change a plan's `options` JSON (quotas) **in Acelle**:

- Changes apply to ALL subscribers (active and new) immediately
- This is one-way: dropping the customer's quota mid-billing-period is bad UX; bump quotas on new plans only

## Refunds + disputes

- **Refund** issued in Stripe → triggers `charge.refunded` event. AcelleMail does NOT auto-cancel the subscription on refund (refunding the latest charge doesn't necessarily end service). To stop service, also cancel the subscription in Stripe.
- **Dispute / chargeback** → triggers `charge.dispute.created`. Manual review required. Acelle doesn't auto-suspend on dispute, but you should suspend manually pending the dispute outcome.

## One-off payments (not subscriptions)

For Acelle's [sending-credit](https://acellesend.com/kb/category/integrations) / [email-verification-credit](https://acellesend.com/kb/category/integrations) products, Stripe handles one-off Payment Intents (not subscriptions). The webhook event for these is `payment_intent.succeeded` (you subscribed to it in Step 3).

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Stripe Checkout opens but customer is never charged | Test card declined OR webhook not configured (Acelle doesn't know it succeeded) | Use `4242 4242 4242 4242` for testing; verify Stripe Dashboard → Webhooks shows your endpoint with deliveries |
| Customer paid in Stripe but Acelle subscription status stays "new" | Webhook signing secret mismatch — Acelle rejects the webhook | Re-copy `whsec_...` from Stripe to AcelleMail; verify exact match |
| Renewal happens in Stripe but Acelle doesn't extend the period | `invoice.paid` event missing from webhook subscription | Edit webhook in Stripe → Add `invoice.paid` event |
| Failed payment doesn't trigger dunning emails | `invoice.payment_failed` missing OR Acelle's mail templates not configured | Add the event; verify **Admin → System Emails → Payment Failed** template exists |
| Customer downgrades plan but quotas don't drop | Acelle's `subscription:monitor` cron hasn't run yet | Wait for the next run (hourly), or trigger manually: `php artisan subscription:monitor` |
| 404 on the webhook URL | Acelle install URL changed OR `APP_URL` in `.env` is wrong | Check `https://your-domain/stripe/webhook` is reachable; verify `APP_URL` |
| Webhook works in test mode, breaks in live mode | Forgot to set up a separate live-mode webhook | Create live webhook in Stripe Dashboard live mode; swap signing secret |
| Stripe says "no such event" | Stripe API version mismatch — your account's default API version is too old | Stripe Dashboard → Developers → API version → Update to latest |
| Customer with 2FA on Stripe can't pay | 3DS challenge not handled correctly | Use Stripe's modern Checkout (default in 2026) which handles 3DS natively |

## FAQ

**Why is PayPal not supported?** The current Acelle Cashier (post-refactor) supports Stripe + Offline only. PayPal recurring subscriptions are operationally messier (no webhook for failed payments, currency conversion overhead, no first-class trial support). If you need PayPal, use Offline mode with manual reconciliation, OR set up Stripe's PayPal pass-through (Stripe can charge PayPal as a payment method on their side).

**Can I use Stripe Tax?** Yes — enable in Stripe Dashboard. Acelle's invoice flow uses Stripe's tax calculation automatically once enabled.

**Can I use Stripe Billing's coupons?** Yes — configure coupons in Stripe Dashboard; pass the coupon code via the customer's checkout URL. AcelleMail's UI doesn't have a coupon field directly; you'd need to extend the checkout flow (see [extending source code](https://acellesend.com/kb/articles/extending-acellemail-source-code)).

**Charging in multiple currencies?** Each AcelleMail plan has one currency. Create parallel plans in each currency (e.g. "Pro $79" and "Pro €72"). Customer's billing currency is locked to the plan they signed up on.

**Migrating from another billing system?** Export Stripe customer + subscription records via Stripe's CSV export. Acelle has no built-in import for Stripe data; you'd need a one-off script that creates Acelle customer records + links them via `stripe_customer_id` metadata field. Talk to support before doing this at scale.

**GDPR / right-to-erasure?** When a customer requests deletion: Stripe's data retention is governed by Stripe's own GDPR processes (you can request deletion via Stripe Dashboard → Customer → Actions → Forget). AcelleMail-side data deletion is via Admin → Customers → Delete.

**Is there a TestStripe / Stripe sandbox UI for verification?** Use Stripe's test mode (Step 1's `pk_test_` / `sk_test_` keys). Test cards from https://stripe.com/docs/testing.

## Related articles

- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — the parent setup (this is Step 2 of that)
- [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas) — pricing + GTM context
- [White-Label Customization Guide](https://acellesend.com/kb/articles/white-label-customization-guide) — branding the Stripe checkout experience
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — Acelle's own customer-lifecycle webhooks (separate from Stripe webhooks)
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — for advanced Stripe Checkout customisation
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — for programmatically managing subscriptions
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — for the system emails (welcome, payment-failed) that Stripe triggers
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — billing-data compliance

---

# Subscriber Preferences & Frequency — A Preference Center That Reduces Unsubscribes

> Offer a preference center — "Hey, instead of unsubscribing, want fewer emails per week, or only the product updates?" — and unsubscribe rates drop materially. AcelleMail's built-in Update form covers this in 3 clicks. Visual walkthrough plus per-topic-list patterns in the collapsible.

- Canonical URL: https://acellesend.com/kb/articles/managing-subscriber-preferences-and-frequency
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-03-03 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What a preference center is and why it matters

A preference center is the "let me adjust what I get" page subscribers see when they want fewer emails. The naive alternative is the all-or-nothing unsubscribe — they leave, you lose the address forever.

When you offer "Daily / Weekly / Monthly / Only product updates" instead, 30-50% of subscribers who clicked unsubscribe will pick a lower cadence option and stay on the list. That's the same address still consenting, still openable, instead of gone.

AcelleMail's built-in **Profile update form** is the preference center for most senders. You configure it once; the unsubscribe link in your emails can land subscribers on the form before fully removing them.

## Setup in three steps

### Step 1: Add the preference fields to your list

Decide the dimensions you want subscribers to control. Common ones:

| Field name | Type | Options |
|---|---|---|
| `frequency` | Dropdown | `daily` / `weekly` / `monthly` |
| `categories` | Multi-select | `product` / `marketing` / `events` / `digest` |
| `format` | Dropdown | `html` / `plain-text` |
| `channel_email_marketing` | Boolean | yes / no |
| `channel_email_transactional` | Boolean | yes / no |

Add via **Audience → [list] → Fields → New field** for each.

### Step 2: Enable the Profile update form

Every list has a built-in Profile update form. Navigate to **Audience → [list] → Forms / Pages**:

![Forms / Pages — list of all forms](https://acellesend.com/plugins/acelle/landing/images/help/kb/preferences/01-forms-pages.png)

Click the **Update form** (or "Profile update form" depending on your install). Edit which fields appear on the form — the preference fields you just added go here:

![Update form — choose fields to display](https://acellesend.com/plugins/acelle/landing/images/help/kb/preferences/02-update-form.png)

Save. Each subscriber's preference-update URL is generated automatically and accessible via merge tag in your emails.

### Step 3: Link to the preference center from your emails

In any campaign or automation email body, drop the preference-center link before the unsubscribe link in your footer:

```
Manage your preferences: {{ profile_update_url }}
Unsubscribe: {{ unsubscribe_url }}
```

The `{{ profile_update_url }}` merge tag generates a unique-per-recipient URL — clicking it lands on the update form with the subscriber's current values pre-filled.

For higher conversion, intercept the unsubscribe flow: configure your unsubscribe page (Settings → unsubscribe options, varies by install) to first land the subscriber on the preference center with a "Or pick less frequent emails" option, *before* the final remove button.

## Common preference-center patterns

### Pattern A: Frequency control only (simplest)

Single dropdown field `frequency` with values `daily` / `weekly` / `monthly`. Three automations (daily / weekly / monthly digest) read the field and only send to matches.

### Pattern B: Topic filters

Multi-select field `topics` with values like `product-updates` / `industry-news` / `events`. Each topic-list has its own campaign; subscribers see only what they opted into.

### Pattern C: Channel preferences

Boolean fields `wants_email_marketing` / `wants_email_transactional` / `wants_email_invoices`. Transactional emails (receipts, password resets) bypass marketing preferences; marketing campaigns check the flag before sending.

### Pattern D: Hybrid (most flexible)

Combine all three. Higher cognitive load for subscribers, but biggest reduction in full unsubscribes. Worth the complexity for senders with 100k+ subscribers.

## Apply preferences in campaign filters

When you launch a campaign, AcelleMail's filter step lets you scope to subscribers matching the preference values:

![Subscriber tags + filter UI](https://acellesend.com/plugins/acelle/landing/images/help/kb/preferences/03-subscriber-tags.png)

For Pattern B (topic filters):

```
Filter: categories contains 'product-updates'
Action: send campaign
```

Subscribers without `product-updates` in their categories list skip this campaign entirely — no unsubscribe pressure from off-topic sends.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Subscribers click "manage preferences" but the form is blank | Preference fields not added to the Update form | Audience → list → Forms → Update form → edit → check each field |
| Subscribers change preference but next campaign still sends to them | Campaign isn't filtering by the preference field | Campaign settings → Recipients → add filter on the preference field |
| Unsubscribe rate didn't drop after adding preference center | Footer link order wrong: unsubscribe before preferences | Move "manage preferences" ABOVE unsubscribe; most clickers take the first option |
| Mass updates needed (e.g. reset everyone's frequency to weekly) | Bulk-update CSV import with `frequency=weekly` for everyone | Audience → list → Export → edit CSV → import with duplicate-handling=Update |
| `{{ profile_update_url }}` shows the literal text | List-side merge tag disabled, OR you're previewing a non-real subscriber | Send a real test email to verify the URL resolves |
| Preference center accessible without authentication (security concern) | The default form uses signed URLs per-subscriber, not session auth | Confirm `{{ profile_update_url }}` has signature query param; sample a few preview URLs |

## What to do when someone still hits Unsubscribe

Make the unsubscribe land:

- Remove the address — don't fight users who clearly want out
- Mark in the system as **Unsubscribed** (not deleted) — AcelleMail keeps the row for compliance + analytics
- Send a one-time confirmation if your jurisdiction requires (some EU markets do)
- For B2B senders, consider a "we're sorry — was there a specific issue?" follow-up email 7 days later — captures qualitative feedback, doesn't try to resurrect

NEVER:

- Add an unsubscribe-confirmation re-opt-in step ("are you sure?") that's hard to dismiss
- Send a "you have been unsubscribed" email if they unsubscribed via list-unsubscribe header (the email IS the confirmation)
- Re-import unsubscribed addresses from a fresh CSV without explicit re-consent

<details>
<summary><strong>Advanced: per-topic-list architecture, preference center as a SPA, GDPR / CASL alignment</strong></summary>

For senders with sophisticated content programs (separate teams owning separate verticals) the single-list-with-topic-filter pattern can become unwieldy. Two scalable alternatives:

**Per-topic lists** (one list per topic):

Instead of one list with a `topics` multi-select, create separate AcelleMail lists per topic — `product-updates`, `industry-news`, `events`. Each list has its own subscribe/unsubscribe flow. Subscribers can be on all, some, or none.

Pros:
- Clean separation; each list's stats reflect its topic
- Each topic owner manages their list independently
- Subscriber unsubscribing from one topic stays on the others

Cons:
- Duplication of identity across lists (one person = N subscriber rows)
- Cross-list deduplication (segment "everyone on at least one list") needs manual setup
- Compliance auditing harder (per-topic unsubscribe records spread across N lists)

For ~10 topics or fewer, per-topic-lists works well. Above that, single-list-with-topic-filter scales better.

**Preference center as a custom landing page:**

The built-in Update form covers most cases. For brand-aligned UX (matches your website chrome, supports complex conditional fields), build a custom preference page on your own domain that calls AcelleMail's subscriber-update API on submit:

```html
<form action="/preferences/update" method="POST">
  <input name="subscriber_token" value="{{ subscriber.token }}" type="hidden">
  <h2>Email preferences</h2>
  <label>How often?
    <select name="frequency">
      <option value="daily">Daily digest</option>
      <option value="weekly">Weekly digest</option>
      <option value="monthly">Monthly only</option>
    </select>
  </label>
  ...
  <button>Save</button>
</form>
```

Server-side handler:

```php
$token = $request->input('subscriber_token');
$subscriber = decryptToken($token);

Http::withToken(env('ACELLE_TOKEN'))
   ->patch("https://acellemail.com/api/v1/subscribers/{$subscriber->uid}", [
       'fields' => $request->only(['frequency', 'categories', 'channel_email_marketing']),
   ]);
```

The subscriber token (signed JWT, expires in 7 days, references the AcelleMail subscriber UID) prevents URL-tampering — only the right person can update their preferences.

**GDPR / CASL alignment** — for EU + Canadian senders, the preference center is your compliance backbone:

- **Per-purpose consent**: GDPR Article 7 requires consent to be specific to each purpose. A single "marketing emails on/off" doesn't meet the bar — split into per-channel + per-content-type consent fields, each toggleable independently. AcelleMail's per-field consent log (in subscriber detail) records each consent grant + revocation with timestamp.

- **Withdrawal as easy as grant**: if subscribing was a single click, unsubscribing must be too. The preference center makes the *granular* withdrawal easy; the unsubscribe link is the *blanket* withdrawal — both must work.

- **Right to access**: GDPR Article 15 means subscribers can request all data you hold. Build a "Download my data" button on the preference center → calls AcelleMail's subscriber-export API → emails them a JSON with their fields + consent history. Most senders rarely get the request, but having the button defuses 90% of compliance complaints before they escalate.

**Centralised preference center across multiple AcelleMail installs** — for organizations with regional AcelleMail instances (EU + US + APAC for data-residency reasons), build a single preference center that fans out:

```
User updates preferences on prefs.example.com
   → Backend identifies which AcelleMail instances host this subscriber (could be multiple)
   → Parallel PATCH to each instance with the same field updates
   → Confirm all 200 OK before showing success to user
```

Worth doing only above a certain scale; below that, send users to the right regional center via a simple lookup.

**Soft unsubscribe** (suppression without deletion):

When a subscriber says "I'm done" you set `status=unsubscribed` rather than `delete`. The row persists; AcelleMail won't send to it; suppression list grows. This matters because:

- Re-uploading the same email (e.g. CRM sync) doesn't accidentally re-subscribe them
- Bounce reporting + complaint tracking remains intact
- Compliance audit can show "this address was on the suppression list since 2026-04-15"

In AcelleMail this is the default behaviour — clicking unsubscribe doesn't delete the row. Only **Bulk delete** in the admin UI hard-deletes.

</details>

## Related articles

- [Email list hygiene — clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Double opt-in vs single opt-in — which to choose](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose)
- [Re-engagement campaigns — win back inactive subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# AcelleMail 2026 Roadmap

> Knowing what's coming next in AcelleMail helps you plan — whether to wait for a feature vs build a custom extension, when to upgrade vs hold, how to align your stack roadmap with the product's direction. This article explains the platform's public commitments + how to follow the evolving roadmap + how to influence it. Specific feature lists drift; the meta-process doesn't.

- Canonical URL: https://acellesend.com/kb/articles/acellemail-2026-roadmap
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: AcelleMail Updates — Release notes, changelogs, new features, roadmap, community news
- Content type: announcement · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-03-01 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Knowing what's coming next in AcelleMail helps you plan: whether to **wait for a feature** vs build a custom extension; when to **upgrade vs hold**; how to align your stack roadmap with the product's direction.

This article is intentionally evergreen — rather than promise specific Q1/Q2/Q3 features (which would drift out of date the moment a release ships differently than expected), it explains:

1. **Where AcelleMail's roadmap actually lives** (it's not a single public document)
2. The **observed direction** based on recent release patterns
3. **How to influence priorities** as an operator
4. **How to plan your own work** around the roadmap's uncertainty

For "what's already in my install", see [What's New in AcelleMail](https://acellesend.com/kb/articles/whats-new-in-acellemail-52). For announcement-style updates, this is the right article.

## Where the roadmap lives

AcelleMail doesn't publish a single "roadmap.md" file. The signals come from multiple places:

### 1. CodeCanyon changelog (the strongest signal — historical)

- Your CodeCanyon Item page → **Changelog tab**
- Reading the last 6 months of patch notes shows what the team has been investing in
- Trends in titles ("Improved SES bounce handling", "New automation merge tag types", "Refactored admin UI") reveal direction
- This is HISTORICAL — not "what's coming" — but it's the most reliable predictor

### 2. CodeCanyon Item description

- Listed features get added when shipped
- "Upcoming" features sometimes appear in the description as previews
- Re-read every few months for changes

### 3. AcelleMail newsletter / blog (if subscribed)

- Periodic emails to license holders highlighting upcoming work
- Less reliable than CodeCanyon (sometimes promises shift)
- Still useful for early signals

### 4. Direct contact with the dev team

- The strongest signal — but requires you to build a relationship
- Engaged operators (those who file thoughtful bug reports, suggest features that get shipped) get more roadmap visibility over time
- See [Contributing to AcelleMail Development](https://acellesend.com/kb/articles/contributing-to-acellemail-development)

### 5. The codebase itself

- For technical operators: looking at `app/Console/Commands/` and recent migrations sometimes hints at not-yet-exposed features
- E.g. a new `ads_*` table without a corresponding admin UI = there's an Ads feature in development
- Take with a grain of salt — not every WIP feature ships

## Observed direction (as of mid-2026)

Based on 12-18 months of release-note patterns + community discussion, the platform has been investing in:

### Deliverability tooling

- **Better SES integration** — refined sending-server config, automatic SNS topic setup, FBL handling
- **Reputation monitoring** — built-in dashboards for Gmail Postmaster + Microsoft SNDS metrics
- **Multi-server rotation** — patterns for spreading sends across multiple sending IPs/servers automatically
- **Bounce / complaint workflows** — finer-grained classification + auto-suppress rules

If you're a high-volume sender, expect ongoing improvements in this area.

### Automation depth

- **More conditional logic** — multi-branch evaluation, complex tag/field conditions
- **API trigger refinements** — better security + payload flexibility
- **Webhook actions inside flows** — fire arbitrary HTTP requests at any point in an automation
- **A/B testing within automations** — branch on send-time experiments

### SaaS operator features

- **Cashier consolidation** — Stripe-only as first-class (already happened); deeper Stripe integration coming
- **Per-customer custom tracking domain** — auto-TLS via Let's Encrypt (in progress / stable depending on version)
- **Sub-account hierarchies** — Agency tier with sub-customer management (in progress)
- **Per-plan rate-limit primitives** (already shipped — see the `plan_rate_limits` table)

### Admin UI polish

- The `Refactor/Admin/*` controller namespace contains rebuilds of older admin screens
- Each minor release moves more screens into the new style
- Eventually the old `Admin/*` controllers get retired

### Integrations + extensibility

- **Plugin system** — formalising the extension API so third parties can ship modules
- **Email-verification service integration** — built-in pre-send verification
- **Multi-channel** — speculation about SMS additions, but not yet committed

## What's not on the public roadmap (don't expect)

- Self-hosted SaaS marketplace ("Mailchimp App Marketplace clone") — not in scope
- Built-in CRM / contact management beyond what AcelleMail already has (list segmentation + tags) — Acelle is staying focused on email
- Real-time chat / live messaging — not the platform's focus
- A free open-source community fork — AcelleMail's business model is the CodeCanyon licence

## How to influence the roadmap

The dev team listens to engaged operators. The pattern that works:

1. **File specific feature requests via CodeCanyon support**, not vague "would be nice"
2. **Frame as a customer problem**, not a feature: "When my customer X needs to do Y, the only way today is Z, which causes problem W"
3. **Quantify impact**: "This blocks ~$N of customer-deal MRR for us"
4. **Offer to be a beta tester** for the eventual feature
5. **Don't repeat-request** the same thing every month — once is heard; ten times is noise

Patterns that get prioritised:

- Bug fixes with clean reproductions
- Compliance-blocking features (e.g. specific GDPR requirements)
- Features that unlock a new market segment for AcelleMail (cited by multiple operators)

Patterns that don't:

- "I want this for me personally" (single-operator asks rarely move the needle)
- Theoretical asks not tied to actual customer problems
- Speculative architecture changes ("you should rewrite in Rust")

## Planning your work around the roadmap

The 4 strategies for an operator-facing roadmap:

| Strategy | When to use |
|---|---|
| **Wait** | The feature is on the roadmap + you can predict ETA + your alternative is "do nothing" |
| **Build a custom extension** | You need it now + the feature might never ship as you need it + it's worth the maintenance burden |
| **Integrate a third-party tool** | The feature is outside AcelleMail's scope (CRM, analytics, etc.) + a mature alternative exists |
| **Switch platforms** | The feature is critical to your business + AcelleMail's direction doesn't align long-term |

The right answer for most features is "wait + workaround in the meantime". Most features ship within 6-12 months of being seriously requested.

For features you build as extensions: see [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) for the upgrade-safe pattern. The faster a feature ships natively, the more your custom work becomes maintenance debt — be ready to deprecate your extension once the native version arrives.

## FAQ

**Why no public roadmap document?** Different products take different approaches. Acelle's team prioritises shipping over publishing roadmaps (which often promise things that change). The CodeCanyon changelog is the closest proxy + is regularly updated.

**Can I see what other operators are requesting?** Not publicly. CodeCanyon support tickets are private. Community channels (Discord/Slack if available) sometimes surface common requests.

**How honest is the team about not-shipping things?** Mixed historical record. Some announced features arrive on time; others shift quarters. The CodeCanyon changelog is the source of truth for what's actually shipped — speculative promises elsewhere may slip.

**Should I plan my year around a specific roadmap item?** Only if you've gotten a direct confirmation from the dev team that it's committed for a specific release. Speculation from blog posts / forum threads isn't enough to bet on.

**Is there an LTS (long-term support) version?** Not formally announced. AcelleMail supports patches across multiple major versions, but no "5.x LTS" branch.

**What about end-of-life for old major versions?** Generally, AcelleMail patches the most recent 2 major versions. If you're on 4.x and 5.x + 6.x are out, you should plan to upgrade.

## Related articles

- [What's New in AcelleMail](https://acellesend.com/kb/articles/whats-new-in-acellemail-52) — release cadence + how to find what's in your install
- [Contributing to AcelleMail Development](https://acellesend.com/kb/articles/contributing-to-acellemail-development) — how to engage with the team to influence the roadmap
- [Community Spotlight: Business Case Studies](https://acellesend.com/kb/articles/community-spotlight-business-case-studies) — what other operators have built (the demand-side signal that shapes the roadmap)
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — for features you can't wait for
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — heavily impacted by the SaaS-tooling roadmap direction
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — the integration most directly improved by the deliverability roadmap
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) — beneficiary of the automation-depth direction

---

# BIMI Setup — Show Your Logo in Gmail (and Beyond)

> BIMI (Brand Indicators for Message Identification) shows your verified logo next to your messages in Gmail, Yahoo, Apple Mail. Recipients see brand trust at glance — open rates measurably rise. This guide walks the prerequisites + the setup.

- Canonical URL: https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-03-01 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What BIMI does + why it matters

BIMI shows your verified brand logo next to your messages in supporting inboxes. Recipients see your logo (not the generic envelope icon) before they open. The effect:

- **Open rate lift of 7-15%** measured across multiple senders (Yahoo published case study; matches our customer reports)
- **Trust signal** — receivers learn "this is the real brand" at glance
- **Phishing resistance** — fake-brand emails can't display the real logo

Supported clients (as of 2026): Gmail (US/EU), Yahoo, Apple Mail (iOS 16+, macOS Sequoia+), Fastmail. Microsoft / Outlook implementation is in pilot.

## The 4 prerequisites

| Prerequisite | Required state |
|---|---|
| **DMARC at p=quarantine or p=reject** | Not p=none. Receivers won't show BIMI if your DMARC is in monitor-only mode. |
| **SVG-formatted logo** | SVG Tiny PS profile (a specific BIMI variant). Most agency logos need re-exporting. |
| **DNS record for BIMI** | TXT record at `default._bimi.yourdomain.com` |
| **VMC (Verified Mark Certificate)** | Required for Gmail; optional for Yahoo / Apple. ~$1500/yr per domain from Entrust or DigiCert. See [BIMI with VMC cost walkthrough](https://acellesend.com/kb/articles/bimi-with-vmc-cost-walkthrough). |

If you don't have all 4, BIMI won't display. Start with DMARC enforcement → SVG export → DNS record → VMC (if going to Gmail).

## Step 1: Confirm DMARC enforcement

Open your sending-server detail and verify DMARC chip is Green:
### The auth chips on the sending-server detail

Open Settings → Sending servers → click your active server. The toolbar shows the live SPF / DKIM / DMARC chip status:

![Sending server detail with auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

- **Green chips on all three** = receiving servers can confirm you're authorized to send from this domain.
- **Any chip red** = receiving servers immediately downgrade reputation. Click *Verify domain* in the toolbar to walk through the DNS-fix wizard.

Your DMARC policy must be `p=quarantine` or `p=reject` (NOT `p=none`). Check with:

```bash
dig TXT _dmarc.yourdomain.com +short
# Look for: v=DMARC1; p=quarantine; rua=...
#       or: v=DMARC1; p=reject; rua=...
```

If still on p=none, see [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration) for the staged progression.

## Step 2: Export your logo as BIMI-compatible SVG

BIMI requires SVG Tiny 1.2 Portable Secure (SVG Tiny PS). Standard SVG won't work — Gmail/Yahoo will reject.

**Constraints:**

- Aspect ratio: 1:1 (square)
- No scripts (no `<script>` tags)
- No external font references (embed paths instead)
- No `<image>` elements with external URLs (embed inline base64 if needed)
- Single XML element root
- Total file size <32KB

Most agency-exported logos need rework. Either:

1. Ask your designer to re-export as SVG Tiny PS (most adobe-illustrator users handle this with the right preset)
2. Use a BIMI logo conversion service (Entrust + DigiCert both offer paid prep services)
3. Self-validate via the [BIMI Inspector](https://bimigroup.org/bimi-generator/) — free online tool

Host the SVG at a publicly-accessible URL on your domain, e.g. `https://yourdomain.com/bimi/logo.svg`.

## Step 3: Publish the BIMI DNS record

At your DNS host, add:

```
Type:  TXT
Name:  default._bimi.yourdomain.com
Value: v=BIMI1; l=https://yourdomain.com/bimi/logo.svg; a=https://yourdomain.com/bimi/vmc.pem
TTL:   3600
```

Two parameters:

- `l=` — the URL of your SVG logo (Step 2)
- `a=` — the URL of your VMC certificate (Step 4; OPTIONAL for non-Gmail)

If skipping VMC (Yahoo / Apple only): omit the `a=` parameter:

```
v=BIMI1; l=https://yourdomain.com/bimi/logo.svg
```

## Step 4: Get a VMC (for Gmail support)

VMC = Verified Mark Certificate. Issued by Entrust (~$1500/yr) or DigiCert (~$1500/yr). Required for:

- Gmail (US/EU)
- Yahoo (recommended; not strictly required)
- Apple Mail (recommended; not strictly required)

Process: trademark verification (your logo must be a registered trademark in your jurisdiction). 4-8 weeks from application to issued certificate.

After issuance:

1. Host the VMC at `https://yourdomain.com/bimi/vmc.pem` (or any URL on your domain)
2. Add the `a=` parameter to your BIMI DNS record

## Step 5: Verify in Gmail

Send a test email to a Gmail address you control. After ~30 seconds (Gmail's BIMI lookup cache), open the message — your logo should appear in the sender column.

If not appearing:

1. Check DMARC pass at receiver (open "Show original" → look for "DMARC: PASS")
2. Verify BIMI DNS record is published: `dig TXT default._bimi.yourdomain.com +short`
3. Verify SVG is publicly accessible: `curl -I https://yourdomain.com/bimi/logo.svg`
4. Verify VMC is publicly accessible (if going Gmail): `curl -I https://yourdomain.com/bimi/vmc.pem`
5. Wait 1-2 hours — Gmail's BIMI cache takes time to refresh on first publish

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| BIMI DNS record published but logo not showing | DMARC still at p=none | DMARC must be quarantine/reject |
| Some Gmail recipients see logo, others don't | Cache propagation in progress | Wait 24-48h; verify your test recipient's Gmail account isn't cached |
| Logo appears in Yahoo/Apple but not Gmail | Missing VMC | Apply for VMC via Entrust or DigiCert |
| SVG file 404s | Hosted at wrong path | Confirm public access: `curl -I https://yourdomain.com/bimi/logo.svg` returns 200 |
| SVG file accessible but Gmail says "BIMI logo invalid" | Not SVG Tiny PS format | Re-export with the BIMI-compatible profile; validate via bimigroup.org |
| Logo appears for some campaigns but not others | Different sending domains used per campaign — check BIMI record on each | Each unique From: domain needs its own BIMI record |

## Cost-benefit math

| Cost | Benefit |
|---|---|
| VMC: $1500/yr | 10% open rate lift on Gmail (largest share of B2C email) |
| DesignerSVG rework: 2-4 hours | One-time cost |
| DNS record setup: 30 min | One-time cost |
| Trademark application (if not yet registered): $250-$2000 | Required for VMC anyway; useful for brand protection |
| Ongoing maintenance: minimal | Just renewal of VMC each year |

For a list of >100k subscribers, BIMI typically pays back within 3-6 months from incremental open rate alone.

<details>
<summary><strong>Advanced: multi-subdomain BIMI, fallback patterns, and BIMI without VMC limits</strong></summary>

**Per-subdomain BIMI:**

If you send marketing from `mail.brand.com` and transactional from `txn.brand.com`, each subdomain needs its own BIMI record:

```
TXT default._bimi.mail.brand.com    "v=BIMI1; l=https://yourdomain.com/bimi/marketing-logo.svg"
TXT default._bimi.txn.brand.com     "v=BIMI1; l=https://yourdomain.com/bimi/txn-logo.svg"
```

Different logos per subdomain if branding requires (e.g. marketing logo vs corporate logo).

**Selector-based BIMI** for fine-grained control:

```
TXT selector._bimi.brand.com  "v=BIMI1; l=https://.../variant-a.svg"
```

In your AcelleMail email headers, set:

```
BIMI-Selector: selector
```

Receivers query the matching DNS record. Allows A/B testing different BIMI logos.

**BIMI without VMC** — supports Yahoo + Apple Mail; NOT Gmail. Some senders skip VMC initially to get partial adoption + revisit Gmail-VMC after measuring impact:

```
TXT default._bimi.brand.com  "v=BIMI1; l=https://yourdomain.com/bimi/logo.svg"
```

Result: Yahoo + Apple display the logo; Gmail shows the default initial-letter circle.

**SVG accessibility checklist:**

```
✅ Single root <svg> element
✅ viewBox attribute set (e.g. viewBox="0 0 64 64")
✅ Aspect ratio 1:1
✅ <title> element for accessibility
✅ No scripting
✅ No external resources
✅ <32KB total
```

**Multi-region BIMI** — Gmail caches the DNS lookup per-domain globally; can't serve different logos per region from one BIMI record. If you need per-region logos, use selector-based BIMI (above) with per-region selector values in email headers.

**Performance impact** — BIMI lookup adds ~50-100ms latency at receive time. Gmail caches aggressively after the first lookup. For high-volume senders this is invisible.

</details>

## Related articles

- [BIMI with VMC cost walkthrough](https://acellesend.com/kb/articles/bimi-with-vmc-cost-walkthrough)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)
- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [Complete DNS setup for email sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending)

---

# Getting Started with the AcelleMail REST API

> AcelleMail ships a REST API that exposes nearly every operation the admin UI offers — create lists, add subscribers, send campaigns, fire automations, read reports. This guide walks the 4 things you need to make your first successful API call: where to find your token, how to send it, what to call first, and how to debug when it doesn't work.

- Canonical URL: https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api
- Author: Tom Becker, Developer Editor
- Category: Developer Guide — REST API, webhooks, customization, extending source code, plugins
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-26 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail ships a **REST API** that exposes nearly every operation the admin UI offers — create lists, add subscribers, send campaigns, trigger automations, read reports. If you've ever wished you could "do that thing in the admin, but from my own code", the API is the answer.

This guide is the **5-minute intro**: where to find your API token, two ways to send it, the first call to make to confirm everything works, and the common failure modes. Once this works, jump to the [REST API Authentication and Endpoints reference](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) for the full surface area.

## Step 1 — Get your API token

Every AcelleMail account (admin OR customer) has an `api_token` stored in the database. Two ways to retrieve it:

### Method A — From the UI (easiest)

1. Log in to AcelleMail
2. Click your name (top-right) → **Account / Profile**
3. Look for the **API Token** field (or **Developer / API** section depending on your version)
4. Click "Generate token" if no token exists, or "Show" / "Copy" if one does

The token is a long random string like `Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ`. Treat it like a password — anyone with this token can act as you.

### Method B — Via the login endpoint (for headless / scripted setups)

```bash
curl -X POST https://mail.example.com/api/v1/user/login \
  -H "Accept: application/json" \
  -d 'email=you@example.com' \
  -d 'password=YourPassword'

# Response:
# {"api_token": "Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ"}
```

Store the returned `api_token` in your code's secrets manager / `.env` file. **Never commit it to version control.**

## Step 2 — Two ways to send the token

Both are equivalent; pick whichever fits your HTTP client better.

### Method A — `Authorization: Bearer` header (preferred)

```bash
curl -H "Authorization: Bearer Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ" \
     -H "Accept: application/json" \
     https://mail.example.com/api/v1/me
```

Standard OAuth-style header. Works in every HTTP client. Doesn't show up in server access logs (where the URL does).

### Method B — `?api_token=XXX` query parameter

```bash
curl "https://mail.example.com/api/v1/me?api_token=Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ" \
     -H "Accept: application/json"
```

Easier for quick CLI testing. **Avoid in production** — query strings get logged in access logs, browser history, referrers. Use the header.

## Step 3 — Your first call

The `/api/v1/me` endpoint returns the authenticated user's profile. It's the standard "is my auth working?" check:

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
     -H "Accept: application/json" \
     https://mail.example.com/api/v1/me
```

Expected response (HTTP 200):

```json
{
  "id": 1,
  "uid": "Y2x4cTBxbGZ6MDAwMDAxazB0cWxqYmJ4dQ",
  "email": "you@example.com",
  "first_name": "Your",
  "last_name": "Name",
  "created_at": "2024-01-15T10:32:11.000000Z",
  "customer": {
    "id": 5,
    "uid": "...",
    "company": "Acme Corp"
  }
}
```

If you see your own data, **the API is working and your auth is correct.** Move on to the [endpoint reference](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints).

## Step 4 — Client snippets

Once auth works in `curl`, the same pattern translates to any language. Here are tested snippets:

### PHP (Guzzle)

```php
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://mail.example.com/api/v1/',
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('ACELLEMAIL_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->get('me');
$me = json_decode($response->getBody(), true);
echo $me['email'];
```

### Python (requests)

```python
import os
import requests

BASE = 'https://mail.example.com/api/v1'
HEADERS = {
    'Authorization': f"Bearer {os.environ['ACELLEMAIL_TOKEN']}",
    'Accept': 'application/json',
}

r = requests.get(f'{BASE}/me', headers=HEADERS)
r.raise_for_status()
print(r.json()['email'])
```

### Node.js (fetch)

```javascript
const BASE = 'https://mail.example.com/api/v1';
const token = process.env.ACELLEMAIL_TOKEN;

const res = await fetch(`${BASE}/me`, {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Accept': 'application/json',
  },
});
const me = await res.json();
console.log(me.email);
```

### Go (net/http)

```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

func main() {
    req, _ := http.NewRequest("GET", "https://mail.example.com/api/v1/me", nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("ACELLEMAIL_TOKEN"))
    req.Header.Set("Accept", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    var me struct{ Email string }
    json.NewDecoder(resp.Body).Decode(&me)
    fmt.Println(me.Email)
}
```

## Step 5 — Try something useful

Now that auth works, fetch your subscriber lists:

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
     -H "Accept: application/json" \
     https://mail.example.com/api/v1/lists
```

Response shape:

```json
{
  "data": [
    { "uid": "abc123", "name": "Newsletter", "subscribers_count": 5230, ... },
    { "uid": "def456", "name": "Customers", "subscribers_count": 1842, ... }
  ],
  "current_page": 1,
  "per_page": 20,
  "total": 2
}
```

The `uid` field is what you'll use for subsequent calls (e.g. `GET /api/v1/lists/abc123/subscribers`). See the [endpoint reference](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) for what each resource looks like.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| `401 Unauthenticated` | Wrong / expired / missing token | Re-fetch from UI; verify you're hitting the right base URL (`/api/v1/` not `/api/`) |
| `404 Not Found` on a known endpoint | Wrong base URL — used `/api/me` instead of `/api/v1/me` | All endpoints are versioned under `/v1/` |
| `415 Unsupported Media Type` | Missing `Accept: application/json` header | Always include this header on every request |
| `422 Unprocessable Entity` | Validation error on request body | Response body lists exactly which fields failed; fix and retry |
| `429 Too Many Requests` | Rate-limit hit (if your install has rate limiting) | Back off; check `Retry-After` header for wait time |
| `500 Internal Server Error` | Server-side bug | Check `/var/www/acellemail/storage/logs/laravel.log` for the stack trace |
| Empty `data: []` on `/lists` | Token belongs to a different customer with no lists | Verify which customer the token is for via `/me` → `customer.id`; lists are scoped per customer |
| Different shape than docs say | API response shape varies slightly across major Acelle versions | Print the full JSON in development; rely on field presence checks |
| `cURL: (60) SSL certificate problem` | Self-signed cert on staging install | `curl -k ...` for testing; fix the cert before production |
| Login endpoint returns "Invalid credentials" | Email/password wrong, OR 2FA enabled | If 2FA is on, you can't get a token via login endpoint — use the UI method (Step 1A) |

## Rate limits

By default AcelleMail doesn't impose API rate limits — it trusts authenticated users. If you're hitting the API hard (1000+ calls/sec from a script), consider adding throttling middleware via the [extending source code](https://acellesend.com/kb/articles/extending-acellemail-source-code) guide.

For **public endpoints** (signup forms via `/api/v1/public/subscribers`), DO add throttling — it's the typical spam-bot target.

## What's authenticated as what

A token belongs to **one user**, and inherits that user's permissions:

- **Admin user token:** can access `/customers`, `/subscriptions`, `/sending_servers` admin endpoints + all customer endpoints
- **Customer user token:** can only access endpoints scoped to *their* customer record (their lists, their campaigns, their automations)

This matters for SaaS operators — never give an end customer an admin token; generate them their own.

## Versioning

Current version is `v1`. AcelleMail has held this API stable across multiple major Acelle releases (4.x → 5.x). Breaking changes are rare; additive changes (new endpoints, new fields) are common — always parse responses defensively (`response.field ?? default`).

If a future `/v2/` ships, the announcement will be in [acellemail-updates](https://acellesend.com/kb/category/acellemail-updates) with a migration period for `v1`.

## FAQ

**Can I use OAuth instead of token?** Not natively in current AcelleMail. The token is functionally equivalent for machine-to-machine use cases.

**Is there a Postman collection?** Not officially shipped. Build your own by importing the OpenAPI spec — see the [endpoint reference](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) for the resource layout.

**Can a token be revoked?** Yes — generate a new one in the UI (it replaces the old one). The old token immediately becomes invalid.

**How do I expire tokens after N days?** Not built-in; add a custom Laravel scheduled task that nulls `users.api_token` for inactive accounts.

**Webhooks?** Yes, separate from the API — see [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) for outbound events (new_subscription, change_plan, etc.).

**Is there a Python / JS SDK?** Not officially. The API surface is small enough that a hand-rolled client per language is straightforward — see Step 4 snippets.

**Can I use this from a browser (CORS)?** AcelleMail doesn't ship CORS headers by default — your `/api/v1/*` calls from a different origin will be blocked. Either proxy through your own backend (recommended) or add CORS headers in nginx for the API routes (see [post-install hardening](https://acellesend.com/kb/articles/post-install-hardening-checklist)).

## Related articles

- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference (lists, campaigns, subscribers, sending_servers, customers, automations)
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — outbound events you can subscribe to
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — when the API isn't enough
- [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) — no-code automation built on the API
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — per-customer API tokens for SaaS
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — securing the API surface

---

# Re-engagement Automation in AcelleMail (Tag + Sunset Pattern)

> Inactive subscribers drag your sender reputation. AcelleMail's pattern: build a "last opened > 90 days" segment, bulk-tag them, run a 3-email win-back automation with engagement-based branching, and sunset non-responders via OPERATION nodes.

- Canonical URL: https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-21 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Subscribers who stopped opening your emails 6 months ago aren't customers — they're a tax on your sender reputation. ISPs (Gmail, Yahoo, Outlook) watch your engagement rates and downgrade your inbox placement when 30%+ of your sends go to people who never open them.

A re-engagement automation gives every inactive subscriber **one last chance**. The ones who come back stay; the ones who don't get sunsetted (suppressed). After running this end-to-end, your remaining list is smaller but engaged — better open rates, better deliverability, better revenue per email.

## The pattern in AcelleMail

This is one of the few automations that **starts with a segment** rather than an event. AcelleMail's pattern:

```
1. Build segment: "Last opened email > 90 days"
2. Bulk-tag those subscribers with `re-engagement-pending`
3. A Tag based automation fires on the new tag
4. The flow sends 3 emails, removing the tag as soon as
   the subscriber clicks anything
5. Anyone still tagged after Email 3 gets sunsetted
```

The combination of **segments** (for the "who") + **Tag based trigger** (for the "when") + **CONDITION + OPERATION nodes** (for branching + sunsetting) is the canonical AcelleMail flow for any cohort-driven automation.

## Step 1 — Build the inactive segment

Open your list's **Segments** tab and create a new segment. See the full segment builder walkthrough in [Segments article](https://acellesend.com/kb/articles/advanced-segmentation-strategies).

Conditions (Match: All):

| Field | Operator | Value |
|---|---|---|
| Last opened email | greater than (days) | 90 |
| Last link click | greater than (days) | 90 |
| Sign-up date | greater than | today-120-days *(exclude very new subscribers)* |

Name the segment **"Inactive — Win-Back"** and save. Note the count — that's how many subscribers will enter the re-engagement flow.

**Calibrate the threshold to your send cadence:**

| Your send frequency | Threshold for "inactive" |
|---|---|
| Daily | 30 days |
| 2-3x/week | 60 days |
| Weekly | 90 days |
| Bi-weekly | 120 days |
| Monthly | 6 months |

## Step 2 — Build the Tag based automation

Open **Automation → + New Automation** and pick the **Tag based** trigger from the 12-trigger picker.

![Create Automation trigger picker showing the Tag based card with description "Trigger the automation for your contacts if a specified tag is assigned to them"](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)

(Screenshot from [Welcome Series article](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) — same trigger picker.)

In the **Configure** tab:

- **Tag** = `re-engagement-pending`
- **Audience list** = the list that contains your inactives

## Step 3 — Build the 3-email win-back flow

The visual flow in the builder:

```
TRIGGER  → Tag based: re-engagement-pending
SEND EMAIL → "{{first_name}}, are you still there?" (Email 1)
WAIT     → 5 days
CONDITION → Opened "are you still there?"?
            ├─ YES → OPERATION: Add tag `re-engaged`
            │       OPERATION: Remove tag `re-engagement-pending`
            │       END
            └─ NO  → continue
SEND EMAIL → "One last thing..." (Email 2)
WAIT     → 5 days
CONDITION → Opened "One last thing..."?
            ├─ YES → OPERATION: Add tag `re-engaged`
            │       OPERATION: Remove tag `re-engagement-pending`
            │       END
            └─ NO  → continue
SEND EMAIL → "We're saying goodbye" (Email 3)
WAIT     → 1 day
CONDITION → Opened OR Clicked anything in this flow?
            ├─ YES → OPERATION: Add tag `re-engaged`
            └─ NO  → OPERATION: Move to blacklist
                     END (subscriber removed from active sends forever)
```

The OPERATION node has three actions worth knowing for re-engagement:

| Operation | What it does | Use it for |
|---|---|---|
| **Add tag** | Tags the subscriber | `re-engaged` marker for future segments |
| **Remove tag** | Untags the subscriber | Clear `re-engagement-pending` once they're saved |
| **Move to blacklist** | Permanent block (see [Blacklist article](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)) | Final sunset for non-responders |

## Step 4 — Compose the 3 emails

### Email 1 — Soft check-in (immediate on tag)

```
Subject:    {{first_name}}, are you still there?
Preheader:  We've missed you — here's what you've been missing.
Body:       Hey {{first_name}},

            We noticed you haven't been around for a few months,
            so we wanted to check in.

            Here's a quick look at what you missed:

              • [Most popular post from the last 90 days]
              • [Best offer from the last 90 days]
              • [One small win from your community]

            Still interested? Click below — that's all it takes.

            [Yes, I'm still in →] — button

            No hard feelings if not.

            — Emma
```

**Acknowledge the absence with kindness, not accusation.** *"We noticed you've been quiet"* lands better than *"You haven't opened our emails in months"*.

### Email 2 — Last value-add (5 days after, only to non-openers)

```
Subject:    {{first_name}}, one last thing before we say goodbye
Preheader:  Free resource inside — no strings.
Body:       Hi {{first_name}},

            Quick honest update: if you don't engage with this
            email or click anything in the next few days, we'll
            stop sending you emails. We hate cluttering your
            inbox if you're not interested.

            Before you go, one last thing we think you'd love:

            [Your single best free resource — guide / template /
             discount / exclusive]

            [Get it now] — button

            Either way — thanks for being part of the list.
```

**The offer needs to be genuinely valuable.** If you've trained subscribers to expect promotional emails, a generic *"10% off"* won't pull them back. Make it something they'd actually want.

### Email 3 — Goodbye (5 days after Email 2, to non-engagers)

```
Subject:    Saying goodbye, {{first_name}}
Preheader:  Unless you'd like to stay — click here.
Body:       Hi {{first_name}},

            This is our last email to you. In a couple of days,
            we'll remove you from our list.

            No hard feelings — your inbox is your space, and we
            respect that.

            If you've changed your mind, one click keeps you on:

            [Actually, keep me on →] — button

            Otherwise, we wish you well, and thank you for the
            time you've spent with us.

            — Emma
```

**Make the final email feel like a real goodbye, not another sales pitch.** Subscribers respect the honesty — some who'd been on the fence reactivate specifically because of it.

## Step 5 — Kick off the campaign

This is where re-engagement differs from event-driven automations. You need to **manually bulk-tag** the inactive segment to seed the flow.

1. Open the **Subscribers** tab of your list, set the filter to your **"Inactive — Win-Back"** segment.
2. Select all rows (or *Select page → Select all* if there are many).
3. Bulk-action: **Add tag** → `re-engagement-pending`.
4. The Tag based automation immediately starts entering each subscriber at TRIGGER.

> **Alternative: bulk-trigger from the automation's Contacts view.** Open the automation → **Contacts** tab → top-right has a **Trigger all subscribers** button (with confirmation: *"Force trigger all subscribers? This will re-run the entire workflow for everyone."*). This bypasses the tag and forces every list subscriber through. Use cautiously — it triggers EVERYONE, not just the inactive cohort.

## Step 6 — After the flow runs

Once subscribers have moved through all 3 emails (about 12 days end-to-end), the OPERATION nodes have done two things:

- **Saved subscribers** are tagged `re-engaged` — they stay on your list, and you can use this tag for future segments (*"recently rescued"* nurture, etc.).
- **Unsaved subscribers** are on your blacklist — they won't receive any future campaign, regardless of which list they're on.

**Expected outcome:** 5-15% of the inactive segment re-engages. The remaining 85-95% gets sunsetted. That's a feature, not a failure — the goal was a cleaner, more engaged list.

## Re-run cadence

Don't run re-engagement constantly. Run it as a **scheduled sweep**:

| Schedule | What to run |
|---|---|
| Quarterly | Re-build the segment, bulk-tag, send the 3-email flow |
| After every major campaign launch | One-off "win-back" send to subscribers who didn't open the launch |
| Before any major paid acquisition | Clean the list first — your CPA improves when your sender reputation is healthy |

Most well-run programs sunset 5-10% of their list per year via re-engagement. That's healthy churn, not a problem.

## Deliverability payoff

The 4-8 weeks after a re-engagement sweep typically show:

- **+15-30% open rate** on subsequent campaigns (dead weight removed)
- **+5-10% click rate** (the people remaining actually engage)
- **−40-60% spam complaint rate** (complainers were the inactive cohort)
- **Better inbox placement** at Gmail / Yahoo / Outlook as ISPs see fresh engagement signals

The first sweep is the biggest. Subsequent quarterly sweeps trim less but maintain the health.

## Common issues

| What you see | What to do |
|---|---|
| Segment shows 0 inactive subscribers | Either your audience is unusually engaged (great!) OR the segment conditions are too narrow. Loosen *"last opened > 90 days"* to 60. |
| Tag based trigger never fires after bulk-tag | The tag spelling doesn't match between the bulk action and the trigger config. Re-verify both say `re-engagement-pending` exactly. |
| Email 3 sends to subscribers who already opened Email 1 | The CONDITION nodes' YES branches don't end the flow. Each YES branch must terminate the flow, not continue to the next SEND EMAIL. |
| Subscribers ended up on the blacklist who shouldn't have | The final CONDITION's check was too strict (only checking opens, not clicks too). Use *"Opened OR Clicked"* in the final check. |
| Open rates on Email 1 are surprisingly high | This is normal and expected. Inactive subscribers often "wake up" specifically because of the *"we miss you"* framing. Don't get over-excited; check sustained engagement over the next few campaigns. |
| Some inactives marked you as spam | Their decision; AcelleMail handled it correctly by sunsetting. Don't take it personally; the goal of re-engagement is to push fence-sitters off the fence one way or the other. |

## What NOT to do

- **Don't run re-engagement immediately after deliverability problems.** Fix the deliverability first (sending domain, sender reputation), THEN run re-engagement. Otherwise you compound the bad signals.
- **Don't sunset without warning.** The 3-email sequence is your warning. Skipping it and silently removing subscribers might feel cleaner but it skips the recovery opportunity.
- **Don't use the same incentive across all 3 emails.** Each email's purpose differs (check-in / last-value / goodbye). Repeating *"10% off!"* three times feels desperate.
- **Don't bulk-blacklist instead of bulk-removing.** Blacklist is permanent across all your lists; removal from one list still lets them re-subscribe via signup forms. Match the tool to the intent.

## Related articles

- [Segments: Slice Your List Into Audiences That Convert](https://acellesend.com/kb/articles/advanced-segmentation-strategies)
- [Email List Hygiene in AcelleMail (Verify, Filter, Blacklist)](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Lead Scoring with Email Automation](https://acellesend.com/kb/articles/lead-scoring-with-email-automation)
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)

---

# Email Subject Line Formulas That Work — The Reference List

> Twelve subject formulas, ranked by reliability across audiences and industries. With examples + when each formula applies + the AcelleMail subject-field walkthrough.

- Canonical URL: https://acellesend.com/kb/articles/email-subject-line-formulas-that-work
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Best Practices — Subject lines, send timing, warmup strategies, engagement optimization
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-02-20 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The 12 formulas

| # | Formula | Example |
|---|---|---|
| 1 | **Direct benefit** | "Get 20% off through Friday" |
| 2 | **Curiosity gap** | "The 5-min change that lifted our open rates 30%" |
| 3 | **Question** | "What if your campaigns sent themselves?" |
| 4 | **Social proof** | "1,247 sellers chose us this month" |
| 5 | **Personalized** | "{{ subscriber.first_name }}, your weekly digest" |
| 6 | **Numbered list** | "7 tips for better deliverability" |
| 7 | **Urgency / deadline** | "Final 24 hours: spring sale" |
| 8 | **Exclusive / FOMO** | "VIP early access (24h before the rest)" |
| 9 | **News / announcement** | "We shipped {{ feature_name }} — finally" |
| 10 | **Story opening** | "I almost canceled our marketing budget last quarter" |
| 11 | **Honest / vulnerable** | "We screwed up. Here's how we're fixing it." |
| 12 | **Reference + benefit** | "After {{ subscriber.signup_age }} months, here's what's new" |

## Where to type the subject

In the campaign builder's Setup step:

![Subject field in setup](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/04-setup.png)

Subject field is the top field on this step. Type your subject. Merge tags work inline — see the picker below.
### Use merge tags in the subject line

The subject field accepts merge tags inline. Type `{{` and the autosuggest dropdown lists every available tag scoped to this list's fields:

![Subject — merge-tag hint](https://acellesend.com/plugins/acelle/landing/images/help/kb/merge-tags/01-setup-subject-hint.png)

The picker:

![Merge-tag autosuggest](https://acellesend.com/plugins/acelle/landing/images/help/kb/merge-tags/02-merge-tag-autosuggest.png)

Pick subscriber fields like `{{ subscriber.first_name }}` to drop into the subject:

```
Hey {{ subscriber.first_name | default('there') }}, last chance for 20% off
```

Renders per recipient: "Hey Maria, last chance for 20% off" / "Hey Tobias, last chance for 20% off" / etc. Without the `default()` filter, missing first names produce awkward blanks — always wrap with a fallback.

## When each formula applies

### Formula 1: Direct benefit
**Use when**: Product-led; recipient knows what you sell.
**Examples**:
- "Get 20% off through Friday"
- "Free shipping all weekend"
- "Save $40 on your next order"

### Formula 2: Curiosity gap
**Use when**: Content-led; recipient values learning.
**Examples**:
- "The 5-min change that lifted our open rates 30%"
- "What happened when we cut email frequency in half"
- "The mistake we made with our welcome series (don't repeat it)"

### Formula 3: Question
**Use when**: Educational or thought-provoking.
**Examples**:
- "Should you A/B test the subject AND body?"
- "What's your email open rate, really?"
- "Are you sending too much — or too little?"

### Formula 4: Social proof
**Use when**: Established brand; conversion target.
**Examples**:
- "1,247 sellers chose us this month"
- "Why 3,000+ marketers use AcelleMail"
- "{{ subscriber.industry }} pros are switching to..."

### Formula 5: Personalized
**Use when**: Have first-name or behavioral data on most subscribers.
**Examples**:
- "Maria, your weekly digest is ready"
- "Hey {{ subscriber.first_name }}, your invoice for March"
- "Your last order shipped, Maria"

### Formula 6: Numbered list
**Use when**: Tip / framework / how-to content.
**Examples**:
- "7 tips for better deliverability"
- "5 things to never put in your subject line"
- "The 3-step welcome series that doubled signups"

### Formula 7: Urgency / deadline
**Use when**: Promotion with hard-stop time.
**Examples**:
- "Final 24 hours: spring sale"
- "Last chance for early-bird pricing"
- "Sale ends tonight at midnight ET"

### Formula 8: Exclusive / FOMO
**Use when**: Members or loyal-segment campaigns.
**Examples**:
- "VIP early access (24h before the rest)"
- "Members-only: April update"
- "You're in: our beta starts today"

### Formula 9: News / announcement
**Use when**: Product or company news.
**Examples**:
- "We shipped automation triggers — finally"
- "AcelleMail 5.2 is live (here's what's new)"
- "Big update: bounce handling is now 3× faster"

### Formula 10: Story opening
**Use when**: Long-form content; opinion / case study.
**Examples**:
- "I almost canceled our marketing budget last quarter"
- "The day my list disappeared (and how I rebuilt it)"
- "What I learned from sending 100,000 emails wrong"

### Formula 11: Honest / vulnerable
**Use when**: Recovery from a mistake; trust-building.
**Examples**:
- "We screwed up. Here's how we're fixing it."
- "Our last campaign was a mess. Here's what went wrong."
- "I owe you an apology"

### Formula 12: Reference + benefit
**Use when**: Tenure-aware audience.
**Examples**:
- "After {{ subscriber.signup_age_months }} months, here's what's new"
- "Welcome back, {{ subscriber.first_name }} — here's the catch-up"
- "Since you joined, we shipped 23 features. Here are the 3 best."

## Pairing formulas with audience

```
Engaged segment       → Formulas 2 (curiosity), 4 (social proof), 10 (story)
At-risk segment        → Formulas 11 (honest), 12 (reference)
New segment           → Formulas 1 (direct), 4 (social proof), 5 (personalized)
Promotional campaigns  → Formulas 1, 7 (urgency), 8 (exclusive)
Newsletter            → Formulas 2, 6 (numbered), 10 (story)
```

## Length guidance

| Length | Use |
|---|---|
| Very short (4-10 words) | Direct benefit, deadline, news |
| Medium (10-15 words) | Curiosity, social proof, story opening |
| Long (15-25 words) | Numbered list, vulnerable / honest |
| Avoid (>25 words) | Truncates in inbox preview |

Aim for 30-50 characters typically; up to 70 with strong front-loading.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Subject open rate <15% | Subject not earning the open | Try Formula 2 (curiosity) or 4 (social proof) |
| Subject open rate >30% but click rate flat | Subject overdelivers; body underdelivers | Tighten body alignment with subject |
| Same formula working forever, then stops | Audience adapted | Rotate formulas; introduce new patterns |
| Personalization triggers spam folder | Spam filter trigger word ("$$$" / "free") | Avoid these in personalized subjects |
| Mobile preview truncates the subject | Too long | Front-load key benefit in first 40 chars |
| Recipient hits unsubscribe on welcome series | Subject claimed something the body didn't deliver | Align subject promise to body content |

## Anti-patterns

| Anti-pattern | Why it hurts |
|---|---|
| "$" / "FREE" in caps | Spam-filter trigger; modern filters detect |
| "Re: " fake-reply | Trust break when discovered |
| Excessive emoji 🔥🎉🚀 | Some clients show as boxes; reads as desperate |
| ALL CAPS WORDS | Triggers spam filters |
| Misleading subject ("Your order has shipped" when it hasn't) | Trust break + complaint risk |
| Same subject every week | Predictable = ignored |

<details>
<summary><strong>Advanced: subject formulas by industry + A/B testing formula combinations + automated subject suggestion</strong></summary>

**Subject formulas by industry:**

| Industry | Top formulas |
|---|---|
| **E-commerce (B2C)** | 1 (benefit), 7 (urgency), 5 (personalized), 8 (FOMO) |
| **B2B SaaS** | 2 (curiosity), 9 (news), 11 (honest), 10 (story) |
| **Newsletter / content** | 2, 6 (numbered), 10, 3 (question) |
| **Education** | 6, 2, 3, 12 (reference) |
| **Non-profit** | 11, 10, 4 (social proof) |
| **Subscription / recurring revenue** | 9, 11, 12, 4 |
| **Financial services** | 1, 7, 4, 11 |

**A/B testing formula combinations:**

For mature programs, test combinations:

```
Variant A: Formula 5 (personalized) + Formula 1 (benefit)
  → "Maria, get 20% off through Friday"

Variant B: Formula 7 (urgency) + Formula 4 (social proof)
  → "Final 24 hours — 1,247 chose us this month"

Variant C: Formula 11 (honest) + Formula 1 (benefit)
  → "We were almost out of stock — last 20% off"
```

Test combinations rather than pure individual formulas. Real-world subjects rarely fit one formula cleanly.

**Automated subject suggestion:**

For high-volume senders, train an ML model on:

```
Input: campaign type + recipient segment + business event
Output: 5 subject formula+content suggestions
```

Examples of input-output pairs:

```
Input: { type: 'promotional', segment: 'engaged', event: 'spring_sale_20pct' }
Output: [
  "{{ first_name }}, 20% off through Friday",
  "Final 24h — spring sale ends tonight",
  "VIP early: spring sale starts now",
  "Why 3,000+ chose us this March",
  "Spring sale — your size + 20% off"
]
```

Senders pick from the suggested list, edit, ship. Reduces "blank-page" overhead per campaign.

**Subject line evergreening:**

Track which formulas work over 12+ months:

```
Quarter 1: Formula 2 (curiosity) = 28% avg open rate
Quarter 2: Formula 2 = 26% (slight decline)
Quarter 3: Formula 2 = 22% (audience fatigue setting in)
Quarter 4: Formula 4 (social proof) = 31% (rotation works)
```

Rotate formulas as audience adapts. Avoid using the same pattern 12+ months running.

**Subject formula stacking:**

```
Day 1: Formula 1 (direct benefit) — full audience
Day 7: Formula 7 (urgency) — re-send to non-openers
Day 14: Formula 11 (honest) — second re-send with vulnerable angle
```

Three different formulas for the same offer. Catches different mood-states across the audience.

**Cross-language formula adaptation:**

Subjects translated literally often lose effectiveness:

```
English:  "Don't miss out — 20% off"  (urgency works in English)
Spanish:  "20% de descuento — solo hasta el viernes"  (urgency phrased differently in Spanish)
Japanese: "{{ first_name }}様、20%オフ - 金曜日まで"  (different formality conventions)
```

Native speakers adapt each formula to their language's natural construction. Don't rely on machine translation for subjects.

</details>

## Related articles

- [Subject line formulas that consistently work](https://acellesend.com/kb/articles/subject-line-formulas-that-consistently-work)
- [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Email preheader text — the hidden conversion booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster)
- [How to write email copy that converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)
- [AI-assisted email copy without sounding AI](https://acellesend.com/kb/articles/ai-assisted-email-copy-without-sounding-ai)

---

# My Domain Shows "Pending" — DNS Propagation Explained

> You published your SPF/DKIM/DMARC records but AcelleMail still shows the domain as Pending. That's almost always normal DNS propagation. Here's what's happening and exactly what to do.

- Canonical URL: https://acellesend.com/kb/articles/dns-propagation-explained
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: guide · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-02-18 · Last updated: 2026-08-15
- Reading time: 3 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

You added your SPF, DKIM, and DMARC records at your DNS host, came back to AcelleMail, and the domain still shows **Pending** instead of **Verified**. Nine times out of ten this is normal **DNS propagation**, not a mistake. This article explains what's happening and exactly what to do — without sending you to a terminal.

## Why it's not instant

When you save a DNS record, it doesn't appear everywhere at once. DNS is cached around the internet, and each record has a "time to live" (TTL) that controls how long resolvers hold the old answer before fetching the new one. Until those caches refresh, a checker — including AcelleMail's **Verify** — may still see "no record yet."

That's the whole story. You don't need to understand DNSSEC or routing to fix a Pending domain; you need to know how long to wait and how to tell a delay from a real error.

## What to do

1. **Wait, then re-check.** Most records propagate in **5-60 minutes**. Give it an hour, then in AcelleMail open the sending domain and click **Verify** again. A lot of "it's broken!" is just checking too soon.
2. **Still Pending after a few hours?** Re-check after the longer window — propagation can occasionally take **up to 24-48 hours**, especially if your old records had a long TTL.
3. **Still Pending after 24 hours?** Now it's worth suspecting a real mistake at your DNS host (see below) — propagation alone never takes that long.

## Telling a delay from a real mistake

If a record genuinely won't verify after a day, it's almost always one of these at your DNS host — not propagation:

- **Wrong record type** — e.g. an A record where a TXT or CNAME was needed.
- **The host/name field is off** — many DNS panels auto-append your domain, so typing `dkim._domainkey.example.com` becomes `dkim._domainkey.example.com.example.com`. Use exactly what AcelleMail shows.
- **Extra quotes or line breaks** — paste the record value exactly; some panels add their own quotes.
- **It didn't actually save** — re-open the record in your DNS panel and confirm it's there as you entered it.

Fix the record, save, wait an hour, and click **Verify** again.

## Common questions

**Does a faster TTL make AcelleMail verify quicker?** A lower TTL helps *future* changes propagate faster, but the first time you add a record there's nothing cached yet, so it's mostly down to your DNS host pushing the record out. Just wait and re-check.

**The record looks right but still won't verify — what now?** Confirm you put it on the exact domain AcelleMail is checking (the apex vs a subdomain catches people out), and that you didn't duplicate an existing SPF record — a domain may only have **one** SPF record, so multiple `v=spf1` lines must be merged into one. See [How to Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records).

## Related articles

- [How to Set Up SPF, DKIM, and DMARC Records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records) — the records AcelleMail asks you to publish
- [Complete DNS Setup for Email Sending](https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending) — the full DNS picture
- [DMARC Enforcement Migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration) — once your records verify, tighten DMARC safely

---

# API Triggers for Custom Automations — Wiring AcelleMail to Anything

> Your CRM, app, or backend can fire AcelleMail automations directly — no Zapier, no manual tagging. This guide walks the UI setup (custom-event triggers + API token generation) plus a complete payload + signing reference in the collapsible developer section.

- Canonical URL: https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-02-18 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## When you need an API-triggered automation

The standard AcelleMail triggers (subscribed-to-list, email opened, tag added) cover most marketing-side events. The **Custom Event** trigger is for everything else:

- "Subscriber finished onboarding step 3 in our app" — fires from your backend
- "Customer renewed their subscription in Stripe" — fires from a Stripe webhook bridge
- "Order shipped from warehouse" — fires from your shipping integration
- "Support ticket closed" — fires from Zendesk / Intercom

The pattern is always: your-system → HTTPS POST to AcelleMail's events API → AcelleMail's automation triggered by that event name → email sent (or tag added, or webhook out, or whatever the flow does).

This article walks the AcelleMail-side setup. The developer-side code is in the collapsible.

## UI walkthrough — get your AcelleMail side ready

### Step 1: generate an API token

In AcelleMail's top-right user menu, click **API & Webhooks** → **API tokens** → **New token**.

Give it a descriptive name (`crm-bridge-2026` or `shopify-webhook-prod`). Copy the token immediately — AcelleMail shows it once.

If you scope tokens per integration, you can revoke just that one when the bridge moves or breaks, without affecting other integrations.

### Step 2: build the automation that listens

### Open the visual automation builder

In AcelleMail's left sidebar, click **Automation** → **Automations**. The index lists every automation in this account with its current state:

![Automations index](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/01-automations-index.png)

Click **New automation** in the top-right toolbar. The trigger picker opens:

![Trigger picker — pick what starts the automation](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)


For the trigger picker, choose **Custom event**:

1. **Event name** — pick a stable identifier you'll send from your system. Examples: `onboarding_step_3`, `subscription_renewed`, `support_ticket_closed`. Lowercase + underscores, no spaces.
2. **Save** the automation

The automation is now listening. Add downstream steps (send email, add tag, send webhook):

![Listening automation — custom event trigger](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/03-flow-builder.png)

### Step 3: test from the AcelleMail UI

Inside the automation, click **Test** → **Simulate event** → fill in a test subscriber email + payload. AcelleMail runs the flow once with this test data:

![Automation flow — test mode](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/02-automation-flow.png)

The automation log records the test run; verify your downstream email or webhook actions worked correctly before you wire up the real source system.

### Step 4: configure your external system to POST

Now fire the event from outside AcelleMail. The HTTP call:

```
POST https://acellemail.com/api/v1/events
Headers:
  Authorization: Bearer <your_api_token>
  Content-Type: application/json
Body:
  {
    "subscriber_email": "user@example.com",
    "event": "onboarding_step_3",
    "data": {
      "step_name": "added_first_team_member",
      "duration_seconds": 45
    }
  }
```

The `event` value must match the event name configured in step 2 above. The `subscriber_email` must already exist on the list the automation is scoped to (or use `upsert: true` to create-if-not-exists).

The `data` object is freeform — payload-shaped JSON your automation steps can read via `{{ event.data.step_name }}` etc.

### Step 5: monitor

The Automations index shows total runs per automation:

![Automations index — run counts](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/01-automations-index.png)

Open the automation detail → run log → see every event received, with timestamp + subscriber + payload, plus the path each subscriber took through the flow:

![Run log + per-subscriber path](https://acellesend.com/plugins/acelle/landing/images/help/kb/automation-debug/02-automation-flow.png)

Failed runs (e.g. subscriber not found, payload invalid) show in red — click to see the error message.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| API returns 401 Unauthorized | Token typo'd or revoked | Regenerate token from API & Webhooks page; rotate in source system |
| API returns 404 Not Found | Wrong API path | Should be `https://your-acellemail-domain.com/api/v1/events` (not `/api/v2`) |
| API returns 200 but automation doesn't run | Event name in payload doesn't match automation's configured event name | Open automation → confirm the exact event name string |
| Automation runs but the email step skipped | Subscriber doesn't exist on the automation's scoped list | Either pre-create the subscriber, or use `upsert: true` in the API call |
| Same event fires multiple times | Bridge service retries on timeout, AcelleMail isn't idempotent on event-id | Include a unique `event_id` in payload; configure automation step to skip if event_id already processed |
| Payload data missing in email body | Merge tag uses wrong path | `{{ event.data.step_name }}` not `{{ step_name }}` — payload is nested under `event.data` |

## What event names work well

Naming events stably is one of the small things that pays back forever:

- **Use past tense** — `purchase_completed` not `purchase` (clear it already happened)
- **One concept per event** — `onboarding_step_3_completed` is fine; `onboarding_or_signup_or_invite` is not
- **Don't put data in the name** — `purchased_yoga_mat` is wrong; the product is in `data.product`, the event is `purchase_completed`
- **Match your internal product vocabulary** — if engineering calls it "session_started", call the event "session_started" too. Don't introduce a new vocabulary just for AcelleMail.

## Common automation patterns

| Use case | Event from your system | AcelleMail flow does |
|---|---|---|
| Onboarding nudge after 3 days idle | `onboarding_paused` | Sends "stuck on something?" email |
| Renewal confirmation | `subscription_renewed` | Tags as renewing-customer + thank-you email |
| Cart abandonment | `cart_abandoned` | Wait 1 hour → reminder email; wait 24 hours → discount email |
| Subscription cancel | `subscription_cancelled` | Add tag, suppress marketing sends, send win-back at 30/60/90 days |
| Customer success milestone | `nps_response_recorded` | If score >= 9, send referral CTA; if score <= 6, webhook to CRM ticket |
| Login activity | `user_logged_in` | Update last-active timestamp; trigger re-engagement if 30+ days gap |

<details>
<summary><strong>Developer: full API reference, signing, retries, batch endpoints, SDK examples</strong></summary>

The custom-event API and adjacent endpoints in detail.

**Authentication:**

```
Authorization: Bearer <token>
```

Tokens are issued per-account; rotate them when team members leave. Scope-per-integration is recommended — separate tokens for `crm-bridge`, `shopify-webhook`, `support-tool-sync` — so a leak from one source rotates without cascading.

**Fire a custom event:**

```bash
curl -X POST "https://acellemail.com/api/v1/events" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Event-ID: $(uuidgen)" \
  -d '{
    "subscriber_email": "user@example.com",
    "event": "onboarding_step_3",
    "data": {
      "step_name": "added_first_team_member",
      "duration_seconds": 45,
      "completed_at": "2026-05-19T14:30:00Z"
    },
    "upsert": true
  }'
```

Response (200 OK):

```json
{
  "event_id": "evt_a1b2c3...",
  "subscriber_uid": "sub_x9y8z7...",
  "automation_runs_triggered": ["auto_onboarding_3_nudge"],
  "processed_at": "2026-05-19T14:30:01Z"
}
```

The `X-Event-ID` header is for idempotency — if your bridge retries on timeout, repeated calls with the same event-id are deduplicated server-side.

**Batch events** (for high-volume backfill):

```bash
curl -X POST "https://acellemail.com/api/v1/events/batch" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {"subscriber_email": "a@example.com", "event": "purchase", "data": {...}},
      {"subscriber_email": "b@example.com", "event": "purchase", "data": {...}},
      {"subscriber_email": "c@example.com", "event": "purchase", "data": {...}}
    ]
  }'
```

Max 1000 events per batch. Useful for backfilling historical events when first integrating a CRM into AcelleMail.

**Webhook signing** (when AcelleMail webhooks out to your system):

If you configure AcelleMail to **Send webhook** as an automation step, AcelleMail signs every outbound request with an HMAC-SHA256 header:

```
X-AcelleMail-Signature: t=1684320000,v1=abc123def456...
```

Verify on your side:

```python
import hmac, hashlib, time

def verify_webhook(body, signature_header, secret):
    parts = dict(p.split('=') for p in signature_header.split(','))
    timestamp = parts['t']
    sig = parts['v1']
    payload = f"{timestamp}.{body}".encode()
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
```

Reject any request older than 5 minutes (replay protection) and verify the signature matches — this prevents anyone-with-the-URL from forging requests.

**Retries:**

AcelleMail's outbound webhooks retry with exponential backoff on 5xx responses: 1m, 5m, 30m, 2h, 12h, 24h. After 6 failed attempts the webhook is marked failed-permanent and the automation step's run shows red.

For inbound events FROM your system, you control retries — if AcelleMail returns 5xx, retry with exponential backoff. Don't retry on 4xx (bad payload — retrying won't fix it).

**SDK examples:**

```javascript
// Node.js
const fetch = require('node-fetch');

async function fireEvent(email, event, data) {
  const res = await fetch('https://acellemail.com/api/v1/events', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ACELLE_TOKEN}`,
      'Content-Type': 'application/json',
      'X-Event-ID': crypto.randomUUID(),
    },
    body: JSON.stringify({
      subscriber_email: email,
      event,
      data,
      upsert: true,
    }),
  });
  if (!res.ok) throw new Error(`AcelleMail event failed: ${res.status}`);
  return res.json();
}

// Usage:
await fireEvent('user@example.com', 'onboarding_step_3', {
  step_name: 'added_first_team_member',
});
```

```python
# Python
import requests, uuid, os

def fire_event(email, event, data):
    res = requests.post(
        'https://acellemail.com/api/v1/events',
        headers={
            'Authorization': f"Bearer {os.environ['ACELLE_TOKEN']}",
            'Content-Type': 'application/json',
            'X-Event-ID': str(uuid.uuid4()),
        },
        json={
            'subscriber_email': email,
            'event': event,
            'data': data,
            'upsert': True,
        },
        timeout=10,
    )
    res.raise_for_status()
    return res.json()

# Usage:
fire_event('user@example.com', 'onboarding_step_3', {
    'step_name': 'added_first_team_member'
})
```

```php
// PHP / Laravel
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

function fireAcelleEvent(string $email, string $event, array $data): array
{
    $response = Http::withToken(config('services.acelle.token'))
        ->withHeaders(['X-Event-ID' => (string) Str::uuid()])
        ->timeout(10)
        ->post('https://acellemail.com/api/v1/events', [
            'subscriber_email' => $email,
            'event' => $event,
            'data' => $data,
            'upsert' => true,
        ]);

    $response->throw();
    return $response->json();
}

// Usage:
fireAcelleEvent('user@example.com', 'onboarding_step_3', [
    'step_name' => 'added_first_team_member',
]);
```

**Local development testing** — use [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) to expose your local dev server to AcelleMail's webhook deliveries while developing:

```bash
ngrok http 3000
# AcelleMail webhook URL: https://<ngrok-id>.ngrok.io/your-endpoint
```

Update the automation's webhook destination during dev; flip back to your production URL when shipping.

**Rate limits:**

The events API accepts 1000 requests/minute per token. Burst higher than that returns 429 with `Retry-After` header — back off and retry. For high-volume systems, use the batch endpoint instead.

**Schema versioning:**

If your event payload schema changes (e.g. you rename `data.product` to `data.sku`), bump the event name explicitly: `purchase_completed` → `purchase_completed_v2`. AcelleMail automations target one event name; switching is opt-in per-automation, so old data and new data don't crash each other.

**Audit trail:**

Every API call to `/api/v1/events` is logged in AcelleMail's audit log (admin → Audit log). Useful for forensics when a bug fires unexpected events — search by token, by event name, by subscriber email.

</details>

## Related articles

- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Post-purchase follow-up automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation)
- [Lead scoring with email automation](https://acellesend.com/kb/articles/lead-scoring-with-email-automation)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

# WooCommerce Post-Purchase Emails

> The window right after a WooCommerce purchase is the highest-engagement moment a customer ever has with you. A well-timed thank-you / usage-tips / review-request / cross-sell sequence quietly increases LTV without any additional ad spend. This guide walks the hook setup, the 4-step sequence template, the product-category tagging trick, and the AcelleMail automation that ties it all together.

- Canonical URL: https://acellesend.com/kb/articles/woocommerce-post-purchase-emails
- Author: Tom Becker, Developer Editor
- Category: Integrations — Stripe, PayPal, WordPress, WooCommerce, Zapier, webhooks, API
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-17 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The window right after a WooCommerce purchase is **the highest-engagement moment a customer ever has with you.** Welcome them well and they convert again at 3-5× the baseline rate. Ignore them and you've left LTV on the table.

This guide walks the production setup: WooCommerce hook → AcelleMail list → automation flow. Pattern 2 from the [WordPress + WooCommerce integration overview](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce).

## Prerequisites

- A working WordPress + WooCommerce store
- A working AcelleMail install with an [API token](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api#step-1--get-your-api-token) configured
- An AcelleMail list dedicated to post-purchase subscribers (separate from your general newsletter — different sequence, different opt-in semantics)
- An [automation flow](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) set up in AcelleMail to consume the events (we'll cover the template below)

## Step 1 — The WooCommerce hook

The right hook for post-purchase is `woocommerce_order_status_completed` — fires after payment AND fulfilment. NOT `woocommerce_thankyou` (fires on the thank-you page render even before payment confirms) or `woocommerce_payment_complete` (fires on payment but before fulfilment, can trip on subscriptions).

In a mu-plugin (per [WordPress Subscriber Sync Step 2](https://acellesend.com/kb/articles/wordpress-subscriber-sync#step-2--create-a-must-use-plugin)):

```php
<?php
/**
 * Plugin Name: AcelleMail WooCommerce Post-Purchase
 * Description: Hook WooCommerce order completion to AcelleMail post-purchase list
 */

if (!defined('ACELLEMAIL_API_TOKEN') ||
    !defined('ACELLEMAIL_POST_PURCHASE_LIST_UID') ||
    !defined('ACELLEMAIL_BASE_URL')) {
    return;
}

add_action('woocommerce_order_status_completed', function ($order_id) {
    $order = wc_get_order($order_id);
    if (!$order) return;

    $email = $order->get_billing_email();
    if (!is_email($email)) return;

    // Build category tags for personalisation in the automation
    $tags = [];
    foreach ($order->get_items() as $item) {
        $cats = get_the_terms($item->get_product_id(), 'product_cat');
        if ($cats) foreach ($cats as $c) $tags[] = $c->slug;
    }
    $tags = array_unique($tags);

    // Pick the "headline" product (highest-value line item) for the email subject merge
    $headline_product = '';
    $top_subtotal = 0;
    foreach ($order->get_items() as $item) {
        if ($item->get_subtotal() > $top_subtotal) {
            $top_subtotal = $item->get_subtotal();
            $headline_product = $item->get_name();
        }
    }

    wp_remote_post(ACELLEMAIL_BASE_URL . '/api/v1/subscribers', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode([
            'MAIL_LIST_UID'   => ACELLEMAIL_POST_PURCHASE_LIST_UID,
            'EMAIL'           => $email,
            'FIRST_NAME'      => $order->get_billing_first_name(),
            'LAST_NAME'       => $order->get_billing_last_name(),
            'ORDER_ID'        => $order_id,
            'ORDER_TOTAL'     => $order->get_total(),
            'ORDER_CURRENCY'  => $order->get_currency(),
            'HEADLINE_PRODUCT' => $headline_product,
            'PRODUCT_CATEGORIES' => implode(',', $tags),
            'PURCHASE_DATE'   => $order->get_date_completed() ? $order->get_date_completed()->format('Y-m-d') : date('Y-m-d'),
        ]),
        'timeout'  => 10,
        'blocking' => false,
    ]);
});
```

Add the custom fields to the AcelleMail list (**Lists → your post-purchase list → Fields → Add**) before pushing — the API won't reject unknown fields, but they're discarded.

## Step 2 — Set up the AcelleMail list

In **AcelleMail → Lists → New**:

- **Name:** Customers (Post-Purchase)
- **Default subject prefix:** `[Your Store]`
- **Custom fields:**
  - `ORDER_ID` (number)
  - `ORDER_TOTAL` (decimal)
  - `ORDER_CURRENCY` (text, 3 chars)
  - `HEADLINE_PRODUCT` (text, 200 chars)
  - `PRODUCT_CATEGORIES` (text, 500 chars; comma-separated)
  - `PURCHASE_DATE` (date)
- **Opt-in:** Single (purchase IS implicit opt-in for transactional follow-up)

The reason to use a separate list (not your general newsletter): different cadence, different unsubscribe behaviour, different compliance status. A newsletter unsubscribe shouldn't stop post-purchase emails about an active order.

## Step 3 — The 4-step automation sequence

In **AcelleMail → Automation → New Workflow** on this list:

| Email | Delay | Subject example | Goal |
|---|---|---|---|
| **1. Thank you + setup** | Immediately | `Welcome, {FIRST_NAME} — your {HEADLINE_PRODUCT} ships soon` | Confirm receipt; set delivery expectations; link to setup guides |
| **2. Usage tips** | Day 3 | `Getting the most from your {HEADLINE_PRODUCT}` | Drive activation; reduce returns; build product confidence |
| **3. Review request** | Day 7 | `How's your {HEADLINE_PRODUCT}? Quick favour?` | Collect reviews; social proof for future buyers |
| **4. Related products** | Day 14 | `You might also like these...` | Cross-sell; based on PRODUCT_CATEGORIES tag |

This is the **proven** 4-step template — see [Post-Purchase Follow-Up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) for the full content templates.

### Personalising per product category

Inside the automation, branch on `PRODUCT_CATEGORIES`:

- "If `PRODUCT_CATEGORIES` contains `coffee`" → use coffee-specific templates (related products, brewing tips)
- "If `PRODUCT_CATEGORIES` contains `electronics`" → use electronics templates (compatibility tips, warranty registration)
- Otherwise → use generic templates

Acelle's automation builder supports conditional branching — see the [advanced triggers guide](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions).

### When the customer buys again

If the customer makes a *second* purchase before completing the 4-step sequence, they're re-added to the list (Acelle handles as upsert) and the automation may re-evaluate. To prevent customers from getting "How's your X?" review-request emails for a product they bought last week, add an automation step: "If purchases in last 7 days > 1, skip" or use a re-entry cooldown.

## Step 4 — Handle refunds / cancellations

When an order is refunded, you may want to pause the automation for that customer (the day-7 review email feels off if they just got a refund):

```php
add_action('woocommerce_order_status_refunded', function ($order_id) {
    $order = wc_get_order($order_id);
    if (!$order) return;

    // Remove from post-purchase list to stop the automation
    // First find subscriber by email:
    $find = wp_remote_get(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/email/' . urlencode($order->get_billing_email()),
        ['headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json']]
    );
    $sub = json_decode(wp_remote_retrieve_body($find), true);
    if (!$sub || empty($sub['id'])) return;

    // Tag them as refunded (the automation can branch on this tag)
    wp_remote_post(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/' . $sub['id'] . '/add-tag',
        [
            'headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json', 'Content-Type' => 'application/json'],
            'body' => wp_json_encode(['tag' => 'refunded:' . $order_id]),
        ]
    );
});
```

Then in the automation: "If `tag = refunded`, exit flow".

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Sequence fires twice for the same order | Order moved through `completed` status twice (e.g. manual admin retrigger) | Check tag for `order-id:{order_id}`; skip if already tagged |
| Review emails sent to refunded customers | No refund hook configured | Add Step 4 refund handler above |
| Custom fields not appearing in email merges | Custom fields not added to the AcelleMail list, OR field names lowercase | Add UPPERCASE field names in AcelleMail Lists → Fields BEFORE pushing |
| Customer complains about getting Day 14 email after a 30-day window passed | Slow queue / paused worker | Verify supervisor + queue per [queue setup guide](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) |
| Hook doesn't fire on subscription renewals | WC Subscriptions uses different hooks | Add `add_action('woocommerce_subscription_renewal_payment_complete', ...)` for renewals |
| Tag with comma in name (`product_cat` slug has comma) | `implode(',', $tags)` produces ambiguous joining | Use `;` separator instead, or filter out commas in slugs first |
| Customer base going up but automation engagement drops | Including non-customer registrations in same automation | Verify the post-purchase hook posts to the dedicated list (not the general newsletter) |

## FAQ

**WooCommerce Subscriptions hooks?** `woocommerce_subscription_renewal_payment_complete` fires on each renewal — add the same body to push to a subscriber-renewals list. Different sequence (skip "first review" since they already bought before; emphasize loyalty rewards).

**WooCommerce Memberships hooks?** `wc_memberships_user_membership_status_changed` — branch on `$new_status` to subscribe to relevant lists ("new member" vs "expired" vs "renewed").

**Can I unsubscribe customers from the post-purchase list after Day 14?** Add a final "Remove from list" step at the end of the automation. Or use a "Move to" step to add them to your general newsletter (they're now a paying customer; appropriate to nurture them long-term).

**What about transactional emails (order confirmation, shipping notification)?** Keep those in WooCommerce — that's its job, and they need to be 100% reliable per-order. The AcelleMail flow is for *marketing* follow-up.

**Can I include the order line items in the welcome email?** Yes — fetch `$order->get_items()` and pass them as a JSON-encoded `LINE_ITEMS` custom field, then parse in the AcelleMail template via merge tags + a small JS rendering. For most stores, just naming the `HEADLINE_PRODUCT` (the highest-value line) is enough.

**How do I A/B test the post-purchase sequence?** AcelleMail supports automation-level A/B (split subscribers 50/50, send different sequences). See [A/B Testing Email Subject Lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) for the principles.

**Multilingual store?** Pass the order's locale (`$order->get_meta('_locale')` if set, else `get_locale()`) as a custom field. Branch the automation on it to send the right-language sequence.

## Related articles

- [Integrating AcelleMail with WordPress and WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) — the master overview
- [WordPress Subscriber Sync](https://acellesend.com/kb/articles/wordpress-subscriber-sync) — sibling pattern for general user registration
- [Post-Purchase Follow-Up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) — the AcelleMail automation template that consumes these events
- [Creating an Abandoned Cart Email Sequence](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence) — for pre-purchase WooCommerce events
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) — for the conditional branching used in Step 3
- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — API fundamentals
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — subscriber + tag endpoints used in Step 4
- [A/B Testing Email Subject Lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) — optimise sequence performance
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — automation depends on the queue

---

# Email Template Design Best Practices — Using AcelleMail's Builder Right

> Single column on mobile, alt text on every image, preheader you actually wrote, button width tested in real clients — most "best practice" lists are abstract. This one walks the AcelleMail builder showing where each setting lives and what to verify in Preview before you Send.

- Canonical URL: https://acellesend.com/kb/articles/email-template-design-best-practices
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-02-17 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Six rules worth keeping, six places to verify them in AcelleMail

Email design best practices are everywhere, mostly abstract. This guide ties each rule to a specific control in AcelleMail's builder so you know where to look (and where to fix) before launching.

### 1. Single-column on mobile (60%+ of opens)

In the AcelleMail email builder, **drag a 1-column block** as your primary content container. Multi-column blocks (2-col, 3-col) stack vertically on mobile by default, which usually works — but only if each column is at least 280px wide on desktop. Below that, text crams and CTA buttons get cut off.

Verify in Preview:

![Mobile preview — confirms stacking](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

Toggle desktop ↔ mobile in the Preview header. If the mobile view shows side-by-side columns crushed into 320px, restructure to single column.

### 2. Preheader text — the second subject

The preheader is the gray text shown after the subject in most inbox lists. Inboxes auto-grab the first text from the email body if you don't set one — usually "Trouble seeing this email? View in browser" — wasted real estate.

Set it explicitly: campaign builder → **Campaign settings** → **Preheader** field (also called "Pre-header" or "Subject snippet" in some installs). Keep it 40-100 characters. Treat it as a second subject line — different angle, supports the main subject.

### 3. Alt text on every image

In the builder, click any image block → right panel **Image properties** → **Alt text** field. Three reasons it matters:

- Email clients with images off (corporate clients, "image off by default" filters) show alt text in place
- Screen readers announce alt text aloud
- Email reputation: image-only emails (no text alongside) trip spam filters

Standard: alt text on every image. Decorative images get `alt=""` explicitly (empty, not missing — screen readers skip them then).

### 4. Buttons: 44px tall minimum, real text

In the AcelleMail builder, the **Button block** ships at the right height by default (48px). Don't shrink it below 44px on mobile — Apple's iOS Human Interface Guidelines minimum tap target.

Real text on the button — "Get my 20% discount" beats "Click here". Click-through rate measurably improves: the button text now answers the recipient's "What's in it for me?" before they tap.

### 5. Dark-mode safety

In Apple Mail and Gmail on iOS/Android, dark mode inverts light backgrounds + flips text to white. Most templates survive, but logos with transparent PNG against a "light blue" background look terrible when the blue becomes navy.

Test by sending yourself a test email and viewing on an iPhone or Android with dark mode on. If anything looks broken, the fix is usually: change a transparent PNG to a solid-background PNG (white box around the logo) so it doesn't invert.

### 6. One primary CTA per email

Email isn't a landing page — one offer per send. If you have a secondary action (e.g. "or read the docs"), make it a text link below the button, not a second button. Multiple buttons compete for attention and the click-through rate of both drops vs. a single button.

## Use Preview + A/B to verify before sending

### Send a real preview to yourself

Builder → **Preview** → Mobile/Desktop tabs:

![Campaign preview — desktop view](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

For real-client testing, click **Send test** in the campaign settings — sends a real email to addresses you specify. Open on your phone, your laptop, Outlook web, Gmail web. Anything that looks broken there will look broken in your subscribers' inboxes too.

### A/B test the subject + preheader

AcelleMail's built-in **A/B Test** lets you split subjects (or preheaders, or content) before committing the full list:

![Pick A/B test campaign type](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/01-select-ab-test.png)

Pick the type when you create the campaign:

![A/B test setup screen](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/02-ab-test-setup.png)

Define the variants:

![Variants tab](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/03-variants-tab.png)

Pick the winner rule — usually highest open rate after 4-12 hours on a 20% sample, then send the winner to the remaining 80%:

![Winner rule picker](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/04-winner-rules.png)

The completed test report shows which subject/preheader won + the delta:

![A/B test report](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/05-ab-report.png)

## Common UI signals + fixes

| Symptom in Preview | Likely cause | UI fix |
|---|---|---|
| Layout breaks on mobile | Multi-column block, columns too narrow | Replace with single-column block |
| Text shows as alt text in test inbox | Receiving client blocks images | Add visible text near every image (don't rely on alt alone) |
| Subject "Trouble seeing this email?" in inbox preview | Preheader not set, client grabbed first text | Set explicit preheader in campaign settings |
| Spam folder on test send | Image-heavy, no plain text version | Builder → toggle **Plain text version** → ensure it has real content, not auto-stripped HTML |
| Button looks tiny on mobile preview | Button height < 44px | Click button → set min-height 44px in properties |
| Logo disappears in dark mode | Transparent PNG | Replace with solid-background PNG |

<details>
<summary><strong>Advanced: raw HTML edits, custom MJML, and inline-style discipline</strong></summary>

For teams managing brand-system email templates or migrating from another platform, the AcelleMail builder supports raw HTML editing.

**Switch to HTML mode:**

Builder → top-right toggle → **Source** mode. Paste or hand-edit HTML directly.

**Email-safe HTML rules** (different from web HTML):

- Tables for layout (Outlook desktop still requires it)
- Inline styles only — `<style>` blocks in `<head>` are stripped by Gmail
- Width fixed in pixels for desktop containers (e.g. `width="600"`), but media queries handle mobile
- No `position: absolute` — Outlook ignores it entirely
- No web fonts on first-tier reliability — use system font stack fallbacks (`-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif`)

**MJML** is the industry tool to write email HTML reliably:

```mjml
<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text font-size="20px">Hello {{ subscriber.first_name }}</mj-text>
        <mj-button href="https://example.com">Get started</mj-button>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
```

Compile MJML to email-safe HTML via the [mjml CLI](https://mjml.io) or the online editor, then paste the compiled HTML into AcelleMail's Source mode. The compiled output handles Outlook quirks, dark mode safety, mobile media queries — manually.

**Litmus / Email on Acid** are paid services that screenshot your email across 90+ real client/device combinations. Worth it for large lists, brand-sensitive sends. AcelleMail's built-in preview covers ~90% of real-world layouts; the paid services catch the long tail (Outlook 2007, Lotus Notes, Yahoo Classic).

**Template versioning** — for teams iterating on a master template, the AcelleMail **Templates** library (sidebar → Templates) lets you save a finalised template and clone it for each campaign rather than re-building from scratch. Update the master to propagate brand-color changes; existing campaigns keep their snapshot at save time.

**CI-side validation** for engineering teams hand-editing HTML — run `html-validate` or `mailparser` against the compiled email before pushing to AcelleMail's template library. Catches malformed tags, missing alt attributes, and dead links automatically.

</details>

## Related articles

- [Personalization beyond first name — advanced merge tags](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags)
- [Understanding email client rendering differences](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences)
- [Setting up UTM parameters for campaign tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking)
- [A/B testing email campaigns — best practices](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)

---

# Birthday & Anniversary Automations in AcelleMail — Date-Triggered Sends

> Send a happy-birthday discount on the day, a year-since-signup gift on the anniversary, a renewal reminder 30 days before expiry. AcelleMail's date-relative automation trigger handles all of these — this guide walks the setup with screenshots.

- Canonical URL: https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-02-13 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why date-based automations earn their spot

Birthday and anniversary emails are reliably the highest-converting transactional sends most senders run — 5-10× the click-through rate of standard newsletters. They're personal, they're expected, they coincide with a high-affinity moment.

The setup is a one-time configuration; once it's running, every subscriber whose date matches fires the email automatically. No manual scheduling.

## What you need on the list

Before building the automation, the list must have the date field you want to trigger on. Three common patterns:

| Use case | Field name | When you collect it |
|---|---|---|
| Birthday | `birthday` (Date type) | Signup form, optional field |
| Year-since-signup anniversary | (uses built-in `created_at`) | Automatic |
| Renewal reminder | `subscription_expires_at` | API push from your billing system |
| Loyalty milestone | `first_purchase_at` | API push from ecommerce |

For birthday, add a Date field via **Audience → [list] → Fields → New field** → name `birthday`, type `Date`, optional.

To collect the value on signup, edit the signup form (**Audience → [list] → Signup forms → [form] → Edit**) and add the `birthday` field with the date picker. Keep it optional — required birthday on signup tanks conversion.

For existing subscribers with no birthday, fill via import or via your account UI — you'll only fire emails for subscribers where the field is non-empty.

## Build the birthday automation

### Open the visual automation builder

In AcelleMail's left sidebar, click **Automation** → **Automations**. The index lists every automation in this account with its current state:

![Automations index](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/01-automations-index.png)

Click **New automation** in the top-right toolbar. The trigger picker opens:

![Trigger picker — pick what starts the automation](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)


For birthday emails:

1. **Trigger**: **Date relative** in the trigger picker
2. **Date field**: `birthday`
3. **Schedule**: **On the date** (offset = 0 days) — fires at midnight in subscriber's timezone if known, or in the campaign's default timezone
4. **Repeat**: **Yearly** — re-fires every year on the same month/day

The trigger picker shows the date-relative option:

![Trigger picker — pick "Date relative"](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)

After the trigger, drop in the email step:

- Subject: `Happy birthday, {{ subscriber.first_name | default('friend') }}!`
- Body: a short personal note + a discount code or gift

The configured flow:

![Configured flow — date trigger + email step](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/03-flow-builder.png)

Save. Going forward, every day at midnight AcelleMail evaluates every subscriber's `birthday` field; matches fire the email.

## Build the anniversary automation

Same pattern, different field:

1. **Trigger**: **Date relative**
2. **Date field**: `created_at` (the built-in signup-date)
3. **Schedule**: **On the date** + **Yearly**
4. **Email step**: thank-you for being-a-subscriber-N-years note

```
Subject: It's been a year, {{ subscriber.first_name | default('friend') }} — thank you 🎉
Body: One year ago today you joined us. As a thank-you, here's a code for 20% off your next order: ANNIV20.
```

For "two years since signup", run a second automation with offset = -730 days from `created_at` + 1095 = checks "exactly 3 years ago". (Or use the `created_at` Date-relative trigger and a condition `WHERE year_diff(created_at, now()) == 2`.)

## Build the renewal-reminder automation

For subscription / contract renewals, fire 30 days before expiry:

1. **Trigger**: **Date relative**
2. **Date field**: `subscription_expires_at`
3. **Schedule**: **Before** the date, offset = 30 days
4. **Repeat**: One-time per subscriber per renewal cycle
5. **Email step**: "Your subscription renews in 30 days — confirm payment method"

Then a 7-day reminder:

1. Trigger: Date relative
2. Field: `subscription_expires_at`
3. Schedule: Before, offset 7 days
4. Email: "Your subscription renews next week"

And a 1-day final notice:

1. Trigger: Date relative
2. Field: `subscription_expires_at`
3. Schedule: Before, offset 1 day
4. Email: "Tomorrow: your subscription renews automatically"

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Birthday emails never fire | `birthday` field empty for most subscribers | Confirm field is collected on signup; back-fill via import for existing subscribers |
| Birthday email fires on the wrong day | Timezone mismatch — birthday stored without TZ, sent in app timezone | See Advanced: timezone handling |
| Subscriber gets birthday email twice in the same year | Duplicate automation, both with same trigger | Open automations index — confirm only one is enabled |
| Renewal reminder fires AFTER expiry | `subscription_expires_at` was updated late by billing system | Move trigger to billing-system-side webhook instead of pulling from AcelleMail's field |
| Some subscribers get the email at 3am their time | Default timezone is server TZ, not subscriber TZ | Add per-subscriber timezone field; see Advanced |

## When to skip the birthday automation

If your industry doesn't fit (B2B SaaS where birthday feels invasive, regulated industries where personalization triggers compliance review), skip it. The "year-since-signup" anniversary is a safer alternative that earns most of the engagement without the personal-data ick factor.

<details>
<summary><strong>Advanced: timezone-aware date triggers, lookahead jobs, and lead-time logic</strong></summary>

The date-relative trigger evaluates dates in a single timezone — by default the AcelleMail server's timezone (typically UTC). For senders with a global audience, you want each subscriber to receive their birthday email at a reasonable local hour.

**Per-subscriber timezone field:**

Add a `timezone` field (Type = Text) to the list. Populate it via signup form (geolocate via IP) or via API push from your app's user model.

**Automation logic with timezone:**

The AcelleMail date-relative trigger handles the matching itself; for per-subscriber timezone offsetting, the cleaner path is to schedule the trigger to evaluate at 09:00 in **each subscriber's local time** using AcelleMail's per-recipient send-time-optimization feature (if your install supports it).

If not, the workaround: run 24 automations, one per hour, each scoped to subscribers in that hour's timezone:

```
Automation 1: trigger 09:00 server time; condition timezone = 'America/Los_Angeles'
Automation 2: trigger 10:00 server time; condition timezone = 'America/Denver'
Automation 3: trigger 11:00 server time; condition timezone = 'America/Chicago'
Automation 4: trigger 12:00 server time; condition timezone = 'America/New_York'
Automation 5: trigger 13:00 server time; condition timezone = 'America/Halifax'
...
```

Tedious to set up; runs forever. Most senders simplify to 4 buckets (Americas / EMEA / APAC / IST) and accept the imprecision.

**Lookahead via API for high-precision renewals:**

For subscription renewals where the field source is your billing system (Stripe, Chargebee), the cleanest pattern is to fire the email from the billing system's webhook rather than from AcelleMail's date trigger:

```
Stripe webhook 'invoice.upcoming' fires 30 days before renewal
→ your backend receives the webhook
→ calls AcelleMail API: trigger a "renewal_reminder_30d" custom event
→ AcelleMail automation triggered by event = renewal_reminder_30d sends the email
```

Advantage: Stripe's `invoice.upcoming` is authoritative; AcelleMail's stale `subscription_expires_at` might not have been updated yet if your billing-to-AcelleMail sync runs nightly.

**Bulk back-fill birthdays from an existing system:**

If you're migrating from a system that already has birthdays stored, push them in via the API:

```bash
# CSV file: email,birthday (YYYY-MM-DD)
while IFS=, read email bday; do
  curl -X PATCH "https://acellemail.com/api/v1/subscribers" \
    -H "Authorization: Bearer $ACELLE_TOKEN" \
    -d "{
      \"email\": \"$email\",
      \"list_uid\": \"$ACELLE_LIST_UID\",
      \"fields\": {\"birthday\": \"$bday\"}
    }"
done < birthdays.csv
```

**Lead-time math for renewal cycles:**

If `subscription_expires_at` = `2026-12-31` and your trigger says "30 days before", that fires on `2026-12-01` at the timezone the trigger runs in. For annual renewals, set the automation to **One-time per subscriber per year** (or whatever your cycle is) — AcelleMail tracks the per-subscriber last-run timestamp so it won't double-fire within the cycle window.

If you change the `subscription_expires_at` mid-cycle (e.g. customer upgrades and the date resets), the automation re-evaluates and may fire the reminder again from the new date. Test this carefully if your billing flow updates the field frequently.

**Multi-step birthday flow** (day-of + 3-day follow-up if no purchase):

```
Date relative on birthday → email "Happy birthday, here's 20% off"
  Wait 3 days
  Condition: did NOT use discount code
    → email "Your birthday discount expires in 24h"
```

The Wait step + condition step are standard automation primitives. Many senders see the highest birthday ROI on the *expiry reminder*, not the day-of email.

</details>

## Related articles

- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Advanced automation triggers and conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)
- [Post-purchase follow-up automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation)
- [Re-engagement campaigns — win back inactive subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# Complete DNS Setup for Email Sending — The 6 Records You Need

> Email deliverability runs on DNS. SPF, DKIM, DMARC, MX, custom-tracking-domain CNAME, BIMI — that's the full setup. This guide walks each in order with the exact records to publish, how AcelleMail verifies them, what each does.

- Canonical URL: https://acellesend.com/kb/articles/complete-dns-setup-for-email-sending
- Author: Lena Hart, Email Deliverability Editor
- Category: DNS & Domain Setup — SPF, DKIM, DMARC, BIMI, domain verification, MX records, Return-Path
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-13 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The 6-record full setup

Every robust email-sending domain needs:

| Record | Purpose | Required? |
|---|---|---|
| **SPF** (TXT) | Authorize sending IPs | Yes — base authentication |
| **DKIM** (TXT or CNAME) | Cryptographically sign emails | Yes — base authentication |
| **DMARC** (TXT) | Tell receivers what to do if SPF/DKIM fails | Yes — base authentication |
| **MX** (for return-path / bounce subdomain) | Route bounces to AcelleMail | If using custom envelope-sender |
| **Tracking domain CNAME** (CNAME) | Brand the click-redirect | Optional (recommended) |
| **BIMI** (TXT) | Show your logo in inbox | Optional (advanced) |

This guide walks each.
### Open the sending-domain detail

In AcelleMail's sidebar, **Sending → Sending domains**. The list shows every domain you've registered with status chips (Verified / Pending / Failed) and per-auth indicators:

![Sending domains list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/17-sending-domains-list.png)

Click into your domain row. The detail page surfaces exactly which DNS records to publish (TXT for SPF, CNAMEs for DKIM, TXT for DMARC) with copy-paste-ready values + current verification state per check:

![Sending-domain detail — DNS records + auth status](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/18-sending-domain-detail.png)

### Record 1: SPF (Sender Policy Framework)

Tells receiving servers which IPs are authorized to send from your domain.

Publish at the apex domain (`yourdomain.com`):

```
Type:  TXT
Name:  yourdomain.com  (root)
Value: v=spf1 include:amazonses.com include:_spf.mailgun.org -all
TTL:   3600
```

Includes:

- `include:amazonses.com` if using Amazon SES
- `include:_spf.mailgun.org` if using Mailgun
- `include:sendgrid.net` if using SendGrid
- `include:spf.example.com` for any other vendor (check their docs)

`-all` (hard fail) tells receivers to REJECT messages from any IP not listed. Use `~all` (soft fail) if you're still bringing new vendors online; switch to `-all` once production-stable.

See [SPF record deep dive](https://acellesend.com/kb/articles/spf-record-deep-dive) for the full reference + edge cases.

### Record 2: DKIM (DomainKeys Identified Mail)

Cryptographically signs your outgoing emails so receivers can verify authenticity.

AcelleMail generates the DKIM keypair when you add a sending domain. Per the verify-domain wizard:

```
Type:  CNAME
Name:  acellemail._domainkey.yourdomain.com
Value: acellemail._domainkey.acellemail.com  (or your AcelleMail-side selector)
TTL:   3600
```

The exact CNAME target varies per AcelleMail install — the sending-domain detail page shows yours specifically. Most installs use AcelleMail's auto-generated 1024-bit or 2048-bit keys.

See [DKIM key rotation playbook](https://acellesend.com/kb/articles/dkim-key-rotation-playbook) for rotation discipline.

### Record 3: DMARC (Domain-based Message Authentication, Reporting & Conformance)

Tells receivers what to do when SPF/DKIM fails for your domain. Plus collects aggregate reports.

Publish at `_dmarc` subdomain:

```
Type:  TXT
Name:  _dmarc.yourdomain.com
Value: v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com; ruf=mailto:dmarc-reports@yourdomain.com; fo=1; pct=100
TTL:   3600
```

Key parameters:

- `p=` — what to do with failed messages (`none` = monitor only, `quarantine` = junk folder, `reject` = bounce)
- `rua=` — where to send aggregate reports (typically `dmarc-reports@yourdomain.com`)
- `ruf=` — where to send forensic reports (per-failure detail; some receivers don't send these)
- `fo=` — what failure types to report (`1` = SPF or DKIM fail; `s` = SPF fail; `d` = DKIM fail; `0` = both fail)
- `pct=` — what percentage of failed messages to apply the policy to (use 100 for full enforcement; useful for staged rollout)

Staged rollout: start `p=none` for 30 days. Move to `p=quarantine` once SPF/DKIM pass rate >99%. Move to `p=reject` for hardest enforcement.

See [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration) for the staged plan.

### Record 4: MX for return-path / bounce subdomain (custom envelope-sender)

If you use a custom envelope-sender (e.g. `bounce.yourdomain.com`), AcelleMail needs an MX record so bounces route correctly:

```
Type:  MX
Name:  bounce.yourdomain.com
Value: feedback.acellemail.com  (or your install's bounce processor)
Priority: 10
TTL: 3600
```

Without this, bounce messages get rejected by the receiving server's MX lookup. AcelleMail's sending-domain detail page tells you the exact MX target.

For default vendor envelope-senders (e.g. SES's `bounces+xxx@amazonses.com`), the MX is on AWS's side — no action needed.

### Record 5: Custom tracking domain CNAME

If using a custom click-tracking domain (recommended for brand consistency):

```
Type:  CNAME
Name:  click.yourdomain.com
Value: track.acellemail.com  (or your install's tracking endpoint)
TTL:   3600
```

See [Custom tracking domain for click URLs](https://acellesend.com/kb/articles/custom-tracking-domain-for-click-urls) for the full setup.

### Record 6: BIMI (show your logo in inbox)

After DMARC enforcement is stable, add BIMI:

```
Type:  TXT
Name:  default._bimi.yourdomain.com
Value: v=BIMI1; l=https://yourdomain.com/bimi/logo.svg; a=https://yourdomain.com/bimi/vmc.pem
TTL:   3600
```

Required: DMARC at `p=quarantine` or `p=reject`. See [BIMI setup walkthrough](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail).

## Verify everything in AcelleMail

After all 6 records published, the sending-domain detail in AcelleMail shows green chips per auth check:
### The auth chips on the sending-server detail

Open Settings → Sending servers → click your active server. The toolbar shows the live SPF / DKIM / DMARC chip status:

![Sending server detail with auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

- **Green chips on all three** = receiving servers can confirm you're authorized to send from this domain.
- **Any chip red** = receiving servers immediately downgrade reputation. Click *Verify domain* in the toolbar to walk through the DNS-fix wizard.

Click *Verify domain* to re-check on demand. AcelleMail's dig-check runs against the receiving-server-side perspective (not your local cache), so propagation issues are detected accurately.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| All 4 auth chips Green except DMARC | DMARC record published but pass rate <99% | Check rua aggregate reports; identify failing source IPs |
| SPF chip Red after adding a new vendor | SPF lookup count exceeded 10 (DNS limit) | Combine includes or use SPF flattening (advanced) |
| DKIM chip Red despite CNAME published | DNS propagation pending OR CNAME pointing wrong target | `dig CNAME acellemail._domainkey.yourdomain.com` to verify |
| MX chip Red on bounce subdomain | MX not published or pointing wrong target | Check sending-domain detail page for exact MX target |
| BIMI logo not appearing in Gmail | VMC not configured | See [BIMI VMC cost walkthrough](https://acellesend.com/kb/articles/bimi-with-vmc-cost-walkthrough) |
| Different chip states on different campaigns | Per-campaign From: domain doesn't match the verified sending domain | Use consistent From: domain per sending-server config |

## Per-vendor SPF includes reference

| Vendor | SPF include |
|---|---|
| Amazon SES | `include:amazonses.com` |
| SendGrid | `include:sendgrid.net` |
| Mailgun | `include:_spf.mailgun.org` |
| Postmark | `include:spf.mtasv.net` |
| Sparkpost | `include:sparkpostmail.com` |
| Self-hosted Postfix | Your sending server IPs (`ip4:1.2.3.4`) |

Combine for multi-vendor:

```
v=spf1 include:amazonses.com include:_spf.mailgun.org include:sendgrid.net -all
```

DNS lookup limit: 10. Most senders fit comfortably.

<details>
<summary><strong>Advanced: SPF flattening, DKIM key length recommendations, DMARC ARC for forwarders</strong></summary>

**SPF flattening:**

If you have many includes (3+ vendors + corporate apps), you can hit the 10-lookup DNS limit. SPF flattening resolves all includes at publish time + republishes as a single record:

```
# Before:
v=spf1 include:amazonses.com include:_spf.mailgun.org include:sendgrid.net include:spf.mtasv.net -all
# 4 lookups; if any vendor's include has 2-3 sub-includes, you blow the cap

# After (flattened):
v=spf1 ip4:54.240.0.0/18 ip4:13.32.0.0/15 ip4:54.240.36.0/22 ... -all
```

Tools like ValiMail SPF + Easy SPF auto-flatten + re-publish daily (vendors change their IP ranges). Self-flatten manually = maintenance burden; tool is worth it for 4+ vendor stacks.

**DKIM key length:**

- 1024-bit: legacy; still works at all receivers
- 2048-bit: modern; recommended for new setups
- 4096-bit: maximum security; some DNS hosts don't allow >2048-bit TXT records

For DKIM-via-CNAME (AcelleMail default), the key length is managed at the AcelleMail side — typically 2048-bit.

**DMARC ARC (Authenticated Received Chain):**

For forwarding scenarios — when your message is forwarded by a recipient or middleman — the original SPF + DKIM can fail. ARC records the original auth state so the eventual receiver can trust the chain.

```
ARC-Authentication-Results: i=1; mx.google.com;
       dkim=pass header.i=@yourdomain.com header.s=acellemail header.b=...;
       spf=pass smtp.mailfrom=bounce@yourdomain.com;
       dmarc=pass header.from=yourdomain.com
ARC-Seal: i=1; a=rsa-sha256; t=...; ...
ARC-Message-Signature: ...
```

AcelleMail handles ARC signing automatically when forwarding scenarios are detected.

**Aggregate DMARC report parsing:**

DMARC `rua=` reports arrive at your inbox as XML files (.zip or .gz). Parse via:

- dmarcian.com (free tier)
- onDMARC by Red Sift (paid; full visualization)
- DMARCLY (budget; basic reports)
- Custom parser (open-source XML libraries)

Reports tell you which IPs sent from your domain and what auth state was — including unauthorized senders trying to spoof your domain.

**Multi-tenant DMARC for SaaS:**

If your platform sends on behalf of many customers:

```
Customer A:  _dmarc.customerA.com  → Customer's own DMARC policy
Customer B:  _dmarc.customerB.com  → Customer's own DMARC policy
```

Each customer manages their own DMARC. Your platform-side SaaS DMARC policy is separate (for emails sent FROM your own platform domain).

**Verification automation:**

```bash
# Daily script: verify all 6 records on all sending domains
domains=$(curl -sH "Authorization: Bearer $TOKEN" \
  "https://acellemail.com/api/v1/admin/sending-domains" | jq -r '.data[].name')

for domain in $domains; do
  spf=$(dig TXT $domain +short | grep -c "v=spf1")
  dkim=$(dig TXT acellemail._domainkey.$domain +short | grep -c "k=rsa")
  dmarc=$(dig TXT _dmarc.$domain +short | grep -c "v=DMARC1")
  bimi=$(dig TXT default._bimi.$domain +short | grep -c "v=BIMI1")

  echo "$domain: SPF=$spf DKIM=$dkim DMARC=$dmarc BIMI=$bimi"
done
```

Alert if any record drops to 0 (DNS host accidentally removed). DNS-host bugs do happen.

</details>

## Related articles

- [How to set up SPF, DKIM, and DMARC records](https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records)
- [SPF record deep dive](https://acellesend.com/kb/articles/spf-record-deep-dive)
- [DMARC enforcement migration](https://acellesend.com/kb/articles/dmarc-enforcement-migration)
- [Return-path and envelope-sender explained](https://acellesend.com/kb/articles/return-path-and-envelope-sender-explained)
- [Custom tracking domain for click URLs](https://acellesend.com/kb/articles/custom-tracking-domain-for-click-urls)
- [BIMI setup — show your logo in Gmail](https://acellesend.com/kb/articles/bimi-setup-show-your-logo-in-gmail)
- [DNS propagation explained](https://acellesend.com/kb/articles/dns-propagation-explained)

---

# A/B Report Deep Dive: Reading AcelleMail Test Results

> After your A/B test finishes, the A/B Report tab tells you the winner — but the real value is in the variant-level numbers. This guide walks through the report, the statistical-significance rules, and what to do for winner / inconclusive / loss verdicts.

- Canonical URL: https://acellesend.com/kb/articles/comparative-campaign-analysis-ab-test-results-deep-dive
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: reference · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-02-12 · Last updated: 2026-10-21
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Setting up the A/B test was the easy part (see [A/B Test Campaigns](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)). The hard part is **reading the result correctly** — knowing when "Variant A won by 23%" is signal vs noise, and what to test next based on the verdict.

This is the deep-dive on AcelleMail's A/B Report tab.

## Where the report lives

After the test emails have gone out and the wait you set in **Winner Rules** has passed, AcelleMail picks the winner automatically (unless you chose **Manual selection**). To open the report, click the campaign's name in the campaign list, or its **A/B Report** tab.

![A/B Report tab on a sent A/B test campaign showing: winner banner "Variant A (A) is the winner! Click rate improved by 23.2% over the runner-up. Winner sent to remaining 80% of the audience." Stat cards: 1,176 Delivered / 39.7% Avg. Open Rate / 4% Avg. Click Rate / 20 Bounces / 0 Unsubscribes. Delivery Funnel: Delivered 1,176 → Opens 467 (39.7%) → Clicks 47 (4%). End-to-end Conversion 2.46% with Click/Open 10.06%, Bounces 1.67%, Unsubscribes 0%, Total Recipients 1,912.](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/05-ab-report.png)

(Screenshot from [A/B Test Campaigns article](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates).)

The report has four tabs. After **A/B Report**, the label "All variants:" introduces three tabs that show every variant together:

| Tab | What's there |
|---|---|
| **A/B Report** | The winner banner, the totals, the delivery funnel, **Variant Details** with each variant side by side, **Statistical confidence**, **Engagement over time** and **Insights & Recommendations** |
| **Overall stats** | The standard campaign report, for all variants together |
| **Insights** | Delivery status and engagement for each subscriber |
| **Logs** | The delivery, bounce, open, click and unsubscribe logs |

The most useful part is **Variant Details** on the **A/B Report** tab. That is where you compare A and B directly.

## Reading the winner banner

The green winner banner is AcelleMail's verdict + the headline improvement. Three parts:

```
"Variant A (A) is the winner!
 Click rate improved by 23.2% over the runner-up.
 Winner sent to remaining 80% of the audience."
```

| Phrase | What to verify |
|---|---|
| **"Variant A is the winner"** | Match the variant label (A/B/C…) to what you actually tested. Notes in the setup tab tell you which variant had which subject/from-name. |
| **"improved by 23.2% over the runner-up"** | The percentage *delta* between winning and runner-up variants on the chosen metric. NOT the absolute rate. |
| **"Winner sent to remaining 80%"** | Confirms the rollout happened automatically (or didn't, if "Manual selection" was picked). |

## Is the delta statistically significant?

A "23.2% improvement" sounds convincing, but with small groups a difference that size is often just chance. AcelleMail does not check this before it picks the winner.

The **Statistical confidence** card further down the same report answers the question when the test has two variants. It runs a z-test on the rate your win condition uses and shows the **Z-score**, the **P-value**, the **Lift** and the **Sample size**. A p-value below 0.05 is the usual bar. The percentage the card calls "confident" is 1 minus the p-value, so read the p-value itself.

In the screenshot example, 1,176 emails were delivered, about 590 per variant, with 47 clicks in total. That is roughly 26 clicks against 21. A difference that size between groups of about 590 has a p-value of about 0.46, far above 0.05. **The 23.2% lift is not reliable.** Treat the result as a tie.

For a test with more than two variants, or one judged by orders, enter the numbers in a calculator such as Evan Miller's [chi-squared test](https://www.evanmiller.org/ab-testing/chi-squared.html). [Email A/B Testing Explained](https://acellesend.com/kb/articles/email-ab-testing-explained) shows how many contacts a test needs and how to read a p-value.

## Variant Details: the data that matters

This section of the **A/B Report** tab shows the variants side by side. For each variant, you see:

| Metric | What it means |
|---|---|
| Delivered | Emails accepted for delivery, out of the contacts assigned to the variant. Similar across variants when the split is even. |
| Opens | Contacts who opened (caveat: [Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp) inflates opens) |
| Clicks | Contacts who clicked (the more reliable measure) |
| Bounces | Should be similar across variants |
| Unsubscribes | If one variant has twice the unsubscribes, that variant put people off |
| Failed | Emails the sending server refused. Should be similar across variants |

**The 4 patterns you'll see:**

| Pattern | Reading |
|---|---|
| Variant A: higher opens AND higher clicks | Clear winner. Subject + content both worked. |
| Variant A: higher opens, similar clicks | Subject pulled more opens but content didn't capitalise. A is "better" if your KPI is opens; B is "as good" if your KPI is clicks. |
| Variant A: similar opens, higher clicks | Subject was tie; B's content/CTA underperformed. The subject didn't matter; the content did. |
| Variant A: lower opens, higher clicks | Counter-intuitive — A had a smaller, more engaged audience. Often happens with personalisation tests. |

## What to do per verdict

### Winner (clear, statistically significant)

✅ Promote the winning pattern. Update your subject-line / CTA / from-name standards to match.

**Next test:** vary a DIFFERENT element. If you tested subject lines this time, test the call to action next. (AcelleMail sends all variants at the same time, so compare send times across separate campaigns.)

### Inconclusive (not significant)

⚠️ The p-value is above 0.05, so the test could not tell the variants apart, even if the lift looks large. **Don't conclude anything.** The test did not have enough contacts for a difference of that size.

**Next test:** increase the test audience size (from 20% to 40%), OR test a more dramatically different variation.

### Loss (variant B significantly underperformed A)

✅ Also valuable! You learned what NOT to do. Cross-reference variant B's pattern against your "stuff that didn't work" notes for future reference.

**Next test:** test the next hypothesis — don't keep beating up variant B.

### Surprise (a variant you thought would lose actually won)

⚠️ The most valuable result. Audit WHY:

- Subject line surprised? Maybe length / formality / emoji preference shifted
- From-name surprised? Maybe personal-name-vs-brand norms shifted
- CTA surprised? Maybe your offer messaging needs an update

Surprises are where you learn the most about your audience.

## Statistical-significance worked example

**Setup:**
- Variant A subject: *"5 things every marketer should do this quarter"*
- Variant B subject: *"Q2 newsletter"*
- Audience: 10,000, 50/50 split
- Metric: click rate

**Result after 72h:**

| | Delivered | Opened | Clicked | CTR |
|---|---|---|---|---|
| Variant A | 4,820 | 1,690 (35.1%) | 145 (3.01%) | — |
| Variant B | 4,810 | 1,500 (31.2%) | 115 (2.39%) | — |

CTR delta: (3.01% − 2.39%) / 2.39% = **+25.9%** relative improvement for A.

At about 4,800 per variant, 145 clicks against 115 gives a p-value of about 0.06, which the card would show as about 94% "confident". That is just above the usual 0.05 bar, so the result is promising but not significant.

**Verdict: promising, not proven.** For this campaign, sending Variant A to the rest of the list was fine. Before you make long, descriptive subject lines your standard, repeat the test or run it with a larger audience.

## Common pitfalls

| Pitfall | What's wrong | Fix |
|---|---|---|
| **Calling a winner early** | Checking again and again and stopping when one variant looks ahead creates false winners, and the people who respond first may not behave like the rest of the list. | Set **Wait before deciding** before you send, and judge the result once, at the end. |
| **Testing 4 things at once** | Subject + sender + content + send time — you don't know which moved the needle. | One variable at a time. |
| **Re-testing the same idea again and again** | Once a result has been confirmed, more repeats teach you little. | Repeat a promising result once to confirm it, then move on to a different element. |
| **Ignoring unsubscribe rate** | A variant with higher clicks AND higher unsubs is poisoning long-term LTV. | Treat unsubscribe rate as a guard metric — never let the winner have 2x the unsubs. |
| **Manual-selection bias** | When picking manual winner, you choose the variant you predicted, regardless of data. | If using manual, document the prediction BEFORE seeing data; compare. |
| **Testing small changes on a small list** | Small groups can only detect very large differences. | Test bigger changes, send each variant to half of the whole list, or repeat the same kind of test over several campaigns. |

## What to log per test (a running playbook)

Maintain a doc tracking every A/B test. Each row:

| Field | Example |
|---|---|
| Date | 2026-05-19 |
| Campaign | Spring Sale 2026 |
| Variable tested | Subject line length |
| Variant A | "5 things every marketer..." (45 chars) |
| Variant B | "Q2 newsletter" (13 chars) |
| Winner metric | Click rate |
| Winner | A (3.01% vs 2.39%, +25.9%) |
| p-value | 0.06 (not significant) |
| Decision applied | Repeat the test before changing the standard |
| Re-test in | Q3 2026 |

After 10-20 tests, patterns emerge — *"our audience consistently prefers question-format subjects on Tuesdays."* That's the real long-term value.

## Related articles

- [Email A/B Testing Explained: How to Run a Test You Can Trust](https://acellesend.com/kb/articles/email-ab-testing-explained)
- [A/B Test Campaigns in AcelleMail (3-Tab Setup + Auto-Winner)](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Read the Campaign Report: Analytics Tour](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics)
- [Open Rates in the Age of Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp)
- [Email Copy That Converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)

---

# Data Encryption for Self-Hosted AcelleMail

> Self-hosting means your subscribers' data lives on your server, not a third party's — which puts encryption in your hands. Here's what actually matters: protecting data in transit, at rest, and in your backups, in plain terms, without a sysadmin wall.

- Canonical URL: https://acellesend.com/kb/articles/data-encryption-for-self-hosted-platforms
- Author: Daniel Reyes, Technical Editor
- Category: Security & Compliance — GDPR, CAN-SPAM, CASL, SSL/TLS, encryption, data privacy, audit
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-09 · Last updated: 2026-08-15
- Reading time: 4 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

When you self-host AcelleMail, your subscribers' personal data lives in **your** database on **your** server — not on a third-party SaaS. That's the privacy win of self-hosting, and it puts encryption decisions in your hands. This article explains, in plain terms, the three places encryption matters and what's actually worth doing — without turning into a sysadmin manual.

If you want the hands-on server commands, those live in your platform's install guide; this page is the *map* so you know which ones matter and why.

## The three layers

### 1. In transit — HTTPS on everything

Every admin login, subscriber export, and API call travels between a browser and your server. Encrypting that path with HTTPS (TLS) is the single most important, lowest-effort layer — without it, anyone on the network can read credentials and subscriber data in the clear.

Your install already sets up a free Let's Encrypt certificate, and AcelleMail can be told to *insist* on HTTPS (`FORCE_HTTPS=true` in `.env`). That's covered as Priority 1 of the [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — do it before you put real subscribers on.

### 2. App secrets — your `.env` and `APP_KEY`

AcelleMail keeps its secrets (database password, mail credentials) in the `.env` file, and a unique `APP_KEY` encrypts session data and other sensitive values the app stores. Two things follow from that:

- **Protect `.env`.** It should be readable by the web user and nobody else. (Hardening Priority 1.5.)
- **Never regenerate `APP_KEY` on a running install** — anything already encrypted with the old key becomes unreadable. It's set once at install and left alone.

You don't manage application-level field encryption yourself — the framework handles it via `APP_KEY`. Your job is just to keep that key and the `.env` file safe.

### 3. At rest — disk and backups

"At rest" means data sitting on disk: the database files, uploaded assets, and your backups. Two practical options, in order of how often they matter:

- **Encrypted backups (do this).** Backups are the copies most likely to leave your server — to S3, Backblaze, another machine. An off-site backup that isn't encrypted is your whole subscriber list sitting in someone else's storage. Encrypt backups before they leave the box. The [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) guide covers the backup routine; encrypting the archive is a step within it.
- **Full-disk / volume encryption (for regulated data).** If you handle health, financial, or other regulated personal data — or your own policy requires it — encrypting the whole volume the database sits on protects you if the physical disk or a cloud snapshot is ever exposed. Most cloud providers (AWS, DigitalOcean) offer volume encryption as a checkbox when you create the server; turning it on at creation time is far easier than retrofitting it later.

For a typical sender, **HTTPS + protected `.env` + encrypted off-site backups** is the right baseline. Full-disk encryption is the next tier up for regulated data.

## Where this isn't AcelleMail's job

Encryption at the OS and database level (TLS certificates, encrypted volumes, encrypted backup archives) is configured on the **server**, not inside AcelleMail's admin panel — there's no "encryption" screen in the app, and you should be wary of any guide that invents one. The app's part is the HTTPS enforcement and the `APP_KEY`; everything else is your hosting setup, which your install guide walks through.

## Common questions

**Does AcelleMail encrypt subscriber emails in the database?** Email addresses are stored so the app can send to them and de-duplicate — they aren't field-encrypted at rest by default (that would break search and sending). Protect them with disk/volume encryption and access control, not field-level encryption.

**Is HTTPS enough on its own?** It's the most important layer and the one to never skip, but it only protects data *in transit*. Pair it with protected secrets and encrypted backups for the full picture.

**Do I need this for GDPR?** Encryption is one of the "appropriate technical measures" regulators expect, but compliance is broader than encryption — see the [GDPR Compliance Guide](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing).

## Related articles

- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — HTTPS, `.env` protection, and the rest of the security baseline
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — the backup routine to encrypt
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — where encryption fits in compliance
- [Install AcelleMail on Ubuntu 24.04](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — TLS / HTTPS setup in context

---

# How to Write Email Copy That Converts — The Practical Framework

> You've got the list, the template, the schedule. What goes in the body? This guide is the practical copywriting framework — what to say, how to structure it, how to verify it lands. Less theory, more "type this, not that."

- Canonical URL: https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-06 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What "converts" means

Conversion isn't always a purchase. It's the action you want the recipient to take:

| Email type | Conversion |
|---|---|
| Newsletter | Click to read full article |
| Promotional | Click through to buy |
| Re-engagement | Update profile / re-confirm subscription |
| Welcome series | Complete onboarding step |
| Transactional | View / acknowledge the receipt |

Write for the conversion you want. Different conversions = different copy patterns.

## The framework: Hook · Value · CTA

The simplest body structure that works across email types:

```
[Subject line]            ← Already covered separately
[Preheader]               ← Already covered separately

[Hook]                    ← First 1-2 lines of body
  └─ Why should they keep reading?

[Value]                   ← Body
  └─ What does the recipient get from this?

[CTA]                     ← End
  └─ What action do you want them to take?
```

This is universal. Variations exist but every effective email maps onto these 3 components.

## Where copy lives in the AcelleMail builder

In the campaign builder's template step:

![Template editor](https://acellesend.com/plugins/acelle/landing/images/help/kb/first-campaign/05-template.png)

Each block (text, button, image-with-text) accepts copy. Edit by clicking the block + typing in the right-panel editor. Merge tags work in any text block via `{{ subscriber.X }}`.

## Hook patterns

The opening 1-2 lines decide if they keep reading.

| Pattern | When to use | Example |
|---|---|---|
| **Quick answer** | Educational content | "Most senders confuse XYZ. Here's the difference:" |
| **Specific number** | Industry / metric content | "We analyzed 1,847 campaigns. Only 12 hit >50% open rate. Here's what they had in common." |
| **Reference what they did** | Behavior-triggered | "You clicked on the pricing page last week. I think this is what you were looking for." |
| **Honest acknowledgment** | Promotional / sales | "Yes, this is another sale email. But this one is different because..." |
| **Future tense** | Anticipation | "Next Thursday, we ship the feature you've been asking about. Here's the early-access invitation." |
| **Empathetic** | Re-engagement / win-back | "We noticed you haven't opened our emails in a while. We've missed you." |

Avoid:

- Generic "Hi {{ subscriber.first_name }}!" with no follow-up
- "I hope this email finds you well." (bot-flavored)
- "We are excited to announce..." (yourself-first language)
- A long preamble before the actual hook

## Value patterns

The body delivers what the hook promised.

| Type | Structure |
|---|---|
| **Educational** | Problem → mechanism → solution → applicable steps |
| **Promotional** | Offer → benefit → social proof → urgency |
| **Newsletter** | 3-5 short items, each with a strong opening line + click-through |
| **Re-engagement** | Acknowledge gap → restate value → easy re-entry path |
| **Welcome** | Welcome → what to expect → first concrete step |

Common discipline:

- **One core idea per email**. If you have 3 unrelated ideas, send 3 separate emails.
- **Second-person language**. "You can save..." beats "We help customers save..."
- **Concrete > abstract**. "Save 20 minutes per week" beats "Save time."
- **Show, don't tell**. Numbers, screenshots (when appropriate), specific examples.

## CTA patterns

The action button or link at the end.

| Pattern | When | Example |
|---|---|---|
| **Direct command** | Single-action | "Get my 20% discount" |
| **First-person** | Aspirational | "I want to try this" |
| **Question-style** | Curiosity | "What does this look like in practice?" |
| **Numbered benefit** | Specific value | "Save $40 in the next 7 days" |
| **Time-bound** | Urgency | "Lock in pricing before Friday" |
| **No-friction** | Low-commitment | "Watch the 60-second demo" |

Avoid:

- "Click here" (the worst possible CTA)
- "Learn more" (too vague)
- "Submit" (sterile)
- 3+ CTAs in one email (dilutes focus)

## Verify in Preview

After typing the copy, click **Preview** in the builder:

![Desktop preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

Read it as if you were a recipient. Three quick checks:

1. **Front-load test** — Cover the bottom of the email. Does the hook + first paragraph still motivate clicking?
2. **Mobile test** — Toggle mobile view. Does the message survive 320px width?

![Mobile preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/02-mobile-preview.png)

3. **Subject + preheader alignment** — Does the body deliver what the subject promised? Mismatch = trust break.

## Before/after rewrites

**Before:**
```
Subject: Important update about our service

Dear valued customer,

We are excited to announce that we have made several improvements to our platform. Our team has been working hard to bring you the best experience possible. We believe these updates will enhance your overall satisfaction.

To learn more, please click here.

Best regards,
The Brand Team
```

**After:**
```
Subject: Your favorite feature got 3x faster

Hey {{ subscriber.first_name | default('there') }},

You know how the export takes 45 seconds for a 50k-row list? It's now 15.

That's it. That's the email.

The change is live in your account — no action needed.

Get back to your work →
```

The "after" version: shorter, specific, second-person, single CTA, no buzzwords.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| Open rate 25%+, click rate <2% | Hook strong, body weak | Read body cold; tighten the offer or specificity |
| Click rate strong, conversion low | Body promised something the landing page doesn't deliver | Align body + landing page; or rewrite the body |
| High unsubscribes | Frequency too high OR content off-target | Reduce cadence OR survey unsubscribers |
| "I never see your emails" complaint | Mobile rendering broken OR going to spam | Test mobile + check spam-folder routing |
| Click bunched at one specific link | Other links weak / under-optimized | Strengthen the under-performing CTAs |
| Open rate trending down over months | Subject formula getting stale | Rotate subject patterns; introduce variety |

## Anti-patterns to avoid

| Mistake | Why it hurts |
|---|---|
| Long greeting paragraph | Recipients scan; greeting wastes attention |
| "I" / "We" first words | Self-centered; weakens engagement |
| Multiple CTAs of equal weight | Decision fatigue; click-rate drops vs single dominant CTA |
| Pet-name / slang you don't earn ("hey friend!") | Reads as fake; hurts trust |
| Pdf attachment | Spam filter triggers; download friction |
| Big intro about you / your company | They subscribed; they know who you are |

<details>
<summary><strong>Advanced: writing for skimmers + A/B testing copy elements + segment-aware copy</strong></summary>

**Writing for skimmers:**

90%+ of recipients skim. Structure for the skimmer:

```
[Hook — 1 line]
[Bold short-line]   ← skimmer reads this
Detail paragraph   ← reader reads this

[Bold short-line]
Detail paragraph

[CTA button]
```

Make the bold short-lines self-sufficient — a skimmer who reads ONLY the bold lines should still understand the message.

See [Writing email for skimmers](https://acellesend.com/kb/articles/writing-email-for-skimmers) for the full skimmer-first pattern.

**A/B testing copy elements:**

Beyond subject lines, the highest-impact copy A/B tests:

| Test | Possible split |
|---|---|
| Opening line | Direct vs Curiosity vs Empathy |
| Body length | 80 words vs 300 words |
| CTA copy | "Get started" vs "Try it now" vs "Watch demo" |
| Image placement | Top hero vs inline vs none |
| Personalization depth | First-name only vs first-name + behavior |

Test ONE element per A/B (otherwise you can't attribute the winner). See [A/B testing email subject lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) for the testing flow.

**Segment-aware copy:**

Different segments = different copy. Engaged segment gets long-form deep dive; at-risk segment gets short-form re-engagement:

```
{% if 'engaged-30d' in subscriber.tags %}
  Long version with detailed walkthrough (5 paragraphs)
{% elsif 'at-risk' in subscriber.tags %}
  Short version: "We've made the export 3× faster. One-line, no action needed."
{% endif %}
```

Liquid templating in the builder supports this. Per-segment campaigns are easier (no Liquid; just separate sends) — choose based on operational complexity tolerance.

**Reading level discipline:**

Aim for 6th-8th grade reading level. Tools:

- Hemingway Editor — free
- Grammarly — clarity score
- Yoast Readability — built into WordPress

Most B2C copy reads better at 7th grade. B2B technical copy can go higher (10-12), but only if your audience is genuinely technical.

**Brand voice consistency:**

If your brand voice is "professional," all your emails should feel professional. If it's "irreverent," consistency wins over occasional polish.

Create a brand voice doc with examples:

```
DO say: "Cheers, Maria"
DON'T say: "Best regards, The Marketing Team"

DO say: "We screwed up. Here's how we're fixing it."
DON'T say: "We apologize for any inconvenience caused."
```

Every copy reviewer references the doc.

**Subject + body alignment:**

The biggest single copy fix:

- Subject promises X
- Body delivers X (clearly and quickly)

If the subject teases curiosity, the body satisfies that curiosity in the first paragraph. If the subject is direct, the body confirms specifics fast.

Promise-vs-delivery mismatch = trust loss = lower future open rates.

</details>

## Related articles

- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Writing email for skimmers](https://acellesend.com/kb/articles/writing-email-for-skimmers)
- [Email body structure — hook value CTA](https://acellesend.com/kb/articles/email-body-structure-hook-value-cta)
- [Re-engagement copy that doesn't feel desperate](https://acellesend.com/kb/articles/re-engagement-copy-that-doesnt-feel-desperate)
- [AI-assisted email copy without sounding AI](https://acellesend.com/kb/articles/ai-assisted-email-copy-without-sounding-ai)

---

# CAN-SPAM Compliance in AcelleMail: The 7-Point Checklist

> CAN-SPAM applies to every commercial email you send to a US recipient — fines reach $51,744 per violating message. This checklist walks the 7 rules, marks the ones AcelleMail enforces for you, and tells you exactly where to set the rest.

- Canonical URL: https://acellesend.com/kb/articles/can-spam-compliance-checklist
- Author: Daniel Reyes, Technical Editor
- Category: Security & Compliance — GDPR, CAN-SPAM, CASL, SSL/TLS, encryption, data privacy, audit
- Content type: guide · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-02-05 · Last updated: 2026-08-15
- Reading time: 4 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The CAN-SPAM Act (US, 2003) applies to every commercial email you send to a recipient in the United States — regardless of where you or your server is located. The Federal Trade Commission can fine you **up to $51,744 per non-compliant message**, and that ceiling is adjusted upward for inflation each January.

The good news: AcelleMail handles the technical half of compliance for you. The other half is editorial — honest subject lines, correct from-name, an accurate physical address on file. This is the buyer-side checklist.

## The 7 requirements, mapped to AcelleMail

| # | Rule | Who handles it | Where in AcelleMail |
|---|---|---|---|
| 1 | Don't use false or misleading header info | **You** | **Lists → [list] → Edit** — your `From name` and `From email` must accurately identify your business |
| 2 | Don't use deceptive subject lines | **You** | Subject is what you type in the **Campaigns** wizard — never use "Re:" / "Fwd:" / fake urgency |
| 3 | Identify the message as an advertisement | **You** | If you don't have express consent (e.g. cold outreach), say "Advertisement" in the body or footer |
| 4 | Tell recipients where you're located | **AcelleMail (after you fill it in)** | **Account → Contact** — fill in *Company info* + *Address* once; AcelleMail injects it into every campaign footer |
| 5 | Tell recipients how to opt out | **AcelleMail** | Every campaign automatically gets an unsubscribe link — you cannot turn it off |
| 6 | Honor opt-out requests within 10 business days | **AcelleMail** | Unsubscribed subscribers are suppressed immediately — they cannot be re-imported into the same list |
| 7 | Monitor what others do on your behalf | **You** | If you give an agency access to your AcelleMail account, you're still liable for their sends |

## Where to fill in your physical address

Go to **Account → Contact**. The page has two cards: **Company info** (legal name + display name) and **Address** (Address 1, Address 2, City, State, ZIP, Country). Save once — AcelleMail uses these values in the campaign footer for every list. If the address card is empty, the footer will fall back to your account email, which is **not** CAN-SPAM compliant.

A valid address is a **street address, PO Box, or commercial mailbox registered with the USPS**. A residential apartment is fine. A virtual mailbox (e.g. iPostal1, Earth Class Mail) is fine. A "we have offices in San Francisco" tagline is *not* — you need a specific deliverable address.

## What AcelleMail enforces automatically

- **Unsubscribe link** in every campaign footer. The template language `{UNSUBSCRIBE_URL}` resolves to a per-subscriber one-click link.
- **Suppression after unsubscribe.** Once a subscriber clicks unsubscribe, they're flagged `unsubscribed` on that list and excluded from all future sends. They can re-subscribe via your signup form if they choose, but you cannot re-import them.
- **List unsubscribe headers (`List-Unsubscribe` and `List-Unsubscribe-Post`).** AcelleMail adds the RFC 8058 one-click headers Gmail and Yahoo now require. Most modern clients render an "Unsubscribe" button in the message header from these.

## What you still need to watch

| Issue | Fix |
|---|---|
| Your **From name** is your CEO's first name only ("Sarah") | Change it to a business identifier — "Sarah at Awesome Solutions". From-line must identify the sender. |
| You're sending to people who never asked — and your footer doesn't say "Advertisement" | Add an "Advertisement" or "Promotional message" line in the footer area of your template, OR get explicit opt-in first. |
| You bought a list and want to email it | CAN-SPAM allows it (it's "opt-out" not "opt-in"), but Gmail/Yahoo deliverability will tank. Don't. See [Email List Hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability). |
| You set up a different unsubscribe page in your template | Don't — AcelleMail's built-in `{UNSUBSCRIBE_URL}` is the only one that triggers automatic suppression. |

## Common issues

| What you see | What to do |
|---|---|
| Campaign footer shows your account email instead of an address | Go to **Account → Contact**, fill in **Address 1 / City / Country**, save. The next send will use it. |
| Subscriber complains they unsubscribed but got another email | Verify they clicked the link in the footer (not a custom "stop" link in your body). If they did, check **Lists → [list] → Subscribers** and confirm their status is `unsubscribed`. If not, contact support — auto-suppression should be instant. |
| You want to re-import someone who previously unsubscribed | Don't. CAN-SPAM forbids it without fresh consent. Have them sign up again via your form. |

## What to do after

1. Open **Account → Contact** and confirm both **Company info** and **Address** cards are filled in.
2. Open one of your last sent campaigns and scroll to the footer — confirm the address renders, and the unsubscribe link works.
3. Read [Double Opt-In vs Single Opt-In](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose) — double opt-in dramatically reduces CAN-SPAM exposure even though it isn't legally required.

## Related articles

- [CASL: Canadian Anti-Spam Requirements](https://acellesend.com/kb/articles/casl-canadian-anti-spam-requirements)
- [GDPR Compliance for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing)
- [Double Opt-In vs Single Opt-In: Which to Choose](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose)
- [Email List Hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)

---

# Double Opt-In in AcelleMail: Toggle, Template, Decision Guide

> AcelleMail has one Double opt-in toggle per list. This walkthrough shows exactly where the toggle lives, what the confirmation email looks like, and a buyer-style decision guide for when to turn it on.

- Canonical URL: https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: reference · Difficulty: beginner
- Tags: Double Opt In
- Published: 2026-02-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail makes the single-vs-double opt-in choice into one toggle, per list. This article shows you exactly where the toggle lives, what changes when you flip it on, and a buyer-focused decision guide so you can make the call without wading through compliance theory first.

**Short answer for most readers:** turn it on, especially if you have any EU / UK / Canadian subscribers or if you import a list from outside AcelleMail. Skip ahead to *Step 1* for the click path.

## What changes when you flip the toggle

| With **Double opt-in** OFF (single) | With **Double opt-in** ON |
|---|---|
| Subscriber's status becomes **Subscribed** the moment they submit the signup form | Status becomes **Unconfirmed** on submission; flips to **Subscribed** only after they click the confirmation link |
| They start receiving campaigns immediately | They receive *only* the confirmation email until they click |
| You have no per-subscriber timestamped proof of consent | The confirmation click is a timestamped, IP-logged consent event (legible under GDPR / CASL / PECR) |
| Typo emails and bots silently inflate your list | Typo emails and bots never confirm, so they never enter the marketing list |

Nothing else changes. The Subscribed status flow, segments, and campaigns are identical — DOI just gates *when* a subscriber becomes Subscribed.

## Step 1 — Open the list's Settings tab

In the left sidebar click **Lists**, open the list you want to configure, and switch to the **Settings** tab in the list's top navigation. The URL is `/rui/lists/<list-uid>/edit`.

![List Settings page for "Newsletter Subscribers (Awesome Solutions)" showing the List details card (List name, From name "Gustave from Awesome Solutions", From email) and a Subscription settings card with three toggles: Double opt-in (ON — teal), Send welcome email (OFF), Unsubscribe notification (OFF). Each toggle has help text. Cancel + Save settings buttons at bottom.](https://acellesend.com/plugins/acelle/landing/images/help/kb/double-optin/01-doi-toggle.png)

Scroll to the **Subscription settings** card. It has exactly three toggles — DOI is the top one. Each has a one-line description from AcelleMail itself:

- **Double opt-in** — *"Require subscribers to confirm their email before being added."*
- **Send welcome email** — *"Automatically send a welcome email when someone subscribes."*
- **Unsubscribe notification** — *"Get notified by email when someone unsubscribes from this list."*

Flip **Double opt-in** to ON. Click **Save settings** at the bottom right.

> **The setting is per-list.** Every list has its own DOI toggle. A common mistake is to enable DOI on your main marketing list but forget to enable it on a smaller cold-traffic list you imported separately.

## Step 2 — Customise the confirmation email

When DOI is on, AcelleMail sends a confirmation email automatically. By default it uses a clean built-in template, but you almost certainly want to brand it.

Switch to the **Forms / Pages** tab on the same list (top navigation). Find the **Confirmation email** card.

![Confirmation email template page with the heading "Confirmation email" + Email badge, description "Email sent to confirm the subscriber's email address (double opt-in)", last edited timestamp, list name shown, and three action buttons: Open builder (teal), Use HTML editor, Preview — plus a "Reset to default" link on the right](https://acellesend.com/plugins/acelle/landing/images/help/kb/double-optin/02-confirmation-email.png)

Three buttons for three workflows:

- **Open builder** — visual drag-and-drop editor. Same builder you use for campaigns; pick a layout, drop in your brand colours, write the subject + body. Best default.
- **Use HTML editor** — paste your own HTML. Use this only if you have a hand-coded transactional template.
- **Preview** — see the rendered version. Always do this before saving — the confirmation email is the first email your new subscriber sees from you.

A **Reset to default** link is on the right if you wreck the template — it restores AcelleMail's built-in version with the working confirmation link.

**The one variable that MUST be present in the body:** the confirmation link/button. AcelleMail's visual builder includes it by default as a *Confirm Subscription* button. If you switch to HTML and write your own template, you need the `{CONFIRM_URL}` placeholder somewhere clickable — otherwise the subscriber has nothing to click and never confirms.

> **Keep the email simple.** One sentence + one button. The confirmation email is not a welcome email — its single job is to get the click. Save your brand story, lead-magnet delivery, and onboarding tour for the welcome email (which fires after the confirmation, if you also enabled *Send welcome email* in Step 1).

## Step 3 — Watch pending vs subscribed states

After DOI is on, the **Subscribers** tab of your list shows everyone, but with different statuses. Switch to the Subscribers tab and use the **All statuses** filter at the top.

![Subscribers list view with "All statuses" filter dropdown visible at the top alongside All verification, Columns, and a search box. Subscriber rows show a Status column with "Blacklisted" and "Subscribed" badges visible in seeded data.](https://acellesend.com/plugins/acelle/landing/images/help/kb/double-optin/03-subscribers-statuses.png)

The Status filter gives you these states:

| Status | What it means | Reaches campaigns? |
|---|---|---|
| **Subscribed** | Confirmed and active | Yes |
| **Unconfirmed** | Signed up but hasn't clicked the link yet | **No** — only the confirmation email fires |
| **Unsubscribed** | Clicked unsubscribe at some point | No |
| **Blacklisted** | On your blacklist (manually added or auto-bounced) | No |

When auditing list quality, regularly filter for **Unconfirmed** subscribers older than 7 days. Either re-send the confirmation (via a campaign segment), or delete them — they're stale signups that won't ever engage.

## Decision guide — Should you turn DOI on?

| Situation | Recommendation | Why |
|---|---|---|
| **Any EU / UK / Canadian subscribers** | **ON.** Always. | GDPR / PECR / CASL need a timestamped consent event. The confirmation click is exactly that. |
| **Brand new list with no prior emails sent** | **ON.** | One-time list-quality win you'll never get back. New programs are most vulnerable to bots and typos. |
| **You just imported a cold list (>6 months old)** | **ON for that list specifically**, before any send. | Filters out dead addresses and treats it as a fresh re-permission pass. |
| **High-volume e-commerce, US-only, with existing customer relationships** | **OFF acceptable**, but pair with email verification | CAN-SPAM doesn't require prior opt-in for an existing-customer relationship; you still benefit from list quality. |
| **High-intent landing page with a lead magnet** | **ON.** | The lead-magnet email IS your confirmation gate — they want the magnet enough to click. No friction lost. |
| **You're trying to recover deliverability** | **ON immediately.** | Mailbox providers reward lists with confirmation events; bounces drop within 2 weeks of switching. |
| **You're paid per acquired subscriber by a partner** | Check the contract | Some lead-gen contracts only count *confirmed* subscribers — DOI is mandatory. |

When in doubt: **turn it on**. The list will grow 10–30% slower, but the 70–90% who confirm are the engaged subscribers who'd have driven your campaign metrics anyway.

## Common issues

| What you see | What to do |
|---|---|
| New signups land as **Subscribed** even though you turned DOI on | Confirm the toggle is on for the **correct list** (it's per-list, not global). Then submit a fresh test signup — old signups keep their original status. |
| Confirmation email is going to spam | Open the email's **Settings** in Forms / Pages and confirm the **From email** matches a verified sending domain. Bare-IP / unverified-from is the #1 reason confirmation emails get filtered. |
| Subscribers click confirmation but nothing happens | The confirmation link in your custom HTML template is missing the `{CONFIRM_URL}` placeholder. Click *Reset to default* in the template editor, then re-customise without removing the placeholder. |
| Pending count grows but never converts | Send a one-off campaign to the **Unconfirmed** segment 24–48h after signup with a friendlier subject line. Some confirmations get caught in spam — a follow-up nudge often unlocks them. |
| You imported a CSV — now everyone's stuck on Unconfirmed | Imports use the list's current DOI setting. If you didn't intend to require confirmation for an import: open *Import settings* and choose "Add as Subscribed" — or temporarily flip DOI off, import, then flip back on. |

## After turning DOI on

- **Re-test your signup form** end-to-end (incognito tab, real inbox). The confirmation email must arrive within 1 minute. If it doesn't, your sending server / sending domain has a deliverability problem you'd rather find now than during a campaign.
- **Audit Unconfirmed subscribers monthly.** Anything over 30 days unconfirmed is dead weight — delete or re-send confirmation.
- **Pair DOI with a welcome email.** Once they confirm, they expect something. Build the welcome series in **Automation** triggered on *Subscriber confirms*.

## Related articles

- [Build a Signup Form and Embed It on Your Site](https://acellesend.com/kb/articles/creating-effective-signup-forms-and-landing-pages)
- [Import Contacts from CSV: A 3-Step Walkthrough](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Email List Hygiene: Clean Your List for Better Deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Building a Welcome Email Series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)

---

# Segments: Slice Your List Into Audiences That Convert

> Build dynamic subscriber groups in AcelleMail using list fields, tags, activities, and email-verification results. Walk-through with real screenshots of the segment builder.

- Canonical URL: https://acellesend.com/kb/articles/advanced-segmentation-strategies
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: List Management — Segments, tags, import/export, double opt-in, verification, hygiene
- Content type: tutorial · Difficulty: intermediate
- Tags: Segmentation
- Published: 2026-02-04 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

A segment is a saved filter over a mail list. When you create a campaign and pick a segment as the audience, AcelleMail re-evaluates the conditions at send time — so a segment defined as *"opened a campaign in the last 30 days"* automatically expands and contracts as subscribers engage.

This walkthrough shows you exactly which buttons to click in the AcelleMail segment builder, what each condition row actually looks like, and a handful of segment recipes you can build right away.

## Before you start

- **You need at least one mail list with subscribers.** Segments are scoped to a list — there's no "global" segment across lists.
- **Tags and custom fields are the main raw material.** If your subscribers don't have tags or custom fields yet, your segments can only use Email + activities (which are still useful, but limited).
- **Segments are dynamic by default.** Every condition re-runs each time a campaign or automation reads the segment. There's no "static snapshot" mode — if you need that, copy the matched subscribers into a separate list.

## Step 1 — Open the Segments tab inside your list

From the sidebar click **Lists**, open the list you want to segment, and switch to the **Segments** tab in the list's top navigation. The URL is `/rui/lists/<list-uid>/segments`.

![AcelleMail Segments overview page showing 3 segments (Active subscribers, Engaged openers, Recent signups), three stats cards (Total segments, List subscribers, Largest segment), and a Segment sizes bar chart](https://acellesend.com/plugins/acelle/landing/images/help/kb/segments/01-segments-overview.png)

You get a dashboard with three things in it:

- **Three stat cards** — Total segments, total list subscribers, and what % of the list the largest segment covers.
- **Segment sizes chart** — a horizontal bar comparing every segment's reach. Useful for spotting segments that have drifted to near-empty (probably stale conditions) or near-100% (probably too broad to be useful).
- **Segment table** — name, match rules, and matching subscriber count. Each row's *Match rules* column previews the conditions inline (e.g. *"Sign-up date · is after · 2026-04-14"*).

Click the green **+ New segment** button in the top right.

## Step 2 — Create the segment skeleton

You land on a clean **Create a new segment** form.

![Empty Create segment form with Segment name field showing placeholder "e.g., Active subscribers, US customers...", Match subscribers who meet dropdown defaulted to All, and a Conditions section with an "+ Add condition" button](https://acellesend.com/plugins/acelle/landing/images/help/kb/segments/02-create-empty.png)

Two decisions to make before you add any rules:

- **Segment name.** Use a name a campaign creator will recognise three months from now (*"Last 30-day openers — US"*) rather than *"Segment 4"*. The placeholder hints at the right shape: *e.g., Active subscribers, US customers…*
- **Match subscribers who meet → `All` or `Any`.** *All* means a subscriber has to satisfy **every** condition (AND logic). *Any* means **at least one** condition is enough (OR logic). Most useful segments are AND.

![Segment name typed in as "KB demo — High-value engaged", Match subscribers who meet still on All, Conditions section still empty](https://acellesend.com/plugins/acelle/landing/images/help/kb/segments/03-name-typed.png)

## Step 3 — Add your first condition

Click **+ Add condition** and a row appears with three controls side-by-side: **Field · Operator · Value**.

![A condition row added beneath the Conditions heading: first dropdown showing "First name", second dropdown showing "equal", a value text input on the right, and a red X to remove the row](https://acellesend.com/plugins/acelle/landing/images/help/kb/segments/04-add-condition.png)

The Field dropdown is where AcelleMail's segmentation shines. It's grouped into four categories you can mix freely:

![Field dropdown expanded showing the category groupings — List fields is the first group with options like "Email" visible underneath](https://acellesend.com/plugins/acelle/landing/images/help/kb/segments/05-field-categories.png)

- **List fields** — every custom field defined on this list (Email, First name, Last name, plus whatever you've added in *Manage list fields*). Operators include *equal / not equal / contains / starts with / is empty / greater than / less than*.
- **Email verification** — *Verification result*. Lets you target only subscribers Athena marked as Deliverable / Risky / Undeliverable.
- **Other** — *Tag*. Use this for VIP / customer / trial / churn-risk groupings you've tagged manually or via automation.
- **Activities** — *Last opened email* and *Last link click*. These are date-based — operators are *greater than (days)* and *less than (days)*. *"Last opened email · less than · 7"* = subscribers who opened in the last week.

## Step 4 — Save and verify the size

Hit **Save segment** when you've added the conditions you want. You can stack as many condition rows as you need — AcelleMail combines them with the AND/OR logic from Step 2.

After saving you're returned to the Segments tab, but the real proof is the **matching subscribers** view — click the segment's name (or its *Subscribers* link) to see exactly who AcelleMail thinks belongs in the segment right now.

![Segment subscribers page for "Active subscribers" showing 1,711 matching subscribers in a table with email, name, status, and created date columns, plus filters at top](https://acellesend.com/plugins/acelle/landing/images/help/kb/segments/06-matched-subscribers.png)

What to check on this page:

- **The count at the top** matches your expectations within an order of magnitude. If you built a segment that should be "last 30-day openers" and you see 0 matches, your operator value is probably wrong (days, not a date).
- **Status column** — segments include subscribers regardless of subscription state by default. If you don't want unsubscribed / blacklisted contacts, add a *Status · equal · Subscribed* condition.
- **Click into a few rows** to sanity-check that the included subscribers actually match what you intended.

## Step 5 — Use the segment in a campaign or automation

A segment on its own does nothing. Hook it up:

- **Campaigns:** in the campaign wizard's *Recipients* step, pick the list, then under *Segment* pick the segment you just built. AcelleMail will only send to matching subscribers.
- **Automations:** when you build a workflow in **Automation**, you can use a segment as both an entry condition (*subscriber enters this segment*) and a goal (*subscriber leaves this segment*).

## Segment recipes worth stealing

Here are 5 segments most lists benefit from. Build them once, reuse them across campaigns.

| Segment | Conditions (Match All) |
|---|---|
| **Engaged in last 30 days** | *Last opened email · less than (days) · 30* |
| **At-risk (lapsing)** | *Last opened email · greater than (days) · 60* AND *Last opened email · less than (days) · 120* |
| **Cold contacts to verify before next send** | *Last opened email · greater than (days) · 120* AND *Verification result · not equal · Deliverable* |
| **High-value customers** | *Tag · equal · customer* AND *Tag · equal · vip* (set Match: Any if you want either tag) |
| **New + un-onboarded** | *Sign-up date · greater than · today-14d* AND *Tag · not equal · welcomed* |

Build these in your highest-traffic list first; the same naming convention copies easily across lists.

## Common issues

| What you see | What to do |
|---|---|
| Segment shows **0 matching subscribers** but you expected hundreds | Most common cause: a date operator using an absolute date instead of a day-offset. *Last opened email · less than · 2026-01-01* won't work — switch operator to *less than (days)* and use a number. |
| Saved segment isn't appearing in the campaign builder | The campaign wizard's *Recipients* step only lists segments **from the list you picked**. Confirm you're inside the right list. |
| The matching count differs every time you load the page | That's correct — segments are dynamic and re-evaluated on every read. New signups, new tags, new opens all change the size. |
| You want to "preview" the segment without saving | Save it with a name like *"Draft — testing X"* and inspect the *Subscribers* tab. Delete the segment if it doesn't pan out. AcelleMail has no separate dry-run mode. |
| Field dropdown is missing a custom field you know exists | Open **Manage list fields** in the list's top nav and confirm the field is on **this** list — list fields aren't shared between lists. |

## After your segment is live

- **Track the size over time.** The Segments overview chart is the fastest way to spot a segment that's drifting empty (probably stale conditions) or ballooning (probably too broad).
- **Re-use through tags.** If a segment's conditions are complicated, build an automation that tags matching subscribers with a stable tag (*"high-value"*) — then use `Tag · equal · high-value` in your campaign segments instead. Faster + composable.
- **Don't overfit.** 3–5 well-named segments you actually send to beat 50 clever ones you forget exist.

## Related articles

- [Import Contacts from CSV: A 3-Step Walkthrough](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Custom Fields and Personalization Tags](https://acellesend.com/kb/articles/how-to-add-custom-fields-to-your-list)
- [Email List Hygiene: Clean Your List for Better Deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Re-engagement Campaigns: Win Back Inactive Subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)

---

# Read the Campaign Report: AcelleMail Analytics Tour

> AcelleMail's campaign report has 6 tabs (Overview / Insights / Links / Map / Sending logs / Email review) and 5 headline metrics. This walkthrough shows where each metric lives, what 'good' looks like, and what to do when a number's bad.

- Canonical URL: https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: guide · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-02-02 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Every campaign you send generates a report with 6 tabs and a dozen numbers. Most of the time you only need 4 of those numbers — but knowing where the other 8 live (and why they matter) is the difference between *"the campaign worked"* and *"the campaign got opens but no clicks, here's the fix."*

This is the orientation tour: where every metric in the AcelleMail campaign report lives, what *good* looks like, and what to do when a number's off.

## Open the report

After a campaign sends, click its name from the **Campaigns** index. You land on the **Overview** tab, which is the right place to start for every campaign.

![Campaign report Overview tab for "Holiday Edition 35" with Sent badge, "Sent 4 days ago to 14,794 recipients" subtitle, and tab navigation: Overview (active) · Insights · Links · Map · Sending logs · Email review. Below: a green "Solid performance" insight banner ("Your open rate of 24% is on track. Consider A/B testing subject lines to push it higher.") followed by 4 stat cards (RECIPIENTS 14,794 · DELIVERED 3,838 (25.94%) · OPENED 931 (24.26%) · CLICKED 146 (3.80%)). Below the cards: a Statistics breakdown bar chart on the left and a Rate summary card on the right showing Opened 24.26% / Did not open 75.74% / Clicked 3.80% / Unsubscribed 0.00% / Bounced 0.00%.](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/01-campaign-overview.png)

The 6 tabs in this report:

| Tab | What's there |
|---|---|
| **Overview** | The 4 headline numbers + statistics breakdown + rate summary — read this first |
| **Insights** | AI-generated analysis ("subject worked but CTA underperformed") |
| **Links** | Per-link click breakdown — which CTA actually drove the clicks |
| **Map** | Geographic distribution of opens and clicks |
| **Sending logs** | Per-recipient delivery status (sent / failed / bounced) |
| **Email review** | The actual email HTML as subscribers received it |

Most decisions are made from **Overview** + **Links**. Use the others to drill down.

## The 4 headline numbers (Overview tab)

The stat cards are the order metrics matter in. Read them left-to-right.

### 1. RECIPIENTS

The size of the audience the campaign was sent to. Pulled from your list + segment + any exclusions.

> Use this to sanity-check the next number. If you sent to 15,000 but only 3,800 are "delivered," something's wrong with delivery (not engagement).

### 2. DELIVERED

How many emails actually reached an inbox (subscriber's mail server accepted the message). The percentage shown — *"3,838 (25.94%)"* — is delivered as a share of recipients.

| Delivered % | Diagnosis |
|---|---|
| **>97%** | Excellent — list is clean, sender reputation healthy |
| **92-97%** | Normal — a small portion bounces on every list |
| **80-92%** | Warning — your list is going stale OR you've hit a deliverability problem |
| **<80%** | Crisis — STOP sending, run [list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability), check sending domain auth |

> **Why "Holiday Edition 35" shows 25.94% delivered?** Because it was sent to 14,794 recipients but only 3,838 are "delivered" — the rest are still being processed, or it's a partial send. For a finished campaign you'd expect 92%+. Always read the campaign's "Last updated" timestamp to know if numbers are final.

### 3. OPENED

Unique opens — how many recipients triggered AcelleMail's open-tracking pixel by loading images. The percentage is opens as a share of **delivered** (not recipients).

| Open rate | Status |
|---|---|
| **>30%** | Excellent — strong subject line + relevant audience |
| **20-30%** | Good — solid program |
| **15-20%** | Average — room to improve subject + sender name |
| **<15%** | Poor — audit subject, sender reputation, list freshness |

> **Caveat: Apple Mail Privacy Protection inflates this number** for ~30-45% of subscribers. See [Open rates in the age of Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp). For most modern lists, the "real" open rate is 30-50% lower than what the dashboard shows.

### 4. CLICKED

Unique clicks — recipients who clicked at least one link in the email. The most reliable engagement signal because it requires real human action (not just image-loading).

| Click rate (CTR) | Status |
|---|---|
| **>5%** | Excellent — content + CTA pulling together |
| **2-5%** | Good — typical for well-targeted lists |
| **1-2%** | Average — CTAs probably need work |
| **<1%** | Poor — content or audience mismatch |

Click rate is the metric to optimise most heavily. It's the proxy for *"did this campaign cause action?"*

## The Rate summary (Overview tab right side)

The right-side panel restates the headline numbers as **percentages with absolute counts in parentheses**:

| Row | What it means |
|---|---|
| **Opened** | Same as Opened stat card — share of delivered who loaded images |
| **Did not open** | The complement — recipients who delivered but never opened |
| **Clicked** | Same as Clicked stat card — share of opens (not delivered!) who clicked |
| **Unsubscribed** | Share of delivered who unsubscribed from this campaign |
| **Bounced** | Share of recipients whose delivery failed |

> **Two important nuances:**
> 1. The *Clicked* row shows **clicks as % of opens** (the click-to-open rate / CTOR), not % of recipients. CTOR is a better measure of content quality than CTR because it isolates content from subject-line effect.
> 2. The *Unsubscribed* and *Bounced* rows are absolute — anything above 0.5% on either is a warning.

**Healthy targets:**

| Row | Healthy | Warning |
|---|---|---|
| Opened (as % delivered) | 20-40% | <15% |
| Clicked (CTOR — % of opens) | 10-25% | <5% |
| Unsubscribed | <0.5% | >1% |
| Bounced | <2% | >5% |

## Statistics breakdown chart (Overview tab left side)

The bar chart breaks every status into absolute counts:

- **Delivered** — top bar, largest
- **Opened** — middle, smaller (subset of delivered)
- **Clicked** — small (subset of opened)
- **Bounced** / **Failed** / **Reported as spam** — bottom rows, should all be near zero

The bars give you a visual sanity check: opens should be 20-40% of delivered; clicks should be 5-20% of opens. Anything dramatically off says something's wrong upstream.

## The "Insight banner" — AcelleMail's quick read

Above the stat cards, AcelleMail shows a one-line insight banner. In the screenshot above:

> *"Solid performance — Your open rate of 24% is on track. Consider A/B testing subject lines to push it higher."*

The banner pulls from the actual numbers and recommends the highest-leverage next action:

| Banner verdict | Typical recommendation |
|---|---|
| **Strong engagement** | "Try stronger CTAs to lift clicks" |
| **Solid performance** | "A/B test subject lines" |
| **Mixed results** | "Audit list hygiene OR re-segment audience" |
| **Underperformed** | "Run re-engagement first, then re-send to remaining" |

It's not a substitute for thinking, but for a quick read at the top of the report it's accurate.

## When to drill into each other tab

| If you want to know... | Open this tab |
|---|---|
| "Which CTA worked best?" | **Links** — per-link clicks ranked |
| "Where geographically did opens/clicks come from?" | **Map** |
| "Did the email actually get delivered to my own inbox?" | **Sending logs** (search your email) |
| "What did the email look like in the wild?" | **Email review** |
| "Should I follow up on this campaign?" | **Insights** (read AcelleMail's analysis) |

Each tab has its own [deep-dive article](https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout) — start with Overview, drill in only when a specific number raises a question.

## What good looks like overall

A "healthy" campaign report (well-built audience, decent content, no deliverability issues):

| Metric | Healthy benchmark |
|---|---|
| Delivered | >97% of recipients |
| Open rate | 20-30% (or higher; inflated by MPP) |
| Click rate | 2-5% of delivered |
| Click-to-open rate (CTOR) | 10-15% |
| Unsubscribe rate | <0.5% |
| Bounce rate | <2% |
| Spam complaint rate | <0.1% |

Hit these consistently and your sender reputation stays healthy, which keeps your inbox placement strong, which compounds across every future campaign.

## When numbers are bad — what to do

| Symptom | Likely cause | Fix |
|---|---|---|
| **Low delivered %** | List hygiene problem OR sending-domain auth issue | Run [list hygiene](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability); audit SPF/DKIM on your sending domain |
| **Low open rate** (and not just MPP) | Weak subject, wrong send time, sender-name issue | A/B test the [subject line + from name](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) |
| **High open / low CTR** | Subject promised something content didn't deliver, OR CTA is buried | Re-write body to lead with the promised value; bigger / brighter button |
| **High bounce rate** | Old list, imports without verification, OR sending-server problem | Run [email verification](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability) on the list before next send |
| **High unsubscribe rate** | Frequency too high, content irrelevant, OR list bought rather than opted-in | Reduce cadence, segment, or run [re-engagement](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers) |
| **High spam-complaint rate** | Buying lists is the #1 cause — even one purchased segment poisons the whole list | Immediately suppress the offending segment; future sends slow-warm only |

## Common questions

| Question | Answer |
|---|---|
| Why does the dashboard show 14,794 recipients but only 3,838 delivered? | Either the campaign is mid-send (read the "Last updated" timestamp), OR there's a deliverability problem. Re-check in 1 hour. |
| My open rate suddenly dropped from 30% to 15% — what happened? | Most commonly: sender reputation took a hit (a recent bad send), OR a major ISP changed how they count opens (this happens). Check the previous 3 campaigns' rates to spot the trend. |
| Why is CTR ≠ Clicked-card-percentage? | CTR can be calculated as clicks / delivered OR clicks / opens (CTOR). AcelleMail's *Clicked* stat card uses clicks / delivered. The *Rate summary* "Clicked" row uses clicks / opens. They're both valid — just know which is which. |
| The "Opened" map shows 931 opens but the stat card says 931. Where's the discrepancy I'd expect? | No discrepancy here — both reflect unique opens. The map plots their geographic locations; the card counts them. |

## Related articles

- [Understanding Email Open Rates in the Age of Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp)
- [Click Map Analysis: Optimise Your Email Layout](https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout)
- [Setting Up UTM Parameters for Campaign Tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking)
- [Email ROI Calculation](https://acellesend.com/kb/articles/email-roi-calculation-measuring-campaign-profitability)

---

# Open Rates + Apple MPP: What AcelleMail's Numbers Actually Mean

> Apple Mail Privacy Protection inflates 30-45% of your "opens" by pre-fetching pixels. This guide shows how to spot MPP inflation in AcelleMail (open-log IPs), what to measure instead, and how to adjust your A/B tests + reporting.

- Canonical URL: https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: guide · Difficulty: intermediate
- Tags: Open Rates, Analytics, Acellemail
- Published: 2026-01-31 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

Your AcelleMail open-rate number is wrong. Not because AcelleMail is broken — because **Apple Mail Privacy Protection (MPP)** pre-fetches the tracking pixel that every ESP uses to record opens. Every email delivered to an MPP-enabled inbox registers as "opened" the moment Apple's proxy fetches it, regardless of whether a human ever read it.

This article shows how to spot MPP inflation in AcelleMail's reports, what an "open" actually means now, and which metrics to optimise instead.

## What MPP does (and why it kills open tracking)

Since iOS 15 (September 2021), Apple's mail apps automatically:

1. **Pre-fetch every image** in an email — including the invisible 1×1 tracking pixel that ESPs use to count opens
2. **Route the fetch through Apple proxies** — so the IP / location AcelleMail records is Apple's data center, not the user's device
3. **Pre-fetch on delivery**, not on user-open — so the open registers BEFORE the user has even seen the email (or ever will)

**Result:** every campaign sent to an Apple Mail user with MPP enabled records as opened. Open rates look fantastic. They aren't.

## How widespread

By industry rough numbers:

| Segment | Share of all email opens (approx.) | MPP-affected |
|---|---|---|
| Apple Mail (iOS + macOS) | 35-50% | 80-90% of these |
| Gmail | 25-35% | Not MPP-inflated (own tracking) |
| Outlook desktop + web | 10-20% | Not MPP-inflated |
| Yahoo / AOL / Others | 5-15% | Not MPP-inflated |

**The math:** if Apple Mail is 45% of your audience and 85% of those have MPP, then **~38% of your "opens" are MPP-inflated.** Your real engagement is significantly lower than the dashboard suggests.

The exact ratio depends on your audience:

- **Consumer / lifestyle / mobile-first lists** — skew heavily Apple, expect 40%+ MPP inflation
- **B2B / enterprise lists** — skew Outlook, expect <15% MPP inflation
- **Mixed audience** — split roughly to the industry average above

## How to spot MPP inflation in AcelleMail

There's no direct "MPP filter" toggle in the refactor UI, but the **open log** tells you exactly what's happening: every open is logged with the IP address that fetched the pixel. Apple proxies have telltale IP ranges.

Open any sent campaign → **Sending logs** tab. (Or directly: `/campaigns/{uid}/open-log`.)

![Open log view (Sending logs tab) for "Holiday Edition 35" showing per-recipient open records with columns Recipient · IP Address · Location · Date. Multiple rows show subscribers (benjamin@valenzuela.org, carrillogeoffrey@roberts-gould.com, etc.) opened from IPs like 157.50.100.20, 99.50.100.10, 100.10.50.5, with Location dashes (—) and dates "1 hour ago" / "2 hours ago".](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/08-open-log.png)

What to look for:

- **Location column showing "—"** (no city) — strong signal of Apple proxy
- **Date clustering** — many opens recorded within 1 minute of each other, hours apart from when the user is plausibly awake — pre-fetch behaviour, not human behaviour
- **Many opens from a small set of IP ranges** — Apple uses a finite pool of proxy IPs; you'll see the same IPs across hundreds of subscribers if MPP is heavy

If 30-50% of opens have the *"—"* location pattern + cluster suspiciously, **roughly that share is MPP-inflated.** Subtract that mentally from your headline open rate to get a more realistic estimate.

## What the Overview tab actually tells you

When you read the campaign report, the headline number is no longer trustworthy on its own.

![Campaign Overview tab showing "Holiday Edition 35" with banner "Solid performance — Your open rate of 24% is on track. Consider A/B testing subject lines to push it higher" and stat cards RECIPIENTS 14,794 / DELIVERED 3,838 / OPENED 931 (24.26%) / CLICKED 146 (3.80%). Rate summary panel showing Opened 24.26%, Did not open 75.74%, Clicked 3.80%](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/01-campaign-overview.png)

(Screenshot from [Read the Campaign Report](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics).)

For "Holiday Edition 35":

- **Dashboard says: 24.26% open rate** (931 of 3,838 delivered)
- **MPP-adjusted (if 40% of audience is Apple): ~14-18% real open rate**
- **CTR (click rate) = 3.80%** — this number is NOT MPP-inflated. Clicks require real human action.

The CTR is the reliable number. If you only look at one metric for engagement, **click rate** is the one.

## What to measure instead

The MPP era is forcing email marketers to shift to metrics that survive the inflation:

| Metric | Why it survives MPP |
|---|---|
| **Click rate (CTR)** | Clicks require a human tapping a link — pre-fetch doesn't trigger clicks |
| **Click-to-open rate (CTOR)** | Less affected because clicks are real; the *denominator* (opens) is inflated, so CTOR appears lower than it should — but the metric is still comparable campaign-to-campaign |
| **Revenue per recipient** | The only metric that matters commercially. Untouched by MPP. |
| **Unsubscribe rate** | Real signal of fatigue / mismatch. Untouched by MPP. |
| **Reply rate** | For 1-to-1 / sales emails — direct human engagement |
| **Conversion rate** (purchase, signup, etc.) | The downstream business goal — what you actually optimise for |

**Stop optimising for opens.** Re-orient your A/B tests, your subject line decisions, and your dashboards around **clicks + conversions**. Open rate becomes a quick sanity check — *"did anyone even see this?"* — rather than the primary KPI.

## How to adjust your A/B testing

If you're running A/B tests on subject lines (see [A/B Test Campaigns](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)), the **Winner Rules** tab lets you pick the win condition:

| Choice | Recommended? | Why |
|---|---|---|
| **Highest open rate** | ❌ Not anymore | MPP inflates both variants equally, but adds noise. Differences <5% are basically random. |
| **Highest click rate** | ✅ Yes | Real signal. Use this for subject + content tests. |
| **Manual selection** | ✅ When testing revenue or conversion | Pull data from your backend; pick the winner by what actually drove dollars. |

For most A/B tests in the MPP era, **switch the winner metric from open rate to click rate** by default.

## How to read open rate when you can't avoid it

Despite the inflation, you still have to report open rate to executives, comparison-shop campaigns over time, and benchmark against industry numbers. Three honest approaches:

1. **Report it as "raw open rate" + caveat.** Add a footnote: *"Open rate inflated ~30% by Apple MPP pre-fetch. CTR is the more reliable engagement metric."*
2. **Report the trend, not the absolute number.** If your open rate went from 24% to 32% campaign-over-campaign, that's still a real lift (assuming MPP share stayed constant). Trend > absolute.
3. **Build an "engaged opener" segment** — subscribers who have **clicked** in the last 30 days. Their "open rate" in your analysis is a real human metric.

## What to do, ranked by impact

| Priority | Action |
|---|---|
| 1 | Switch primary KPI from open rate to **click rate** in dashboards |
| 2 | Re-run A/B tests on subject lines using **click rate** as winner condition |
| 3 | Build engagement-based segments off **clicks**, not opens |
| 4 | Don't change re-engagement thresholds yet — using opens > 90 days for sunset still works because Apple subscribers' opens are *consistently* inflated; the relative comparison holds within a single subscriber's history |
| 5 | Update internal reporting templates to caveat open rate |

## What NOT to do

- **Don't compare your post-2021 open rates to pre-2021 numbers** — they're not the same metric. You'll look like a hero or a failure for spurious reasons.
- **Don't try to "filter out" MPP opens in AcelleMail.** There's no reliable way to do this client-side; Apple intentionally mixes in real opens with prefetches.
- **Don't switch ESPs hoping MPP will be solved.** Every ESP has the same problem. This is industry-wide.
- **Don't tell your team "open rate is dead."** It's still useful as a directional / trend signal — just stop using it as your primary KPI.

## Common questions

| Question | Answer |
|---|---|
| Will Google add similar MPP to Gmail? | Hasn't yet (as of 2026). Gmail uses its own tracking that's more accurate than the pixel. |
| If MPP inflates everyone equally, isn't it still useful for comparing campaigns? | Roughly yes — relative comparison within the same audience still works. But absolute numbers and benchmarks against industry are useless. |
| What about "Open Statistics → By Email Client" the docs mention? | That UI path doesn't exist in the AcelleMail refactor UI. Use the **Sending logs** tab + **Open log** sub-view to see per-open IP / Location, which surfaces Apple-proxy patterns. |
| Should I switch to text-only emails to bypass tracking? | Text-only emails have no images = no tracking pixel = AcelleMail can't measure opens at all. Useful for plain transactional, not for marketing. |

## Related articles

- [Read the Campaign Report: AcelleMail Analytics Tour](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics)
- [A/B Test Campaigns in AcelleMail](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Click Map Analysis: Optimise Your Email Layout](https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout)
- [Email ROI Calculation](https://acellesend.com/kb/articles/email-roi-calculation-measuring-campaign-profitability)

---

# Email Delivery Issues — UI Diagnostic Checklist

> When messages don't arrive — late, bounced, missing entirely — the AcelleMail dashboard surfaces nine signals that tell you whether the issue is sending-side or recipient-side. Walk the checklist, fix from the UI in most cases.

- Canonical URL: https://acellesend.com/kb/articles/debugging-common-email-delivery-issues
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Troubleshooting — Common errors, debugging, delivery issues, logs, performance
- Content type: troubleshooting · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-28 · Last updated: 2026-08-15
- Reading time: 6 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## The 60-second dashboard check

Before assuming the worst, open AcelleMail and walk these four screens. 80% of delivery cases resolve from the UI without needing the operator.

### 1. Look at the campaign's tracking log

Open the campaign → **Tracking log** tab. Every send attempt is here with status + timestamp:

![Tracking log — per-message audit](https://acellesend.com/plugins/acelle/landing/images/help/kb/troubleshoot-stuck/03-tracking-log.png)

What the rows tell you:

- **All recent timestamps, all "Delivered"** → AcelleMail sent successfully. The issue is recipient-side (spam folder, blocked domain). Skip to *Sending server test* below.
- **Recent timestamps, mix of "Bounced" + "Deferred"** → receiving servers are slow or rejecting. Open the bounce log next.
- **Tracking log is empty / old** → the campaign never actually sent. See [Campaign stuck in Sending](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status).

### 2. Read the bounce log

Same campaign → **Bounces** tab. The DSN code column tells you why each message failed:

![Bounce log — mixed hard/soft](https://acellesend.com/plugins/acelle/landing/images/help/kb/smtp-error-codes/01-bounce-log-mixed.png)

- **Most bounces are "5.1.1 User unknown"** → stale list, addresses no longer exist. Normal at <5%. Run **Subscribers → Email verification** before next send.
- **Most bounces are "5.7.x"** (delivery not authorized) → your sending IP or domain is being blocked. Move to step 3.
- **Most bounces are "4.x.x"** (temporary) → AcelleMail retries automatically. Wait — no action needed.

See [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages) for the full DSN code reference.

### 3. Check sending server health

**Settings → Sending servers** → click the active server. The detail page shows the three authentication signals:

![Sending server config — auth chips](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/02-sending-server-config.png)

What to verify:

- **SPF, DKIM, DMARC chips all green** — receiving servers can confirm you're authorized to send.
- **Daily quota not exhausted** — if you sent your daily limit, the rest queues to tomorrow.
- **Bounce + complaint rates < 5%** — sustained higher rates throttle your IP.

If any chip is red, click *Verify domain* → AcelleMail walks you through the DNS records to add at your DNS host.

### 4. Run the live diagnostic test

Same sending server detail → **Test** button. Sends a single message to an address you specify:

![Sending server test diagnostic](https://acellesend.com/plugins/acelle/landing/images/help/kb/spam-folder/03-sending-server-test.png)

Send to your own personal email. Three outcomes:

- **Arrives in Inbox** → sending setup is OK. The original issue is content-side (spam-trigger words) or recipient-side (their filter). See [Why emails go to spam](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder).
- **Arrives in Spam** → authentication or reputation issue. Open the message → *Show original* (Gmail) → check `Authentication-Results` header.
- **Doesn't arrive at all** → IP outright blocked. Check the sending IP at mxtoolbox.com → blocklist lookup.

## Common UI fix paths

| Signal in dashboard | Likely cause | What to do |
|---|---|---|
| Tracking log empty hours after launch | Queue worker idle | Operator: restart workers (see Advanced) |
| Most bounces "5.1.1" (User unknown) | Stale list | Run email verification, prune addresses |
| Most bounces "5.7.x" (Delivery not authorized) | IP/domain blocked | Check sending server *Verify domain*; if SPF/DKIM/DMARC all green, IP blocklist issue |
| SPF or DKIM chip red on sending server | DNS records missing | Click *Verify domain* → follow wizard → update DNS at registrar |
| Test diagnostic doesn't arrive | IP on blocklist | mxtoolbox.com lookup → delisting request per blocklist's process |
| Sending server quota hit | Daily limit exceeded | Wait for reset OR provision additional sending server in pool |
| All to ONE domain failing (e.g. @example.com only) | That receiver is blocking you specifically | Investigate FBL complaints from that domain; pause sends to it until reputation recovers |

## When to escalate to the operator

The dashboard exposes everything most users need. Escalate to the server operator when:

- Tracking log is empty AND the campaign should have started >30 minutes ago (queue worker dead)
- Test diagnostic doesn't arrive AND the IP is verified clean on blocklists (deeper SMTP / DNS / firewall issue)
- Daily quota is large enough but campaigns still stop mid-send (PHP-FPM worker exhaustion, database lock)

<details>
<summary><strong>Advanced: server-side checks for the operator</strong></summary>

When dashboard checks point at the worker or server-side delivery, the operator's checklist:

**Worker health:**

```bash
ps aux | grep "queue:work" | grep -v grep
# Expect at least one process for each --queue= pool the supervisor config defines.

php artisan queue:size
# Expected: dropping toward zero as the worker processes the backlog.

php artisan queue:failed | wc -l
# >0 means jobs gave up after their tries. Investigate with:
php artisan queue:failed
```

**Sending server connection test from the CLI:**

```bash
php artisan tinker --execute='
  \$server = \App\Model\SendingServer::where("status","active")->first();
  echo "Driver: " . \$server->type . "\n";
  echo "Host: " . \$server->host . "\n";
  echo "Test result: " . json_encode(\$server->test()) . "\n";
'
```

**SMTP smoke** (raw connectivity from the AcelleMail host to the SMTP gateway):

```bash
# Replace with your sending server's host + port.
nc -zv email-smtp.us-east-1.amazonaws.com 587
# Expected: Connection succeeded. Failure = firewall blocking outbound SMTP.
```

**TLS handshake verification:**

```bash
openssl s_client -connect smtp.sendgrid.net:587 -starttls smtp -crlf < /dev/null \
  2>/dev/null | grep -E "(subject|issuer|verify)"
# Expected: valid cert chain. Self-signed or expired = TLS rejection.
```

**DNS record check** (run from the AcelleMail host to verify the records that receivers actually see):

```bash
dig TXT yourdomain.com +short | grep spf
dig TXT default._domainkey.yourdomain.com +short
dig TXT _dmarc.yourdomain.com +short
```

If any of those return empty, the published records aren't propagated. Wait 24h after publishing; if still empty, the DNS host hasn't accepted the record.

**Log triage:**

```bash
tail -200 /home/acelle/domains/acellemail.com/storage/logs/laravel.log | grep -iE "(send|smtp|fail|reject)"
tail -200 /var/log/mail.log    # if MTA = local Postfix
```

**Sending server queue lag** — for high-volume installs, the per-server queue depth tells you which sending server is the bottleneck:

```bash
php artisan tinker --execute='
  foreach (\App\Model\SendingServer::where("status","active")->get() as \$s) {
    echo \$s->name . ": " . \$s->getCurrentDailySendingQuotaUsage() . " / " . \$s->daily_quota . " today\n";
  }
'
```

A server at 100% quota is the cause of queue spillover into the next day.

</details>

## Related articles

- [Decoding bounce messages](https://acellesend.com/kb/articles/decoding-smtp-error-codes-and-bounce-messages)
- [Why are my emails going to spam folder](https://acellesend.com/kb/articles/why-are-my-acellemail-emails-going-to-spam-folder)
- [Campaign stuck in Sending status](https://acellesend.com/kb/articles/why-is-my-acellemail-campaign-stuck-in-sending-status)
- [Setting up queue workers and cron jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs)

---

# Integrating AcelleMail with WordPress and WooCommerce

> WordPress + WooCommerce is the most common stack people connect to AcelleMail. There's no first-class plugin — instead, you wire up the REST API from a few WordPress hooks. This guide walks the three integration patterns (user registration, WooCommerce purchase, optional unsubscribe sync) end-to-end, plus the choice of "API from theme/plugin" vs "embedded signup form" vs "third-party Zapier".

- Canonical URL: https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce
- Author: Tom Becker, Developer Editor
- Category: Integrations — Stripe, PayPal, WordPress, WooCommerce, Zapier, webhooks, API
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-01-28 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

WordPress + WooCommerce is the most common stack people connect to AcelleMail. There's **no first-class AcelleMail plugin** in the WordPress.org plugin directory — instead, you wire up the [REST API](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) from a few WordPress hooks. The good news: it's ~30 lines of PHP and works reliably.

This guide is the **master overview** with all three integration patterns explained. Each pattern has a dedicated deep-dive article linked at the end.

## Three integration patterns

| Pattern | Use when | Code goes in |
|---|---|---|
| **User-register sync** | You want every WordPress account creation to add a subscriber | `functions.php` or a custom mu-plugin, hooking `user_register` |
| **WooCommerce purchase sync** | You want every paying customer added to a list (and possibly trigger automations) | Same place, hooking `woocommerce_order_status_completed` |
| **Embedded signup form** | You want a marketing-list signup form on a WP page (no PHP) | Paste AcelleMail's form embed code into a Custom HTML block |
| **Zapier** | You want point-and-click without writing PHP | See [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) |

Pick the one that matches your use case. Most stores end up using #2 + #3 together — purchase sync for paying customers + an embedded form for newsletter signups.

## Prerequisites (all patterns)

- A working AcelleMail install
- An AcelleMail API token — see [Getting Started with the REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) Step 1
- An AcelleMail list (UID copied from **Lists → Overview → click the list → URL contains the UID**)
- WordPress 5.5+ (for the modern `wp_remote_post` reliability)

> **Field name gotcha:** AcelleMail's subscriber API uses **UPPERCASE** field names (`EMAIL`, `FIRST_NAME`, `LAST_NAME`, `MAIL_LIST_UID`) — not lowercase. Older guides get this wrong. The API accepts uppercase only.

## Pattern 1 — Sync on WordPress user registration

Detail in [WordPress Subscriber Sync](https://acellesend.com/kb/articles/wordpress-subscriber-sync). Summary:

```php
add_action('user_register', function ($user_id) {
    $user = get_user_by('id', $user_id);

    wp_remote_post('https://mail.example.com/api/v1/subscribers', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode([
            'MAIL_LIST_UID' => 'YOUR_LIST_UID',
            'EMAIL'         => $user->user_email,
            'FIRST_NAME'    => $user->first_name ?: '',
            'LAST_NAME'     => $user->last_name ?: '',
        ]),
        'timeout' => 10,
        'blocking' => false,   // fire and forget
    ]);
});
```

Define `ACELLEMAIL_API_TOKEN` in `wp-config.php` (not in the theme — credentials must not be in version control).

## Pattern 2 — Sync on WooCommerce purchase

Detail in [WooCommerce Post-Purchase Emails](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails). Summary:

```php
add_action('woocommerce_order_status_completed', function ($order_id) {
    $order = wc_get_order($order_id);
    if (!$order) return;

    $tags = [];
    foreach ($order->get_items() as $item) {
        $cats = get_the_terms($item->get_product_id(), 'product_cat');
        if ($cats) foreach ($cats as $c) $tags[] = $c->slug;
    }

    wp_remote_post('https://mail.example.com/api/v1/subscribers', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode([
            'MAIL_LIST_UID' => 'POST_PURCHASE_LIST_UID',
            'EMAIL'         => $order->get_billing_email(),
            'FIRST_NAME'    => $order->get_billing_first_name(),
            'LAST_NAME'     => $order->get_billing_last_name(),
            'ORDER_TOTAL'   => $order->get_total(),
            'ORDER_ID'      => $order_id,
            'tags'          => implode(',', array_unique($tags)),
        ]),
        'timeout' => 10,
        'blocking' => false,
    ]);
});
```

This subscribes the customer to the "post-purchase" list with the order context. Configure an automation in AcelleMail to send a welcome → review-request → upsell sequence — see the [post-purchase emails article](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails) for the 4-step sequence template.

## Pattern 3 — Embedded signup form

Inside AcelleMail:

1. **Lists → Your list → Forms → Create new**
2. Customize fields shown (email + first name is typical for newsletters)
3. **Copy embed code** — gives you either an `<iframe>` snippet or a JS-rendered snippet

In WordPress:

1. Edit the page where you want the form (homepage, footer widget, etc.)
2. Insert a **Custom HTML block**
3. Paste the embed code
4. Save → the form renders inline

**Trade-offs:** the embedded form posts directly to AcelleMail — no WordPress code needed, no API token needed. Downside: limited styling (iframe constraints) and no opportunity to add extra logic on submit. For a simple newsletter signup, it's perfect.

For more customisation, build your own HTML form that POSTs to `/api/v1/public/subscribers` (the unauthenticated endpoint — see [REST API reference](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints#public-unauthenticated)).

## Optional — sync unsubscribes back to WordPress

When a subscriber unsubscribes from your AcelleMail list, you may want to update WordPress user metadata (e.g. clear a `newsletter_subscribed` flag, set a "do not contact" tag).

**Two approaches:**

### A. Per-campaign webhook on `campaign.unsubscribe`

Configure an AcelleMail webhook (Campaign → Webhooks) on the `campaign.unsubscribe` event, pointing to a custom REST endpoint in WordPress:

```php
// In a plugin file
add_action('rest_api_init', function () {
    register_rest_route('acellemail/v1', '/unsubscribe', [
        'methods'  => 'POST',
        'callback' => function (\WP_REST_Request $req) {
            // Verify auth (Bearer token or shared header — match what you set in AcelleMail webhook config)
            $auth = $req->get_header('authorization');
            if ($auth !== 'Bearer ' . WEBHOOK_SHARED_SECRET) {
                return new \WP_REST_Response('Unauthorized', 401);
            }

            $email = $req->get_param('subscriber_email');
            $user = get_user_by('email', $email);
            if ($user) {
                update_user_meta($user->ID, 'newsletter_subscribed', '0');
            }

            return new \WP_REST_Response(['ok' => true], 200);
        },
        'permission_callback' => '__return_true',
    ]);
});
```

The endpoint is at `https://your-wp-site.com/wp-json/acellemail/v1/unsubscribe`. Configure that URL in the AcelleMail webhook config (per [webhook reference](https://acellesend.com/kb/articles/webhook-events-reference)).

### B. Periodic sync via REST API

Run a daily cron in WordPress that fetches the list's current unsubscribed subscribers via the API and updates user meta:

```php
add_action('acellemail_daily_sync', function () {
    $response = wp_remote_get(
        'https://mail.example.com/api/v1/subscribers?filter[list_uid]=YOUR_LIST_UID&filter[status]=unsubscribed&per_page=200',
        ['headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json']]
    );
    $data = json_decode(wp_remote_retrieve_body($response), true);
    foreach (($data['data'] ?? []) as $sub) {
        $user = get_user_by('email', $sub['email']);
        if ($user) update_user_meta($user->ID, 'newsletter_subscribed', '0');
    }
});

if (!wp_next_scheduled('acellemail_daily_sync')) {
    wp_schedule_event(time(), 'daily', 'acellemail_daily_sync');
}
```

Pattern A is real-time but more setup; Pattern B is simpler but lags up to 24 hours. Pick based on your urgency.

## Where to put the code

**Don't paste into `functions.php`** unless you're 100% comfortable losing it on a theme switch. Better:

```bash
# Create a custom mu-plugin (must-use; auto-loaded; survives any theme change)
sudo mkdir -p /path/to/wp-content/mu-plugins
sudo tee /path/to/wp-content/mu-plugins/acellemail-integration.php <<'EOF'
<?php
/**
 * Plugin Name: AcelleMail Integration
 * Description: Sync WordPress + WooCommerce users to AcelleMail
 */

if (!defined('ACELLEMAIL_API_TOKEN') || !defined('ACELLEMAIL_LIST_UID')) {
    return; // Skip if not configured (defined in wp-config.php)
}

// ... your add_action hooks here ...
EOF
```

Then in `wp-config.php`:

```php
define('ACELLEMAIL_API_TOKEN', 'your-token-here');
define('ACELLEMAIL_LIST_UID', 'your-list-uid');
```

## Testing the integration

```bash
# Tail Acelle's app log + WP debug log simultaneously
sudo tail -f /var/www/acellemail/storage/logs/laravel.log \
              /path/to/wp-content/debug.log

# Then in WordPress, register a test user OR place a test WC order
# You should see in Acelle's log: a subscribers.create API hit
# And in WP debug.log (if WP_DEBUG_LOG is on): the wp_remote_post response
```

If nothing happens:

- Verify `WP_DEBUG_LOG = true` is set in `wp-config.php` to see PHP errors
- Verify the API token works via curl (see [getting-started](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api))
- Verify the `MAIL_LIST_UID` is the *list* UID, not the campaign UID (common confusion)

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Subscriber appears but field values are empty | Used lowercase field names (`email`, not `EMAIL`) | Switch to uppercase per the API spec |
| API returns 422 "MAIL_LIST_UID required" | Posted to `/lists/{uid}/subscribers` without the field, or posted to `/subscribers` without the field | Use `/api/v1/subscribers` + include `MAIL_LIST_UID` in the body |
| WordPress page loads slowly after adding the hook | Default `wp_remote_post` is blocking — adds 200-500ms per registration | Add `'blocking' => false` to make it fire-and-forget |
| Duplicate subscribers (1 in WP user, 1 in WooCommerce) | Both `user_register` AND `woocommerce_order_status_completed` fired for the same email | Acelle handles duplicates (upserts by email) — verify; or scope each hook to a different list |
| Embedded form doesn't render | iframe blocked by Content-Security-Policy | Add the AcelleMail domain to your CSP `frame-src`; or use the JS-rendered embed instead |
| `wp_remote_post` returns `WP_Error` "cURL error 60" | TLS verification failed (self-signed cert on dev install) | Use `['sslverify' => false]` for testing only; fix the cert before production |
| Unsubscribe webhook arrives but WP user not updated | `get_user_by('email')` returned null — email mismatch | Lowercase both sides before lookup (`strtolower($email)`) |

## FAQ

**Is there an official AcelleMail WordPress plugin?** Not on WordPress.org as of 2026. The hooks-based integration is the recommended pattern — small, transparent, no plugin-dependency risk.

**Can I import existing WordPress users into AcelleMail?** Yes — export the WP user list as CSV (via a plugin like User Import Export) and import into AcelleMail via **Lists → Your list → Subscribers → Import**. See [Importing Contacts](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping).

**WooCommerce Subscriptions / Memberships?** Same approach — hook the relevant event (`woocommerce_subscription_status_active`, `wc_memberships_user_membership_status_changed`) and POST to the AcelleMail API. Read the WC documentation for the exact hook names.

**Can AcelleMail send transactional emails for WooCommerce (order confirmation, password reset)?** Yes via the [Amazon SES](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) integration — both WooCommerce and AcelleMail can send through the same SES sending identity. Don't try to route WooCommerce transactional through AcelleMail's send-a-campaign API (wrong shape).

**Performance impact on WP site?** With `'blocking' => false` added, < 1ms overhead per user-register / order completion. Without it, 200-500ms — measurable on busy sites.

**Multi-site WordPress (WPMU)?** Same hooks work network-wide. Use `is_multisite() ? $blog_id : null` to differentiate, or scope per-blog to different AcelleMail lists.

**Can I push back to WordPress from AcelleMail?** Yes via webhooks — see the "Optional — sync unsubscribes back" section above.

## Related articles

- [WordPress Subscriber Sync](https://acellesend.com/kb/articles/wordpress-subscriber-sync) — Pattern 1 in detail
- [WooCommerce Post-Purchase Emails](https://acellesend.com/kb/articles/woocommerce-post-purchase-emails) — Pattern 2 + automation sequence
- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — prerequisite
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — for unsubscribe sync back to WP
- [Zapier Integration Guide](https://acellesend.com/kb/articles/zapier-integration-guide) — no-code alternative
- [Importing Contacts](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping) — bulk-import existing WP users
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — shared sending stack
- [Post-Purchase Follow-up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) — the automation template that consumes Pattern 2 events

---

# Click Analysis in AcelleMail: Links Tab + Geographic Click Map

> AcelleMail has two views for click analysis: the Links tab (per-link breakdown — which CTA worked) and the Map tab (geographic distribution). This walkthrough shows both and tells you which to read for what question.

- Canonical URL: https://acellesend.com/kb/articles/click-map-analysis-optimize-your-email-layout
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-01-27 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

"Click analysis" in AcelleMail is split across two tabs of the campaign report. **Most other ESPs call this a Click Map** and conflate them; AcelleMail keeps them honest:

| Tab | What it shows | Use when |
|---|---|---|
| **Links** | Per-URL click breakdown — which link in the email was clicked, how often | *"Which CTA worked best?"* |
| **Map** | Geographic distribution — where in the world the clicks happened | *"What's our reach?"* |

There is **no in-email layout heatmap** (a visual overlay showing "people clicked here on the email image") in AcelleMail. The Links tab is the substitute: it groups clicks by URL, and since your email links to different URLs from different blocks, you can infer layout effectiveness from URL data.

This article walks through both tabs.

## The Links tab — which CTA actually worked

Open any sent campaign → **Links** tab. URL pattern: `/campaigns/{uid}/links`.

![Links tab for "Holiday Edition 35" with subtitle "Click tracking and link performance analysis". 4 stat cards across the top: LINKS TRACKED 1 unique URLs · TOTAL CLICKS 146 · CLICK RATE 3.80% (146 unique clickers) · TOP LINK 100% of all clicks. Below: Click distribution donut chart (100% on the single link) on the left, Clicks per link horizontal bar chart on the right showing https://acelle-demo.com/... with 146 clicks. Below those: "All tracked links" section header with a "View click log" link.](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/06-links-report.png)

The 4 cards at the top:

| Card | What it means | Healthy range |
|---|---|---|
| **LINKS TRACKED** | How many unique URLs your email contained | 1-5 (more dilutes attention) |
| **TOTAL CLICKS** | Sum of all clicks across all links | Use as denominator only |
| **CLICK RATE** | Unique clickers / delivered | 2-5% is healthy |
| **TOP LINK** | % of total clicks captured by your best-performing link | 60%+ = focused message; <30% = scattered |

**TOP LINK is the most diagnostic.** If 100% of your clicks go to one link, your email had one job and did it. If clicks are split across many links (TOP LINK at 25%), the message was too scattered.

### The two charts side-by-side

- **Click distribution (donut)** — visual share of clicks per URL. Hover to see counts.
- **Clicks per link (bar chart)** — ranks URLs by absolute click count. Top bar = best-performing CTA.

### The "All tracked links" table

Scroll below the charts to see the raw table:

| Column | What it shows |
|---|---|
| URL | Full destination URL |
| Clicks (unique) | Different subscribers who clicked this URL |
| Clicks (total) | All clicks including duplicates (one person clicking twice) |
| CTR | Clicks as % of delivered |
| First clicked / Last clicked | Time of first and most recent click |

**Hard insight:** if Clicks (total) is significantly higher than Clicks (unique), some subscribers clicked the same link multiple times — strong signal of high interest in that destination.

## Pattern: deliberately track multiple URLs

A campaign with only 1 URL tells you nothing about WHICH part of your email worked. Build campaigns with 2-3 distinct URLs to learn:

| Block | URL | Purpose |
|---|---|---|
| Headline image | `yoursite.com/?utm_content=hero` | Top-of-email engagement |
| Body CTA button | `yoursite.com/?utm_content=cta` | The primary action |
| Footer text link | `yoursite.com/?utm_content=footer` | Bottom-scrollers |

After the campaign, the Links tab tells you which of the three got the most clicks. Iterate by:

1. **Hero pulled more clicks than CTA** → image is your real CTA; make it more obviously clickable
2. **Footer link pulled significant clicks** → some readers skip to the bottom; put your offer there too
3. **CTA pulled most clicks** → your email did its job; replicate the layout

Use UTM parameters on the URLs so you can also trace this in your site analytics. See [Setting Up UTM Parameters](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking).

## The Map tab — geographic click distribution

Switch to the **Map** tab. URL: `/campaigns/{uid}/click-map`.

![Map tab for "Holiday Edition 35" subtitle "Geographic distribution of clicks worldwide". 3 stat cards: LOCATIONS 146 unique click points · COUNTRIES 10 reached worldwide · TOTAL CLICKS 146 recorded. Below: "Click locations worldwide" section with "146 markers · Click to see details" hint, then a world map with green dots clustered in North America, Europe, and Asia showing where each click originated.](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/02-click-map.png)

The Map tab tells you 3 things:

| Card | Use |
|---|---|
| **LOCATIONS** | Number of unique click points — same as TOTAL CLICKS in most cases unless multiple clicks from the same IP |
| **COUNTRIES** | Geographic reach. 10 countries means your audience is global; 1 country means localised. |
| **TOTAL CLICKS** | Same number as on Links tab — cross-reference |

The world map plots each click as a green dot at the click's IP-geolocation. **Click any dot** for details (subscriber email, time, URL clicked).

**What to learn from the map:**

- **Single-country audience?** Schedule campaigns for that country's prime time (see [Campaign Scheduling](https://acellesend.com/kb/articles/campaign-scheduling-and-timezone-optimization)).
- **Two-continent split?** Build segments by region and send separate campaigns timed to each.
- **Unexpected geographic cluster** (a tiny country with disproportionate clicks)? Investigate — could be a viral share, an unsubscribed-then-resubscribed cluster, or a bot.

> **Geographic data isn't perfectly accurate.** IP geolocation is approximate, and Apple MPP routing makes Apple Mail subscribers' "locations" all show as Apple's data centers (mostly US). For most campaigns, treat country-level data as accurate, city-level as a rough guide.

## Combining Links + Map for diagnostic reads

The two tabs together answer specific questions:

| Question | Where to look |
|---|---|
| Which CTA worked? | **Links** — TOP LINK + Clicks per link chart |
| Did the audience match our targeting? | **Map** — COUNTRIES + dot distribution |
| Why did the campaign underperform? | **Links** — if all clicks went to footer, your hero didn't hook them |
| Should we localise? | **Map** — if 90% of clicks are in 1 country, yes |
| Is there click fraud / bot activity? | **Map** — clicks all from one obscure location, OR all clicks within 30 seconds of send |

## Layout optimisation — without an in-email heatmap

Since AcelleMail doesn't render a visual layout heatmap, you reconstruct it from URL data + your knowledge of where each URL appears in the email.

**Practical workflow per campaign:**

1. **Plan link placement before send.** Note in a spreadsheet which URL appears in which block:
   ```
   URL 1 — yoursite.com/?utm_content=hero       → headline image (top)
   URL 2 — yoursite.com/?utm_content=primary    → primary CTA button (mid)
   URL 3 — yoursite.com/?utm_content=secondary  → secondary text link (lower)
   URL 4 — yoursite.com/?utm_content=footer     → footer
   ```
2. **After send**, compare click counts. Higher click count = better-performing block.
3. **Iterate**: if footer URL got 30% of clicks despite being last, move that messaging higher in the next campaign.

After 5-10 campaigns of this pattern, you have a "layout heatmap" specific to your audience — derived from data, not assumed.

## Common issues

| What you see | What to do |
|---|---|
| LINKS TRACKED says 1 but my email had 5 links | The 4 missing links probably weren't tracked because click-tracking was off when you sent. Check the campaign Setup step → Tracking section. |
| CLICK RATE on Links tab differs from Overview tab | Different denominators. Overview "Clicked" stat = unique clickers / delivered. Some derived metrics use total clicks / unique opens. Read the percentage label. |
| Map shows all clicks in 1 location | Either small list / local audience (expected), OR your tracking is being filtered by a proxy. Check the Apple MPP article for proxy IP patterns. |
| TOP LINK is 100% but you had multiple CTAs | All other links got 0 clicks. Bad sign — your other CTAs didn't even register. Make them visually distinct + offer different value. |
| Sudden spike in clicks from one country you don't sell to | Possible click bot. Audit the open log for IP patterns; consider blocking that IP range if it persists. |
| "View click log" returns sparse data | Click log only shows actual click events. A campaign with 5 clicks shows 5 rows; sparse means low engagement, not a bug. |

## What NOT to do

- **Don't put 10 links in one email** to "spread the bet." High link count dilutes attention. 1-3 distinct destinations max.
- **Don't read absolute click counts.** A campaign to 1,000 with 50 clicks is excellent (5% CTR); a campaign to 100,000 with 200 clicks is poor (0.2% CTR).
- **Don't optimise location-targeting from a single campaign's Map data.** Geographic patterns need 3-5 campaigns to be reliable.
- **Don't ignore the Time of first-clicked.** A 0-second first-click is bot-suspicious; legitimate clicks usually arrive minutes-to-hours after send.

## Related articles

- [Read the Campaign Report: AcelleMail Analytics Tour](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics)
- [Open Rates + Apple MPP](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp)
- [Setting Up UTM Parameters for Campaign Tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking)
- [Comparative Campaign Analysis: A/B Test Results Deep Dive](https://acellesend.com/kb/articles/comparative-campaign-analysis-ab-test-results-deep-dive)

---

# Setting Up AcelleMail as a SaaS Platform

> AcelleMail can be operated as a multi-tenant SaaS where each customer signs up, picks a plan, pays via Stripe, and gets an isolated workspace (lists, campaigns, automations, sending quotas). This guide walks the end-to-end setup: Extended License, Stripe (the only first-class subscription gateway today), plan design, customer onboarding flow, tenant isolation, and the operational realities of running this as a business.

- Canonical URL: https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform
- Author: Martin Keller, SaaS & Business Editor
- Category: SaaS & Multi-tenant — White-label, subscription plans, billing, tenant management, branding
- Content type: tutorial · Difficulty: advanced
- Tags: Acellemail
- Published: 2026-01-26 · Last updated: 2026-08-15
- Reading time: 11 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail can be operated as a multi-tenant **SaaS** — each customer signs up, picks a plan, pays via Stripe, and gets an isolated workspace (lists, campaigns, automations, sending quotas, even their own sending domain). The operator (you) collects subscription revenue, manages the underlying infrastructure, and keeps the margin.

This guide walks the end-to-end setup. **Before you start, decide whether SaaS is actually the right model** — see the [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas) sibling article for the pricing + GTM playbook.

**Prerequisites:**

- An AcelleMail [**Extended License**](https://codecanyon.net/item/acellemail-self-hosted-email-marketing-application/10149682) from CodeCanyon. The Regular License does not allow you to resell access to the platform — the Extended License is required for any SaaS use case.
- A live AcelleMail install — see the [canonical Ubuntu 24.04 guide](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404). At SaaS scale, target [Medium tier](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) (4 vCPU, 8 GB) minimum.
- A Stripe account (the only first-class subscription gateway in current Acelle Cashier — see the FAQ for why PayPal isn't recommended).
- A sending relay configured ([Amazon SES](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) is the typical choice; you can also let customers BYO their own SES/Mailgun/SendGrid).

## Step 1 — Enable customer signups

By default, AcelleMail is single-tenant — only admins can create accounts. For SaaS, you flip this in **Admin → Settings → General → User Signup**:

- **Enable user signup:** Yes
- **Email confirmation required:** Yes (essential — without it spammers will sign up to use your sending IPs)
- **Default plan after signup:** the free / trial plan you'll create in Step 3
- **Welcome email template:** pick or create one that sets customer expectations + links to your docs

The public `/register` route is now live. Customers who hit it will see your branded signup form (configured per [the white-label guide](https://acellesend.com/kb/articles/white-label-customization-guide)).

## Step 2 — Configure Stripe as your payment gateway

In **Admin → Payment Gateways → Stripe → Edit**:

| Field | Where it comes from |
|---|---|
| Publishable Key | Stripe Dashboard → Developers → API keys → `pk_live_...` |
| Secret Key | Stripe Dashboard → Developers → API keys → `sk_live_...` |
| Webhook Signing Secret | Set up the webhook first (next paragraph), then paste the `whsec_...` from Stripe |
| Mode | `live` (use `test` for staging) |
| Currency | The Stripe-account default; must match plan currencies |

**Set up the Stripe webhook** — required for renewals, cancellations, and dunning to work:

- URL: `https://your-domain.com/stripe/webhook` (the URL is shown in your AcelleMail's Stripe gateway page after you save)
- Events to send: `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.paid`, `invoice.payment_failed`, `payment_intent.succeeded`

> **Test mode first.** Use the Stripe test keys + test webhook on a staging install before flipping to live mode. A wrong webhook in production means missed renewals and angry customers.

Click **Enable** on the gateway. Disable any other gateways you're not using (`Admin → Payment Gateways` → toggle the rest off) — leaving inactive ones enabled shows confusing options to your customers at checkout.

## Step 3 — Design plans

In **Admin → Plans → Add new**. Each plan controls *what a customer can do*, and the price they pay for it.

The Plan form has these fields (per the `plans` table schema):

| Field | What it sets |
|---|---|
| **Name** | Plan display name on your pricing page |
| **Description** | Markdown allowed; shows under the plan card |
| **Price** | Decimal; the subscription amount per `frequency` |
| **Currency** | Must match your Stripe account currency |
| **Frequency amount + unit** | e.g. `1` + `month` for monthly; `1` + `year` for annual |
| **Trial amount + unit** | e.g. `14` + `day` for a 14-day free trial; leave blank for no trial |
| **Status** | Active / Inactive |
| **Visible** | If false, plan exists but doesn't show on the public pricing page (use for grandfathered or hidden plans) |
| **Own tracking domain required** | Forces each customer on this plan to set up their own tracking subdomain (recommended for higher tiers to protect your shared domain's reputation) |
| **Options (JSON)** | The quota / feature flags — see below |

The **Options** field is a JSON blob stored on the plan. This is where the per-plan quotas live:

```json
{
  "max_subscribers": 5000,
  "max_lists": 10,
  "max_campaigns": -1,
  "max_emails_per_month": 50000,
  "max_subscribers_per_list": 5000,
  "max_segments_per_list": 10,
  "max_automations": 3,
  "max_templates": -1,
  "max_sending_servers": -1,
  "max_email_verification_servers": -1,
  "max_team_members": 2,
  "api_access": true,
  "create_sending_server_enabled": false,
  "automation_enabled": true,
  "custom_domain_enabled": false,
  "custom_html_enabled": false
}
```

> Use `-1` for "unlimited" on numeric quotas. `true`/`false` for feature toggles. The admin UI exposes these as labelled form fields, so you usually don't hand-edit JSON — but knowing the underlying shape helps when scripting plan creation.

**Attach sending servers + email verification servers** to the plan (via the Plan edit page → Sending Servers tab). A plan with no sending servers attached means the customer must BYO — set `create_sending_server_enabled: true` in that case so they can add their own SES/Mailgun creds.

## Pricing-tier recipe (use as starting point)

Three-tier ladder that works for most B2B SaaS positionings:

| Tier | Price | Subscribers | Sends/mo | Automations | Custom domain | Notes |
|---|---|---|---|---|---|---|
| **Starter** | $19/mo | 2,500 | 25,000 | 3 | No | Self-serve credit-card signup |
| **Pro** | $79/mo | 15,000 | 150,000 | Unlimited | Yes | Most popular; the "anchor" tier |
| **Agency** | $199/mo | 50,000 | 500,000 | Unlimited | Yes + sub-accounts | Sales-assisted; 14-day trial |

Add an annual variant of each plan at ~17% discount (10 months for the price of 12) — this is the standard SaaS lever and meaningfully reduces churn.

For sizing the underlying server, see [Server Requirements](https://acellesend.com/kb/articles/server-requirements-and-hosting-options). 100 Pro-tier customers (≈15M sends/month aggregate) is the Medium tier; 500 is the Large tier.

## Step 4 — Customer onboarding flow

What a new customer experiences end-to-end:

1. Lands on your pricing page → clicks **Get Started** on a plan
2. Hits your AcelleMail `/register` → fills email + password
3. Confirms email via the link AcelleMail sends
4. Logs in → lands on the welcome screen
5. **AcelleMail Cashier prompts for payment** (unless the plan is free or has a trial) → Stripe Checkout
6. On successful payment, the customer is auto-subscribed to the plan, and their tenant workspace is ready

You should customise the welcome flow:

- **Welcome email template** (`Admin → System Emails → Welcome`) — set tone, link to your getting-started docs, embed a YouTube walkthrough
- **First-login flow** — Acelle shows a generic empty dashboard. Consider linking from your welcome email directly to "Step 1: import your first list" rather than the dashboard
- **In-app tour** — Acelle ships no in-app tour. For an upmarket SaaS, consider adding an Intercom / Userpilot overlay via your custom CSS/JS (see [white-label guide](https://acellesend.com/kb/articles/white-label-customization-guide))

## Step 5 — Tenant isolation

Each AcelleMail customer is a separate tenant with their own:

- Subscriber lists + segments
- Campaigns + templates
- Automation workflows
- Sending quotas (enforced per-plan via the `options` JSON quotas)
- Sending server attachments (per plan)
- API key (each customer generates their own under their profile)
- Optional: their own sending domain + tracking domain

**What's NOT isolated** (important to know):

- The underlying database is single-database — all tenant data is in the same `acelle` MySQL database, scoped by `customer_id` on each row. This is fine for legitimate SaaS, but means a database-level compromise affects all tenants.
- Storage paths are also shared (`storage/uploads/<customer_id>/...`) — same shared-file-system caveat.
- Sending IPs are shared by default — unless a customer is on a plan that requires "Own sending server" + has their own SES configured, they share your shared IP pool. **One bad-actor customer can degrade reputation for everyone** — see the FAQ for mitigations.

## Step 6 — Day-2 operations

After the system is live, the recurring operational tasks:

- **Monitor signups for spam.** Spammers will sign up to use your free trial / shared sending pool. Watch the `email_log` table for unusual sending patterns; consider requiring credit-card-on-file even for free trials.
- **Reconcile Stripe ↔ AcelleMail subscriptions.** The webhooks are reliable but not perfect — once a week, spot-check that Stripe's active subscription count matches AcelleMail's. Acelle ships a reconcile job (see `acelle:remote-subscriptions:reconcile` in `php artisan list`).
- **Sending quota enforcement.** AcelleMail enforces `max_emails_per_month` at send time — a customer hitting their cap will see "You have exceeded your sending quota". They have to either wait until the next cycle or upgrade their plan.
- **Customer support tickets.** Most common: bounce-handling failures (the customer's SES setup), authentication issues, "where do I add SPF/DKIM" (link them to your guide). Stand up a help-desk early.
- **Plan migrations.** When you change a plan's price or limits, existing subscribers stay on their old terms unless you explicitly migrate them. Acelle's admin UI has a Plan → Migrate flow.

## Step 7 — White-labelling

If you're running this under your own brand (not "Powered by AcelleMail"), follow the [white-label customization guide](https://acellesend.com/kb/articles/white-label-customization-guide). The Extended License explicitly allows full re-branding.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Stripe charge succeeds but customer plan not updated in AcelleMail | Webhook not configured or signing secret wrong | Re-create the webhook in Stripe Dashboard; paste the `whsec_...` into AcelleMail's Stripe gateway settings |
| Customer can't add their own SES — "feature not enabled" | Plan's `options.create_sending_server_enabled` is `false` | Edit the plan; toggle Allow custom sending server: Yes |
| Customer sends are counted against an old plan after upgrade | Plan migration wasn't propagated | Run `php artisan acelle:remote-subscriptions:reconcile` or use Admin → Subscriptions → Sync |
| Customers complain about deliverability after a spam-blast tenant | Shared-IP pool reputation hit | Move legitimate customers to a dedicated sending server; suspend the bad-actor tenant |
| Trial customers never convert | No automation, no email reminders | Set up a [3-step trial-conversion automation](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) targeting customers near trial expiry |
| Free-tier customers send to spam-trap lists | No list-hygiene policy enforced | Require [double opt-in](https://acellesend.com/kb/articles/double-opt-in-vs-single-opt-in-which-to-choose) on free tier (`require_confirmation_on_signup` per plan), or run a [list-hygiene check](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability) at signup |

## FAQ

**Why is PayPal not a first-class option anymore?** Acelle Cashier (the subscription billing layer) consolidated on **Stripe + Offline** in the recent refactor. PayPal recurring subscriptions are operationally messier (no webhook for failed payments, no first-class trial support, currency conversion overhead) and the maintenance burden didn't justify keeping it. If you need PayPal, run it as "Offline" mode with manual reconciliation, or set up a separate Stripe → PayPal Express Checkout integration.

**Can I migrate from PayPal to Stripe?** Yes — existing PayPal subscriptions stay until they next renew, at which point they'll need to re-enter payment via Stripe. Communicate this to customers in advance via a 30-day notice email.

**Can I run as a hybrid (some self-hosted, some SaaS)?** Yes. Disable user signup (Step 1) for the self-hosted tier; only your team creates those customer accounts. Use plans for both, just hide non-public plans (`visible: false`).

**How do I bill customers in multiple currencies?** Each plan has a single currency. Create parallel plans in each currency (e.g. "Pro $79" and "Pro €72"). Customer's billing currency is locked to the plan they signed up on; you can't auto-convert.

**Can a customer have multiple subscriptions?** Yes — Acelle's data model supports it (a customer can have multiple active subscriptions to different plans, e.g. a "Sending credit" add-on alongside a base plan). The UI for end-customers shows them their primary plan plus any add-on subscriptions.

**Can I require credit card on file for free trials?** Set `require_payment_method_on_trial: true` in the plan options. Customers hit Stripe's payment method capture flow before the trial starts. Significantly reduces signup fraud.

**Does AcelleMail send abandoned-checkout emails?** No — that's outside Acelle's scope (it's not an e-commerce platform). Integrate via your own marketing-automation tool (HubSpot, Customer.io, ActiveCampaign), or write a custom Laravel listener on the `SubscriptionAbandoned` event.

**What happens when a subscription is cancelled?** Per Stripe's webhook, AcelleMail flips the subscription status to `cancelled` and downgrades the customer to the default free plan (or suspends if there's no free plan). Existing campaigns / lists / templates are retained; only sending is blocked.

**GDPR / right-to-erasure?** When a customer requests deletion, **Admin → Customers → ... → Delete** removes the customer record and all their related data (lists, subscribers, campaigns, automations). Sending logs may be retained per your retention policy — document this in your privacy policy.

## Related articles

- [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas) — pricing + GTM playbook (the business side)
- [White-Label Customization Guide](https://acellesend.com/kb/articles/white-label-customization-guide) — branding the install as yours
- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — the install (prerequisite)
- [Server Requirements and Hosting Options](https://acellesend.com/kb/articles/server-requirements-and-hosting-options) — sizing for SaaS scale
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — required before going live with paying customers
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — typical shared-pool sending relay
- [Building a Welcome Email Series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail) — trial-conversion automation
- [GDPR Compliance Guide for Email Marketing](https://acellesend.com/kb/articles/gdpr-compliance-guide-for-email-marketing) — required reading for SaaS operators
- [CAN-SPAM Compliance Checklist](https://acellesend.com/kb/articles/can-spam-compliance-checklist) — required reading for SaaS operators

---

# What's New in AcelleMail

> AcelleMail ships updates regularly — minor patches every few weeks, larger feature releases a few times a year. This article explains the release cadence, where to find the current list of "what's new" for your install, the upgrade discipline that keeps things running smoothly, and the patterns of improvement the platform has been heading toward.

- Canonical URL: https://acellesend.com/kb/articles/whats-new-in-acellemail-52
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: AcelleMail Updates — Release notes, changelogs, new features, roadmap, community news
- Content type: announcement · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-25 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail ships updates regularly. **Minor patches every few weeks** (security fixes, bug fixes, small enhancements). **Larger feature releases a few times a year** (new modules, UI updates, infrastructure improvements).

This article is intentionally evergreen — rather than listing specific version features that drift out of date, it explains:

1. The **release cadence** + how AcelleMail versions are numbered
2. **Where to find the current changelog** for your install
3. The **upgrade discipline** that keeps things running smoothly
4. The **patterns of improvement** the platform has been heading toward

For specific announcements, see the [AcelleMail 2026 Roadmap](https://acellesend.com/kb/articles/acellemail-2026-roadmap) sibling article.

## Release cadence + versioning

AcelleMail uses semantic-ish versioning: `MAJOR.MINOR.PATCH`.

| Type | Frequency | Typical content |
|---|---|---|
| **Patch release** (5.2.x → 5.2.y) | Every 2-6 weeks | Bug fixes, security patches, minor enhancements |
| **Minor release** (5.2 → 5.3) | Every 4-8 months | New features, UI refinements, mostly backwards-compatible |
| **Major release** (5.x → 6.0) | Every 18-24 months | Breaking changes, architecture shifts, may require migration steps |

Updates ship as `.bin` patch files from CodeCanyon. You download the patch from your CodeCanyon item page and apply it via Acelle's admin upgrade flow (see Upgrade Discipline below).

## Finding what's new in your install

There's no single "What's New" page in the admin (yet). To know what's in *your* install:

### 1. The Admin About page

- **AcelleMail Admin → About** (or Settings → About depending on version)
- Shows: AcelleMail version, PHP version, MySQL version, license info, install date

### 2. The CodeCanyon changelog

- Go to your CodeCanyon Item page → **Changelog tab**
- Lists every version with bullet-point release notes
- This is the authoritative source — Acelle's official "what changed"
- Browse older entries to understand the trajectory

### 3. The current version's release notes

When you apply a `patch-x.x.x.bin`, the upgrade UI shows the changelog for that specific patch before you confirm. **Read it.** Notes call out:

- Required PHP / MySQL version changes (rare; usually not bumped within a major)
- New required environment variables (added to `.env`)
- Database migrations (auto-run, but knowing they happen helps)
- Behavior changes that might affect existing automations

### 4. From the install itself

Some hints live in the codebase:

```bash
# Current version
cat /var/www/acellemail/config/app.php | grep "'version'"

# Migration history — shows what schema changes have been applied
cd /var/www/acellemail && sudo -u www-data php artisan migrate:status

# Composer dependencies (for cross-referencing if a vendor changed)
cat /var/www/acellemail/composer.lock | grep -A1 '"name":' | head -20
```

## Upgrade discipline

The right upgrade workflow has 5 steps:

### Step 1 — Read the patch notes

Before downloading. Note any breaking changes, required `.env` additions, or known issues with the patch.

### Step 2 — Test on a staging install first

If you operate any tier where downtime matters (SaaS, production sending), maintain a staging install (one VPS, same dist + PHP + MySQL versions as prod). Apply the patch there first, exercise the admin UI + a test campaign send + your automations. **If you don't have staging, at least apply patches outside business hours.**

### Step 3 — Take a backup before applying

```bash
sudo /usr/local/bin/acellemail-db-backup.sh
# See: /kb/articles/automated-database-backups for the script
```

Plus a file-tree snapshot if the patch touches more than the code (rare for patch releases; common for minor releases that change templates):

```bash
sudo tar czf /var/backups/pre-upgrade-$(date +%F).tar.gz /var/www/acellemail
```

### Step 4 — Apply the patch

In Admin:

- **Admin → Upgrades → Upload `.bin` file → Run**
- OR via API: `curl -X POST -H "Authorization: Bearer $TOKEN" -F "patch=@patch-x.x.x.bin" https://your-acelle/api/v1/upgrade/run-file`

The patch runs migrations + replaces files. **The web UI may be intermittently unavailable for 30-90 seconds** during this — schedule accordingly.

### Step 5 — Restart workers + clear caches

```bash
cd /var/www/acellemail
sudo -u www-data php artisan optimize:clear
sudo supervisorctl restart acellemail-master:* acellemail-worker:*
```

**Worker restart is non-negotiable.** Workers run the OLD code from when they started — they don't pick up the new patch's code until restarted. Without this, you'll see weird half-state behavior for hours.

For Docker installs, see the [Docker patch-upgrade workflow](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail#step-5--patch-upgrade-workflow-the-docker-quirk).

### Step 6 — Verify

```bash
# Quick smoke checks
curl -s -o /dev/null -w "%{http_code}\n" https://your-acelle/        # 200
curl -s -o /dev/null -w "%{http_code}\n" https://your-acelle/admin   # 200 or 302
sudo supervisorctl status                                              # all RUNNING
sudo -u www-data php artisan queue:failed | head                       # no new failures
```

Then send a test campaign to a small internal list to confirm sending works.

## Recent platform direction

Looking at AcelleMail's evolution over the past 18 months, the platform has been investing in:

- **Deliverability tooling** — better SES integration, more granular per-sending-server quotas, native Postmaster + SNDS integration, FBL handling improvements
- **Automation depth** — more conditional logic, better merge tag handling, API triggers from external systems, in-flow webhook actions
- **SaaS operator features** — Cashier subscription billing consolidation on Stripe, per-customer custom domains with auto-TLS, multi-server sending-pool patterns
- **Admin UI polish** — refactor of older controllers (the `Refactor/Admin/*` namespace), improved responsive design, dark-mode support

These aren't promises — they're the observed direction from comparing 12 months of release notes. Your specific install may have features from any subset of these depending on version.

## Common upgrade issues

| What you see | Likely cause | Fix |
|---|---|---|
| `Class XYZ not found` after upgrade | Cached views/config reference removed classes | `php artisan optimize:clear` then restart workers |
| Workers running old code | Forgot Step 5 (worker restart) | `sudo supervisorctl restart acellemail-worker:*` |
| Database errors after upgrade | A migration didn't run | `sudo -u www-data php artisan migrate --force` |
| Admin shows wrong version after upgrade | Browser / opcache cache | Hard refresh; restart php-fpm if needed |
| Custom extensions broke | Acelle changed an internal API the extension used | Check [extending source guide](https://acellesend.com/kb/articles/extending-acellemail-source-code) for upgrade-safe patterns |
| `.env` missing new required variable | New release added a required env var | Patch notes should list these; add to .env + clear config cache |
| Patch file rejected as "invalid" | Downloaded for wrong major version | Verify you're downloading the patch for YOUR current major version, not the latest |

## FAQ

**How do I get notified of new releases?** CodeCanyon sends an email when a new patch is published for items you own. Make sure your CodeCanyon profile has email notifications enabled. You can also subscribe to the AcelleMail newsletter if available.

**Can I skip a patch (go from 5.2.1 directly to 5.2.5)?** Usually yes — patches are cumulative within a minor version. The 5.2.5 patch includes all changes from 5.2.2 through 5.2.5.

**Going from a major version to the next (5.x → 6.x)?** Read the major-release migration guide carefully. There may be breaking changes (`.env` variable renames, removed features, database schema changes that need manual prep). Don't skip.

**What if my install is years out of date?** You may need to apply patches sequentially through multiple major versions. This is doable but tedious — for installs 2+ years behind, consider a fresh install + data migration as the cleaner path.

**Where do I report a bug found in a release?** Contact CodeCanyon support via the item's Support tab. Include AcelleMail version, PHP version, exact reproduction steps, and error log excerpts.

**Is the source code open?** AcelleMail is a commercial CodeCanyon product. The source is provided to license holders but is not publicly open-source. See [Contributing to AcelleMail Development](https://acellesend.com/kb/articles/contributing-to-acellemail-development) for what "contributing" means in this context.

**Beta versions?** Not formally — releases are tested internally before going to CodeCanyon. If you want to help test, build a relationship with the Acelle team via support; they sometimes share early builds with engaged operators.

## Related articles

- [AcelleMail 2026 Roadmap](https://acellesend.com/kb/articles/acellemail-2026-roadmap) — sibling article on the platform's direction
- [Contributing to AcelleMail Development](https://acellesend.com/kb/articles/contributing-to-acellemail-development) — how to engage with the team
- [Community Spotlight: Business Case Studies](https://acellesend.com/kb/articles/community-spotlight-business-case-studies) — what other operators have built
- [Install AcelleMail on Ubuntu 24.04 LTS](https://acellesend.com/kb/articles/install-acellemail-on-ubuntu-2404) — for fresh installs
- [Docker Deployment Guide for AcelleMail](https://acellesend.com/kb/articles/docker-deployment-guide-for-acellemail) — Docker-specific upgrade flow
- [Automated Database Backups](https://acellesend.com/kb/articles/automated-database-backups) — backups before every upgrade
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — worker restart is part of every upgrade
- [Post-Install Hardening Checklist](https://acellesend.com/kb/articles/post-install-hardening-checklist) — re-run after major upgrades
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — for keeping custom extensions upgrade-safe

---

# UTM Parameters in AcelleMail — Tracking Clicks to Your Analytics Tool

> Configure UTM tags once in your campaign settings — AcelleMail appends them to every link in the email automatically. This guide walks the campaign-level + global UTM settings, the post-send click report, and how the UTMs land in Google Analytics or Plausible.

- Canonical URL: https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking
- Author: Ryan Walsh, Audience & Analytics Editor
- Category: Analytics & Reporting — Open rates, click maps, campaign performance, device reports, exports
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-25 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why UTMs and where they go

UTM parameters are query-string tags appended to your campaign's outbound links — `?utm_source=newsletter&utm_medium=email&utm_campaign=spring_launch` — that your analytics tool (Google Analytics, Plausible, Fathom, Matomo) reads to attribute traffic + conversions back to the email that drove them.

Without UTMs, every click from your email shows up as **(direct) / (none)** in analytics. With UTMs, you see "spring_launch newsletter drove 1,847 sessions and 312 conversions" — the kind of report you can defend in a quarterly review.

The good news: AcelleMail handles this for you at the campaign level. Set it once, every link gets tagged.

## Set UTMs in the campaign builder

### 1. Open campaign settings → URL parameters

When you create or edit a campaign, the **Tracking** (or **URL parameters**) section in campaign settings has fields for the standard UTM keys:

- **utm_source** — where the click came from. For email: usually `newsletter` or your brand name like `acellemail`.
- **utm_medium** — the channel. Almost always `email` for AcelleMail-driven sends.
- **utm_campaign** — the specific campaign identifier — `spring_launch_2026`, `weekly_digest_w17`, `cart_abandonment_v3`.
- **utm_term** (optional) — usually paid-search specific, sometimes used for segment-of-list identification.
- **utm_content** (optional) — variant identifier — `header_cta` vs `footer_cta` to A/B test which link position pulls more.

A standard configuration:

```
utm_source:   newsletter
utm_medium:   email
utm_campaign: spring_launch_2026
```

Click **Save**. AcelleMail will append these to every link in the email automatically — you don't need to add `?utm_source=...` to each individual `<a href>` in the body.

### 2. Verify in Preview

Open the email Preview, hover over any link — the URL shown at the bottom of the screen will include the UTM query string. If you don't see it:

- Check that **Tracking** is enabled in the campaign settings (some installs default it off)
- The link is to your own domain (some setups skip UTM-tagging on external partner links)
- The URL doesn't already have query parameters that conflict (AcelleMail will append `&utm_source=...` rather than overwrite)

## Read the click report after sending

### Tracking log — per-click audit

Campaign → **Tracking log** tab shows every action (open, click, bounce) with timestamp + subscriber:

![Tracking log — per-event audit](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/03-tracking-log.png)

For UTM verification, find a Click row → the destination URL stored is the original link (without UTMs) — AcelleMail tracks the *click event* server-side via the redirect, then forwards the user to the URL *with* UTMs appended.

### Click log — link-level click breakdown

The **Click log** tab focuses on click events specifically:

![Click log per subscriber](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/04-click-log.png)

### Links report — aggregated per-link totals

The **Links report** rolls everything up — per-link click count + unique clickers:

![Links report — per-link aggregate](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/06-links-report.png)

This is the most useful view for content optimization: "Did our footer CTA outperform the header CTA?" → look at the per-link click count.

### Click map — visualisation of where clicks went

The **Click map** overlays click counts on the rendered email:

![Click map overlay](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/02-click-map.png)

The heatmap tells you the same thing as the Links report, but visually — useful for layout decisions ("our main button is below the fold; almost all clicks come from the inline text link above").

### Campaign overview — top-line metrics

The campaign **Overview** tab shows the summary:

![Campaign overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/analytics/01-campaign-overview.png)

This is what you screenshot for stakeholder reports — open rate, click rate, unsubscribe rate, all aggregated.

## How the UTMs show up in Google Analytics

After the campaign sends and recipients click, your analytics tool (GA4 / Plausible / etc.) starts recording sessions with the source = `newsletter` / medium = `email` / campaign = `spring_launch_2026`.

In Google Analytics 4: **Reports → Acquisition → Traffic Acquisition** → filter by `Session source / medium = newsletter / email`.

In Plausible: filter by `Source = newsletter` directly on the dashboard.

The UTMs persist across page navigations within the session, so even if the recipient clicks your email → lands on /pricing → navigates to /signup → converts, the conversion is attributed back to the email.

## Common UI signals + fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Click count on AcelleMail report doesn't match analytics-tool sessions | UTM-tagged links going through redirect strip the UTMs at some hop | Verify the destination URL in Preview hovers — should still have UTMs after AcelleMail's click-redirect |
| Multiple campaigns showing up as same source in analytics | Same `utm_campaign` value used for both sends | Use unique `utm_campaign` per send — include week or version number |
| utm_source = `(direct)` in GA despite UTMs being set | Analytics drops UTMs on HTTPS → HTTP redirects, OR JS-driven SPAs that don't read URL params | Make sure your destination uses HTTPS end-to-end; if SPA, configure your analytics tool to read query string on every route change |
| Want to A/B test header vs footer CTA | Use `utm_content` differently per variant — `header_cta` vs `footer_cta` | But: AcelleMail's campaign-level setting applies one `utm_content` to all links; need link-level override (see Advanced) |

## Common UTM conventions worth adopting

| Field | Recommended pattern | Why |
|---|---|---|
| `utm_source` | One per-channel identifier, lowercase, hyphenated | Stays consistent across years; "newsletter" works forever |
| `utm_medium` | Always `email` for AcelleMail sends | Standard convention; analytics tools group it predictably |
| `utm_campaign` | `{campaign_type}_{date_or_version}_{variant}` | `cart_abandon_2026q2_a` is parseable + sortable |
| `utm_content` | Variant identifier when A/B testing | `header_cta` vs `footer_cta`; `button_red` vs `button_green` |
| `utm_term` | Only when running paid keyword tests in parallel | Usually leave blank for organic email |

<details>
<summary><strong>Advanced: per-link overrides, dynamic UTM via merge tags, integration patterns</strong></summary>

When the campaign-level UTMs aren't granular enough — typically when you're A/B testing within a single send, or when you need per-recipient personalization in the UTM itself — drop to link-level overrides.

**Per-link override in the email body:**

Click any link in the AcelleMail builder → right panel → **Tracking** section. Toggle "Override campaign UTMs for this link". Set per-link values:

```
header CTA:
  utm_source:   newsletter
  utm_medium:   email
  utm_campaign: spring_launch_2026
  utm_content:  header_cta

footer CTA:
  utm_source:   newsletter
  utm_medium:   email
  utm_campaign: spring_launch_2026
  utm_content:  footer_cta
```

This gives you two click reports in your analytics tool, both attributed to spring_launch_2026, but you can break down which CTA pulled more.

**Dynamic UTM via merge tag** (per-recipient personalization):

Write the link directly in HTML mode with the merge tag in the URL:

```html
<a href="https://example.com/upgrade?utm_source=newsletter&utm_medium=email&utm_campaign=spring_launch&utm_term={{ subscriber.signup_plan }}">
  Upgrade your plan
</a>
```

Now `utm_term` = `pro` or `enterprise` per recipient. Useful for cohort analysis in GA4: "did the click-to-upgrade rate differ across plan tiers?"

**Server-side click-to-conversion attribution:**

If you're running your own backend (e.g. a Laravel app, a Next.js app), capture UTMs server-side at first request and persist them in the session or a cookie:

```php
// Laravel middleware example
if ($request->has('utm_source')) {
    session([
        'first_touch' => [
            'source' => $request->get('utm_source'),
            'medium' => $request->get('utm_medium'),
            'campaign' => $request->get('utm_campaign'),
            'landed_at' => now(),
        ],
    ]);
}
```

Then attach to the conversion event when the user signs up or purchases. This sidesteps analytics-tool quirks (especially the GA4 referrer-detection limits) for your most important conversions.

**Integration with Plausible / Fathom (privacy-friendly analytics):**

Both read the standard UTM query-string params with no extra configuration. Filter the dashboard by **Source = newsletter** to see all email-driven traffic; further filter by **Campaign = spring_launch_2026** for per-campaign attribution. No GA4 setup ceremony required.

**AcelleMail API to populate UTM values dynamically per campaign:**

If you create campaigns programmatically (e.g. from a marketing-automation backend), the campaign update endpoint accepts the UTM fields:

```bash
curl -X PATCH "https://acellemail.com/api/v1/campaigns/{campaign_uid}" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "track_open": true,
    "track_click": true,
    "url_tags": {
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "weekly_digest_w17",
      "utm_content": "auto_generated"
    }
  }'
```

Useful for: triggered campaigns where the campaign name encodes a date (`weekly_digest_w17` → week 17), automated A/B variant naming, or syncing UTM conventions across many campaigns in bulk.

**Disabling AcelleMail's click-redirect** — by default, AcelleMail's tracker rewrites every link to a tracking redirect (e.g. `https://acellemail.com/c/abc123/d/xyz`) so it can count the click before forwarding. The UTMs survive the forward. If you need raw direct links (e.g. for a press release where the URL should be pristine), turn off click tracking per campaign: campaign settings → **Tracking** → uncheck **Track click** → UTMs are still appended, but no redirect hop.

</details>

## Related articles

- [Email analytics — beyond open and click rates](https://acellesend.com/kb/articles/understanding-email-analytics-and-key-metrics)
- [Email template design — best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)
- [A/B testing email campaigns — best practices](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates)
- [Personalization beyond first name — advanced merge tags](https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags)

---

# Installing AcelleMail on Ubuntu 22.04 LTS

> This guide has moved. The install walkthrough for any server, an Ubuntu 22.04 server included, is Install AcelleMail and Get Started.

- Canonical URL: https://acellesend.com/kb/articles/installing-acellemail-on-ubuntu-2204
- Author: Daniel Reyes, Technical Editor
- Category: Installation & Setup — Server requirements, deployment, Nginx/Apache, PHP config, SSL
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-25 · Last updated: 2026-10-06
- Reading time: 1 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## This guide has moved

**👉 Please read the install guide here: [Install AcelleMail and Get Started](https://acellesend.com/kb/articles/install-acellemail-and-get-started).**

That one guide applies to any server, an Ubuntu 22.04 server included. It walks through the installation wizard step by step (system check, site and admin details, database, cron job) and then the first sending server and plan. It is the install walkthrough we keep current, so follow it instead of a server-specific copy that drifts out of date.

---

# Bulletproof CTA Buttons in AcelleMail (Outlook-Safe)

> AcelleMail's drag-and-drop Button widget generates Outlook-safe VML automatically. This guide covers when the widget is enough, when you need hand-coded VML, and the exact pattern for custom HTML templates.

- Canonical URL: https://acellesend.com/kb/articles/bulletproof-cta-buttons-for-outlook
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Design & Content — HTML email, responsive design, dark mode, accessibility, content strategy
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-01-18 · Last updated: 2026-08-15
- Reading time: 7 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The cheapest way to ruin an email design is a CTA button that looks great in Apple Mail and Gmail — then renders as plain blue text in Outlook on Windows. This is **not your fault** — it's because Outlook 2007-2019 uses Microsoft Word as its HTML rendering engine, which ignores CSS `border-radius`, `background-color` on `<a>` tags, and most modern CSS.

The fix is **VML (Vector Markup Language)** — Microsoft's pre-HTML5 graphics language that Word respects. AcelleMail's drag-and-drop Button widget generates this automatically. If you hand-code custom HTML, you write it yourself.

## Path 1 — Use the Button widget (recommended)

If you build emails in AcelleMail's drag-and-drop builder, you don't need to think about VML. The **Button widget** (one of the 12 Basic widgets) generates a bulletproof button — Outlook-safe HTML with VML fallback baked in.

How:

1. Open your campaign at the **Template** step
2. Click **Open email builder**
3. From the right palette, drag **Button** onto the canvas
4. Click the placed button → side panel opens
5. Configure: text, URL, colour, corner radius, padding
6. Save

The builder handles every Outlook quirk for you. **For 95% of campaigns, this is the right answer.**

See the [first-campaign walkthrough](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail) for the Template-step screenshot showing the button widget in context.

## Path 2 — Hand-coded VML (custom HTML templates)

If you import a custom HTML email (Template step → *Change template* → *Use HTML editor* → paste your own HTML), AcelleMail doesn't post-process the markup. **You write the bulletproof code yourself.**

The canonical bulletproof button pattern:

```html
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
  xmlns:w="urn:schemas-microsoft-com:office:word"
  href="https://yoursite.com/cta?utm_source=newsletter&utm_medium=email&utm_campaign=spring_sale_2026&utm_content=primary_cta"
  style="height:44px; width:220px; v-text-anchor:middle;"
  arcsize="10%"
  fillcolor="#0066cc">
  <w:anchorlock/>
  <center style="color:#ffffff;font-family:Arial,sans-serif;font-size:16px;font-weight:bold;">
    Shop the sale
  </center>
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-->
<a href="https://yoursite.com/cta?utm_source=newsletter&utm_medium=email&utm_campaign=spring_sale_2026&utm_content=primary_cta"
   style="background-color:#0066cc;
          border-radius:4px;
          color:#ffffff;
          display:inline-block;
          font-family:Arial,sans-serif;
          font-size:16px;
          font-weight:bold;
          line-height:44px;
          padding:0 24px;
          text-decoration:none;">
  Shop the sale
</a>
<!--<![endif]-->
```

How it works:

- **`<!--[if mso]>...<![endif]-->`** is a Microsoft Office (Word/Outlook) conditional comment. Code inside is ONLY parsed by Outlook desktop. Other clients ignore it.
- **`<v:roundrect>`** is the VML shape Outlook respects — a rounded rectangle with `fillcolor` and `arcsize`.
- **`<!--[if !mso]><!-->...<!--<![endif]-->`** is the inverse — code visible to every client EXCEPT Outlook desktop.
- The plain `<a>` inside the second block uses normal CSS that Apple Mail / Gmail / Outlook web understand.

End result: Outlook sees the VML; every other client sees the CSS `<a>`. Same visual outcome.

## Key VML attributes — what each does

| Attribute | Effect | Typical value |
|---|---|---|
| `href` | Click destination URL (include UTMs!) | Full URL with query string |
| `style="height:44px"` | Button height in pixels | 40-48px (tappable on mobile) |
| `style="width:220px"` | Button width | Match your CSS `<a>` padding visually |
| `arcsize` | Corner radius as % of height | 10% ≈ 4px radius; 50% = pill shape |
| `fillcolor` | Background colour | Match your CSS `background-color` |
| `v-text-anchor:middle` | Vertical centre text | Always set |
| `<w:anchorlock/>` | Make the entire box clickable | Always include |

## Match the CSS and VML values

Both blocks render in different clients but should look IDENTICAL. Match these pairs:

| CSS (`<a>`) | VML (`<v:roundrect>`) | Why |
|---|---|---|
| `background-color: #0066cc` | `fillcolor="#0066cc"` | Same button color |
| `border-radius: 4px` | `arcsize="10%"` (for 40px-tall button) | 10% of 40px = 4px |
| `line-height: 44px` | `style="height:44px"` | Same height |
| `padding: 0 24px` | `style="width:220px"` (≈ text width + 48px padding) | Visual width match |
| `font-family: Arial...` | `style="font-family:Arial..."` (inside `<center>`) | Same font |
| `color: #ffffff` | `style="color:#ffffff"` (inside `<center>`) | Same text color |

If the two blocks disagree visually, your design feels "off" in Outlook only — a hard bug to spot.

## When modern Outlook handles border-radius natively

Outlook for Mac (any version) and **Outlook for Windows "New Outlook"** (2022+) DO support modern CSS including `border-radius`. They render the `<a>` correctly without VML.

**Distribution of Outlook versions** (rough):

| Version | % of Outlook opens | Renders VML | Renders modern CSS |
|---|---|---|---|
| Outlook 2007-2019 desktop | ~30-40% | ✅ Yes | ❌ No |
| Outlook 2021 desktop | ~10% | ✅ Yes | Partial |
| "New Outlook" desktop (2022+) | ~10% | Ignored | ✅ Yes |
| Outlook for Mac | ~5% | Ignored | ✅ Yes |
| Outlook web | ~30-40% | Ignored | ✅ Yes |

The 30-40% on legacy desktop is **the reason VML still matters**. Audiences shrink as Microsoft migrates users, but until that's <5%, write the VML fallback.

## AcelleMail-specific gotcha — preserve raw HTML

If you paste bulletproof HTML into AcelleMail's HTML editor, **AcelleMail's compile pipeline shouldn't touch your VML** — but always test. Send yourself a test campaign and view in real Outlook (or use a multi-client previewer like Litmus).

The places where AcelleMail's pipeline might interfere:

| Pipeline stage | Could break | Check |
|---|---|---|
| Spintax processing | `{...|...}` patterns inside CSS | Wrap CSS curly braces in spintax-safe escaping (rare in buttons) |
| Click-tracking wrap | Replaces `href` with a redirect URL | UTMs should pass through; verify |
| Inline-style copying | Sometimes strips Outlook-specific attributes | Test in actual Outlook |

If something breaks, file a bug. The drag-and-drop Button widget is the safer path until then.

## Save as a reusable template block

The bulletproof pattern is verbose. Save it once + reuse forever:

1. Build one email with the bulletproof button (using the canonical pattern above)
2. **Templates → + Create template** → save this email as a reusable template
3. Future campaigns: pick the template → swap out the `href` + text → save
4. Or: save just the button HTML block separately (in your team wiki) for paste-and-replace into custom HTML emails

After one round of setup, every future button reuses the proven code.

## Common issues

| What you see | What to do |
|---|---|
| Button looks correct in Apple Mail, blue underlined text in Outlook | Outlook is ignoring your CSS. Add the VML fallback (or switch to the Button widget). |
| Button text is centered horizontally but stuck at top in Outlook | Missing `v-text-anchor:middle`. Add it. |
| Button "outline" shows in Outlook even though you set `border: none` | Outlook adds default border to `<a>` tags. Use the VML `<v:roundrect>` path — it has no inherent border. |
| Modern "New Outlook" shows the button twice (once VML, once CSS) | Your conditional comments are malformed. `<!--[if mso]>` must wrap the VML; `<!--[if !mso]><!-->` must wrap the CSS. |
| Button width different in Outlook vs Gmail | VML width is fixed pixels; CSS `<a>` width is content + padding. Match by setting VML width = (CSS text width) + (CSS left+right padding). |
| Button text wraps in Outlook but not Gmail | Outlook wraps if text + padding exceeds VML width. Increase the VML `width` value. |

## What NOT to do

- **Don't put your CTA in a background image.** Outlook ignores background images, AND image-blocking by default in some clients means subscribers see nothing.
- **Don't use a button image** (PNG/JPG button with the text rendered as graphics). Image-blocking shows alt-text, font scaling won't work, and accessibility is worse.
- **Don't skip the VML for B2B audiences.** Outlook desktop is dominant in enterprise. Skipping the fallback breaks 40%+ of business audiences.
- **Don't test in just one client.** Send a test, open in real Apple Mail, real Gmail, real Outlook desktop, real Outlook web. Catching button rendering issues at design time costs minutes; catching after launch costs the campaign.

## Related articles

- [Send Your First Campaign: 5-Step Wizard Walkthrough](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail)
- [Email Rendering Across Clients](https://acellesend.com/kb/articles/understanding-email-client-rendering-differences)
- [Email Copy That Converts](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts)
- [Setting Up UTM Parameters for Campaign Tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking)

---

# IP Warmup Schedule — Ramping a New Sending Server in AcelleMail

> A fresh sending IP has zero reputation. Send 50,000 emails on day one and every major mailbox provider routes you to spam. AcelleMail's Warmup Strategies grow your volume gradually using one of three presets. This guide walks the admin UI plus a realistic ramp plan.

- Canonical URL: https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers
- Author: Lena Hart, Email Deliverability Editor
- Category: Sending & Deliverability — SMTP setup, bounce handling, sender reputation, IP warmup, throttling
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-01-18 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why a new IP needs ramping

When you provision a new sending IP — a fresh Amazon SES verified domain, a new dedicated IP from your ESP, a brand-new SMTP relay — receiving servers (Gmail, Outlook, Yahoo) have no reputation data on it yet. Their default treatment of an unknown IP is conservative: a brand-new IP suddenly pushing high volume looks like a compromised host, so the mail lands in spam or gets rejected.

Warmup means sending in small batches at first and growing the daily volume gradually over several weeks. Each clean batch teaches the receiving servers "this IP sends legitimate mail at this volume." Once you have earned a baseline reputation, full volume becomes sustainable.

Skip warmup and you can be filtered or blocked for weeks while reputation slowly recovers from a bad first impression.

## What AcelleMail gives you

Two controls do the work, and you do not need to write any code or babysit a spreadsheet:

1. **Sending limit** (per sending server) — a cap on how many emails AcelleMail hands to that server per unit of time (per minute / hour / day). This is the manual lever.
2. **Warmup Strategies** (admin) — a reusable plan that automatically grows the daily volume day over day, with safety rules that pause the ramp if bounce or complaint rates climb. You build it once and attach it to any new server.

The rest of this guide walks both, starting with the per-server limit and ending with the automated strategy.

## Before you start

- The Warmup Strategy feature lives in the **admin area**, not the customer dashboard — you need operator access to build a strategy and attach it to a server.
- Have the new sending server already added and tested (use **Test connection** / send a test email from the server detail before warming it).
- Warm using your **most engaged subscribers** first. A segment of people who opened recently produces high open rates, which lifts reputation fastest. Starting with cold or re-engagement segments makes warmup take far longer.

## A realistic ramp to aim for

A warmup plan grows from a small starting volume toward your target. The exact numbers depend on your list size and how aggressive you can afford to be, but a typical ramp looks like this:

| Week | Daily volume range | What to watch |
|---|---|---|
| Week 1 | 1,000 → 8,000 | Bounce rate under 2%, complaint rate under 0.1% |
| Week 2 | 15,000 → 50,000 | First reputation check at Gmail Postmaster Tools |
| Week 3 | 100,000+ | Hold if complaint rate creeps above 0.15% |
| Week 4 | Full volume | Warmup complete once signals stay clean |

The point of the AcelleMail strategy presets below is that you do not have to manage this table by hand — the strategy generates a per-day plan for you and enforces it. The exact day-by-day curve it produces is shown in the **Strategy preview** chart when you build the strategy.

## Step 1 — Set the per-server sending limit

### Open the sending-server detail

In AcelleMail's sidebar, open **Sending → Sending servers**. The list shows every server connected to this account with its name, quota chip, status, and the date it was created:

![Customer sending-server list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/01-customer-server-list.png)

Click into the row you want to configure. The detail page is organised into a **Connection** card (host / credentials) and a **Configuration** card (server name, defaults, sending limit), with **Test connection** and a test-send action in the toolbar:

![Server detail — Connection + Configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/02-server-detail-full.png)

In the **Configuration** card, the **Sending limit** dropdown caps how many emails AcelleMail will hand to this server per unit of time (per minute / per hour / per day). For the very first day of warmup, set this low — around 1,000 per day. AcelleMail enforces the cap: when the rolling-window counter hits the limit, the queue holds back until the window slides.

If you only have a few servers and a small list, you *can* warm up by raising this dropdown by hand each day following the ramp table above. For anything larger, use a Warmup Strategy (next step) so the increase happens automatically.

## Step 2 — Build a Warmup Strategy (admin)

In the admin sidebar, open **Warmup Strategies**. This screen lists every reusable warmup plan with its name, preset, warmup length, target volume, risk level, and an Active / Inactive status chip:

![Admin Warmup Strategies list](https://acellesend.com/plugins/acelle/landing/images/help/kb/sending-servers/16-admin-warmup-strategies.png)

Click **Add strategy** to open the create form. It is organised in sections.

**1. Basic information.** Give the strategy a clear internal name (for example *Balanced — New Domains*) and a short description of when to use it. Then pick a **Preset**. AcelleMail ships three:

| Preset | Behaviour | Use it for |
|---|---|---|
| **Cautious** | Slow ramp, lower daily increment, tighter safety thresholds | A brand-new IP with no prior reputation — buys deliverability insurance |
| **Balanced** | Moderate, steady linear ramp — the recommended default | Most warmups; a sensible middle ground |
| **Aggressive** | Faster growth toward full volume | A known-good IP on a new account, or when you are under time pressure |

The preset just fills in sensible starting values — you can still customise every field below it.

**2. Volume plan.** This is where the ramp shape comes from:

- **Starting volume** — emails allowed on day 1.
- **Growth strategy** — *Linear* (add a fixed amount each period) or *Exponential* (multiply each period).
- **Daily increment** (for linear) or **Exponential factor** (for exponential — e.g. 1.20 means +20% per step).
- **Quota type** — the time unit the growth is counted in (Daily / Hourly / Monthly).
- **Limit type** — what ends the warmup: a **Per day cap**, a cumulative **Target volume**, or **Stop after days**.
- **Send on weekends** — leave off if your audience does not engage on Saturday/Sunday.

**Safety rules.** Keep **Enable safety checks** on and **Pause on negative signals** on while reputation is still forming. Set **Max bounce rate** and **Max complaint rate** to the thresholds at which the strategy should automatically slow down or pause the ramp instead of pushing through bad signals.

As you fill the form, the **Strategy preview** panel updates live — it shows the **Start volume**, the **Estimated full warmup** day count, a **Risk level** (Low / Medium / High), and a chart of planned daily volume against cumulative volume. Use the preview to sanity-check that the ramp lands where you want before you save.

Click **Create strategy**. It appears in the list as Active.

## Step 3 — Attach the strategy to the new server

Strategies are attached from the **admin** sending-server detail page (the customer-facing server detail does not expose warmup). Open the server in the admin Sending servers area and find its **Warmup** section. Use the **Select strategy** dropdown to pick the strategy you just built, then click **Save selection**.

The server now follows that plan automatically — the daily allowance grows day over day according to the strategy, and the safety rules hold the ramp if bounce or complaint rates breach your thresholds. You no longer have to raise the **Sending limit** dropdown by hand.

## Step 4 — Watch the warmup statistics

From the server's Warmup section, click **View Statistics** to open the warmup stats page for that server. It charts the planned ramp against what actually sent, day by day, with **Total Sent** / **Total Failed** counts and which warmup day the server is currently on.

Check it daily during the ramp (it takes about five minutes):

1. **Planned vs actual** — the actual line should track the planned line. A big gap usually means the queue can't keep up or the source list is smaller than the plan expects.
2. **Failed count** — a rising failed count is your early warning. Cross-check the campaign **Bounce log** to see *why* (hard vs soft, and the receiving server's reason text).
3. **External reputation** — pair this with [Gmail Postmaster Tools](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough) and [Microsoft SNDS](https://acellesend.com/kb/articles/microsoft-snds-walkthrough). IP reputation there should climb over the warmup; if it stays poor after the first couple of weeks, the problem is content or list quality, not volume.

If you ever need to restart a server's warmup from day one, the **Reset Warmup** button on the same screen does exactly that (it asks for confirmation first).

## What to do if warmup hits a wall

| Signal | What it means | Action |
|---|---|---|
| Bounce rate climbing to 4–6% | The source segment is probably stale | Hold the current daily volume for a few days. Run list verification before resuming. |
| Bounce rate 8%+ | Likely a bad import | Pause the warmup. Audit the list source, clean it, then resume. |
| Complaint rate 0.2%+ | Content or subject-line problem, not volume | Hold volume and review the campaigns going out during warmup. |
| Gmail/SNDS reputation still poor after ~2 weeks | Engagement is too low | Narrow to your most-engaged segment for several days, then re-evaluate. |
| Daily quota exhausted by mid-morning | The sending limit cap is set lower than the day's plan needs | Raise the per-server **Sending limit**, or warm a second server in parallel to share the load. |

If you run several new IPs at once, stagger them: warm one fully before introducing the next, a few days apart. That way a problem on one IP does not coincide with low reputation on all of them.

## Common UI questions

| Question | Answer |
|---|---|
| Where are the warmup presets? | Admin → **Warmup Strategies** → **Add strategy** → the **Preset** dropdown (Cautious / Balanced / Aggressive). |
| Why is there no warmup option on my customer dashboard? | Warmup Strategies are an admin feature. Set them up as an operator, then attach them to servers from the admin sending-server detail. |
| My ramp didn't increase yesterday | If you were raising the **Sending limit** by hand, you missed a day. Attach a Warmup Strategy so the increment is automatic. |
| Can I change a preset's numbers? | Yes — the preset only fills in starting values. Every field in the Volume plan and Safety rules sections is editable before you save. |

## What to do after

- Once warmup completes and signals stay clean, move from the cautious limit to your normal sending volume.
- Keep an eye on reputation monthly — see [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring).
- If you run multiple senders, set per-server limits and routing — see [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers).

## Related articles

- [Configuring multiple sending servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers)
- [Sender reputation monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring)
- [Sending throttling strategies](https://acellesend.com/kb/articles/sending-throttling-strategies)
- [IP warmup by ISP](https://acellesend.com/kb/articles/ip-warmup-by-isp)
- [Gmail postmaster tools walkthrough](https://acellesend.com/kb/articles/gmail-postmaster-tools-walkthrough)
- [Microsoft SNDS walkthrough](https://acellesend.com/kb/articles/microsoft-snds-walkthrough)

---

# Dedicated vs Shared Sending IP: Which to Pick

> AcelleMail sends through whatever sending server you connect, so the dedicated-vs-shared IP choice is really a choice about your sending provider. Here's how to decide, and where it lives in AcelleMail.

- Canonical URL: https://acellesend.com/kb/articles/dedicated-vs-shared-ip-address
- Author: Lena Hart, Email Deliverability Editor
- Category: Email Infrastructure — Mail servers, MTA configuration, relay setup, IP management, monitoring
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-01-16 · Last updated: 2026-08-15
- Reading time: 3 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

A common question when setting up AcelleMail: should I send from a **dedicated** IP (one that's only mine) or a **shared** IP (a pool used by many senders)? The honest answer is that AcelleMail itself doesn't own the IP — it sends through whatever **sending server** you connect (your own SMTP, Amazon SES, SendGrid, Mailgun, and so on). So the dedicated-vs-shared choice is really a choice about your *sending provider*, and you act on it where you configure that provider.

This article helps you make the call and shows where it lives in the app.

## The short version

- **Shared IP** — your mail goes out over a pool of IPs the provider also uses for other senders. The pool already has an established reputation, so a low-volume or inconsistent sender often *benefits* from it. You don't warm it up, you don't babysit it. The downside: another sender's bad behaviour can affect the pool, and you have less individual control.
- **Dedicated IP** — an IP that's only yours. Its reputation reflects only your sending, which is exactly what you want once you send enough to *maintain* a reputation. The catch: a brand-new dedicated IP has *no* reputation and must be warmed up gradually, and if you send too little or too sporadically, mailbox providers never build enough signal to trust it.

## How to decide

Use volume and consistency, not a magic number:

- **Send a lot, regularly?** A dedicated IP gives you control and isolates your reputation from other senders. Worth it.
- **Send modestly, or in irregular bursts?** A shared pool's established reputation usually serves you better than a cold dedicated IP you can't keep warm.
- **Just starting out?** Begin on shared. Move to dedicated once your volume is steady enough to justify warming and maintaining it.

These are rules of thumb — your provider's own guidance on minimum volume for a dedicated IP is the figure to follow, since it's their pool and their thresholds.

## Where this lives in AcelleMail

You don't toggle "dedicated vs shared" inside AcelleMail — you choose it at the provider and then connect that provider as a **sending server**:

1. Decide at your provider (e.g. lease a dedicated IP in your Amazon SES / SendGrid account, or stay on their shared pool).
2. In AcelleMail, go to **Sending → Sending servers** and add/configure that provider (see [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail)).
3. If it's a **new dedicated IP**, warm it up before full volume — AcelleMail's warmup strategy ramps your daily volume gradually. See [Domain & IP Warmup](https://acellesend.com/kb/articles/domain-warmup).
4. Keep sending consistent and watch your reputation — see [Sender Reputation Monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring).

## Common questions

**Can I run dedicated for marketing and shared for transactional?** Yes — connect two sending servers in AcelleMail (one provider/IP per server) and route campaigns to the one you want. See [Configuring Multiple Sending Servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers).

**Does a dedicated IP guarantee the inbox?** No. IP reputation is one signal; authentication (SPF/DKIM/DMARC), list quality, and engagement matter just as much. A dedicated IP only *isolates* your reputation — you still have to earn it.

## Related articles

- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — connect the provider that owns the IP
- [Configuring Multiple Sending Servers](https://acellesend.com/kb/articles/configuring-multiple-sending-servers) — mix providers/IPs per campaign
- [Domain & IP Warmup](https://acellesend.com/kb/articles/domain-warmup) — ramp a new dedicated IP safely
- [Sender Reputation Monitoring](https://acellesend.com/kb/articles/sender-reputation-monitoring) — keep an eye on the IP you chose

---

# Zapier Integration Guide for AcelleMail

> AcelleMail doesn't ship a native Zapier app, but the REST API + webhook system gives you everything you need to wire AcelleMail to the 7,000+ apps on Zapier. This guide walks both directions — AcelleMail as a Zapier action (POST to API), and AcelleMail as a Zapier trigger (webhook → Zap). Plus 6 production-tested Zap recipes.

- Canonical URL: https://acellesend.com/kb/articles/zapier-integration-guide
- Author: Tom Becker, Developer Editor
- Category: Integrations — Stripe, PayPal, WordPress, WooCommerce, Zapier, webhooks, API
- Content type: guide · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-15 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

AcelleMail doesn't ship a native Zapier app (as of 2026), but the [REST API](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) + [webhook system](https://acellesend.com/kb/articles/webhook-events-reference) give you everything you need to wire AcelleMail to **the 7,000+ apps on Zapier** — without writing any code. This guide walks both directions:

- **AcelleMail as a Zapier action** — Zap fires → POST to AcelleMail API → subscriber added / campaign sent / etc.
- **AcelleMail as a Zapier trigger** — AcelleMail event fires → webhook to Zapier → Zap runs

Plus six production-tested Zap recipes that cover most use cases.

## Prerequisites

- A Zapier account (free tier works for 2-step Zaps; paid for multi-step)
- An AcelleMail [API token](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api#step-1--get-your-api-token)
- An AcelleMail list UID (for the subscriber-add action)
- Comfortable with Zapier's "Webhooks by Zapier" built-in app — it's the bridge

## Direction 1 — AcelleMail as a Zapier action (Zap → AcelleMail)

Use this when: an external event (form submission, Stripe payment, calendar booking, CRM record creation) should add or update a subscriber in AcelleMail.

### Setup

1. In Zapier, create a new Zap
2. **Trigger:** your external app (Typeform / Stripe / Calendly / etc.)
3. **Action:** choose **Webhooks by Zapier → POST**
4. Configure:
   - **URL:** `https://mail.example.com/api/v1/subscribers`
   - **Payload Type:** `JSON`
   - **Data:**
     - `MAIL_LIST_UID` → your list UID (a static value, type it in)
     - `EMAIL` → map from the trigger output (e.g. `{{1.email}}` from Typeform)
     - `FIRST_NAME` → map from the trigger
     - `LAST_NAME` → map from the trigger
     - any custom fields → map similarly
   - **Headers:**
     - `Authorization` → `Bearer YOUR_API_TOKEN`
     - `Accept` → `application/json`
     - `Content-Type` → `application/json`
5. Test the action — Zapier sends a test POST; you should see HTTP 200 with the new subscriber JSON in the response
6. Turn the Zap on

> **Field names are UPPERCASE.** `EMAIL`, `FIRST_NAME`, `LAST_NAME`, `MAIL_LIST_UID` — not lowercase. Older guides get this wrong; AcelleMail's API rejects lowercase field names.

### Tag a subscriber from a Zap

For tagging (instead of subscribing), use the tag-add endpoint:

```
POST https://mail.example.com/api/v1/subscribers/{{subscriber_id}}/add-tag
Headers: Authorization: Bearer YOUR_TOKEN
Body: { "tag": "stripe-customer" }
```

You'll typically use a "Find Subscriber by Email" GET first (`/api/v1/subscribers/email/{{email}}`) to get the `id`, then POST the tag-add.

## Direction 2 — AcelleMail as a Zapier trigger (AcelleMail → Zap)

Use this when: an AcelleMail event (new customer signup on your SaaS, plan change, subscription cancellation, automation step reached) should fire a Zap.

### Setup

1. In Zapier, create a new Zap
2. **Trigger:** **Webhooks by Zapier → Catch Hook**
3. Copy the Webhook URL Zapier generates — looks like `https://hooks.zapier.com/hooks/catch/12345/abcde/`
4. In AcelleMail Admin → **Webhooks → New**:
   - **Event:** pick one (see below)
   - **Request URL:** paste the Zapier URL
   - **Method:** POST
   - **Body type:** JSON
   - **Auth:** None (Zapier's webhook URL is the auth — it's secret)
5. In AcelleMail, save the webhook
6. Trigger the event (e.g. create a test customer)
7. In Zapier, click **Test trigger** — you should see the webhook payload
8. Add your Zap action steps (notify Slack, write to Google Sheets, etc.)
9. Turn the Zap on

### Available events

Per [webhook events reference](https://acellesend.com/kb/articles/webhook-events-reference), the events are SaaS-lifecycle (not subscriber-lifecycle):

| Event | Params posted | Good Zap use case |
|---|---|---|
| `new_customer` | `customer_id` | Notify Slack #signups; create CRM contact |
| `new_subscription` | `customer_id`, `plan_id` | Track in revenue dashboard; thank-you SMS |
| `change_plan` | `customer_id`, `old_plan_id`, `new_plan_id` | Update CRM deal size; trigger upgrade-email |
| `cancel_subscription` | `customer_id`, `plan_id` | Schedule a win-back outreach; flag for retention call |
| `terminate_subscription` | `customer_id`, `plan_id` | Tag CRM contact as "churned" |
| `automation_webhook` | `automation_id` | Fire arbitrary external action from within an Acelle automation |

> **`subscriber.created` / `campaign.opened` / `campaign.clicked` are NOT in the customer-lifecycle webhook system.** For subscriber events, use per-campaign webhooks (Campaign → Webhooks tab) instead. For real-time subscriber sync, the most reliable pattern is the webhook hitting Zapier's Catch Hook.

## 6 Production-tested Zap recipes

### 1. Typeform → AcelleMail subscribe

When a customer fills out your "Get the lead magnet" Typeform, add them to your AcelleMail "Lead Magnet" list with the form answers as custom fields.

- **Trigger:** Typeform → New Entry
- **Action:** Webhooks → POST to `/api/v1/subscribers`
- Map Typeform answers to `EMAIL`, `FIRST_NAME`, `LEAD_MAGNET_TOPIC`, etc.

### 2. Stripe → tag AcelleMail subscriber as customer

When a Stripe Checkout completes, find the customer in AcelleMail by email and add a `customer-paid` tag.

- **Trigger:** Stripe → New Successful Checkout
- **Step 1 Action:** Webhooks → GET `/api/v1/subscribers/email/{{customer_email}}` → extract `id`
- **Step 2 Action:** Webhooks → POST `/api/v1/subscribers/{{id}}/add-tag` with `{"tag": "customer-paid"}`

This works great as a budget alternative to the [direct WP+WC integration](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) for non-WP stores.

### 3. Calendly → AcelleMail pre-event sequence

When someone books a call via Calendly, add them to a list that triggers a pre-event reminder + post-event follow-up.

- **Trigger:** Calendly → New Invitee
- **Action:** Webhooks → POST `/api/v1/subscribers`
- `MEETING_TIME` custom field captures the booked slot for personalised reminders

### 4. AcelleMail new_customer → Slack notification

Every time a new SaaS customer signs up on your AcelleMail-powered platform, post to a Slack channel.

- **Trigger:** Webhooks → Catch Hook (your AcelleMail webhook on `new_customer`)
- **Action:** Slack → Send Channel Message — `New AcelleMail customer: customer_id={{customer_id}} — investigate in Admin → Customers`

### 5. AcelleMail change_plan → update HubSpot deal

When a customer upgrades or downgrades, sync to HubSpot.

- **Trigger:** Webhooks → Catch Hook (`change_plan` event)
- **Step 1:** Webhooks → GET `/api/v1/customers/{{customer_id}}` to enrich payload (Acelle webhooks only send IDs, not full customer data)
- **Step 2:** HubSpot → Update Deal — set deal value to new plan's monthly value

### 6. AcelleMail cancel_subscription → schedule win-back

When a customer cancels, automatically add them to a "win-back" list 30 days later (don't email immediately — they're irritated).

- **Trigger:** Webhooks → Catch Hook (`cancel_subscription`)
- **Step 1:** Delay (Zapier built-in) → 30 days
- **Step 2:** Webhooks → POST `/api/v1/subscribers` with `MAIL_LIST_UID=WIN_BACK_LIST_UID`

## Multi-step Zaps (paid tier)

Many recipes above use multi-step Zaps — the free Zapier tier limits you to 2-step (Trigger → 1 Action). Paid plans unlock 3+ steps, which you need for:

- Find subscriber by email → add tag (2 actions, so 3 steps total)
- Enrich data from a third API → format → POST to AcelleMail (3+ actions)
- Conditional logic (only run for paid customers, only run on weekdays, etc.)

For Acelle-heavy automation, paid Zapier is worth it. Alternative: write a Laravel webhook listener in [extending source code](https://acellesend.com/kb/articles/extending-acellemail-source-code) for the heaviest workflows; use Zapier for the long tail.

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Zapier POST returns 422 with "EMAIL required" | Field name lowercase | Switch to UPPERCASE — `EMAIL` not `email` |
| Zapier POST returns 401 | Authorization header missing or wrong format | Must be exactly `Bearer YOUR_TOKEN` (note the space) |
| Acelle webhook configured but Zapier "Test trigger" finds nothing | Webhook fires async — Zapier may not see the test event | Trigger the actual event (create a real customer); Zapier will pick it up |
| Webhook payload missing customer email | Acelle webhooks only carry IDs by default | Add a Webhooks GET step (`/api/v1/customers/{{customer_id}}`) to enrich before next action |
| Zap fires twice for same event | Acelle retried the webhook (your endpoint timed out on first attempt) | Zapier's Catch Hook returns fast — should be safe; check Acelle webhook retry settings |
| Free tier's Catch Hook URL stops working | Free tier of Zapier has limits on monthly Zap runs | Upgrade to paid; or move heavy-volume integrations to direct API |
| `Cannot use the Webhooks app trigger because it requires a paid plan` | Some Zapier webhook actions are paid-only | Catch Hook trigger is on free tier; "Custom Request" action may be paid |
| Action runs out of order | Multi-step Zaps run sequentially but async; ordering not guaranteed for parallel events | Don't depend on strict event ordering; design for idempotency |

## FAQ

**Is there a native Acelle Zapier app coming?** Not announced as of 2026. The Webhooks-bridge pattern works fine; a native app would just save 1-2 setup steps.

**Make (formerly Integromat) / n8n alternatives?** Both work identically — they have HTTP/Webhook connectors that you wire to AcelleMail's REST API. n8n is self-hosted (open source) and often a better fit for an AcelleMail self-hosted user.

**Rate limits?** AcelleMail doesn't impose API rate limits by default. Zapier rate-limits its own API calls per its plan tier (free = ~100 task runs/month). Watch your Zapier Task usage.

**Can I trigger a campaign send from Zapier?** Yes — POST to `/api/v1/campaigns/{uid}/run`. But this is risky (a misfire blasts your list). Most operators trigger campaigns from inside Acelle's UI and use Zapier for subscriber-level events.

**Async webhook delivery + Zapier timeout interplay?** AcelleMail's webhook firing happens via the queue worker — usually within seconds. Zapier's Catch Hook then triggers within ~1 minute on free tier (faster on paid). Total worst-case latency: 1-2 minutes from Acelle event to Zap action fired.

**Can I pass binary data (uploaded files)?** Zapier's Webhooks support multipart/form-data POSTs for file uploads. Use Acelle's `/api/v1/file/upload` endpoint with the file as a form field.

**Webhook security — anyone can hit Zapier's Catch Hook URL?** True. The security comes from URL obscurity (the URL is long random). For added security, send a custom header from Acelle (e.g. `X-Acelle-Source: secret-token`) and add a Filter step in Zapier that only proceeds if the header matches.

## Related articles

- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — API fundamentals
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — full endpoint reference
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — the 6 outbound events
- [Integrating AcelleMail with WordPress and WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) — direct code alternative to Zapier for WP stores
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — when Zapier isn't enough
- [Using API Triggers for Custom Automation Workflows](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) — Acelle's per-automation API trigger endpoint
- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — SaaS-lifecycle webhooks are the main Zap trigger source for operators

---

# Personalization Beyond First Name — Using Merge Tags in AcelleMail

> Drop a subscriber's name, location, last-purchase, or any custom field straight into your email subject and body. The AcelleMail builder has a merge-tag picker that surfaces every field on your list — no syntax to memorize. This guide walks the UI; raw Twig syntax for power users is in the collapsible section.

- Canonical URL: https://acellesend.com/kb/articles/personalization-beyond-first-name-advanced-merge-tags
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-13 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What a merge tag actually does

A merge tag is a placeholder you drop into your email subject or body that AcelleMail replaces — per recipient, at send time — with that subscriber's actual value.

Subject `Hey {{ subscriber.first_name }}, your invoice for July` becomes `Hey Maria, your invoice for July` for Maria, `Hey Tobias, your invoice for July` for Tobias.

The values come from list fields — every list has at least `email` + `first_name` + `last_name`, and you can add unlimited custom fields like `city`, `signup_plan`, `last_purchase_total`.

## Insert a merge tag from the campaign builder

### 1. Start typing in the subject line

In any campaign, the **Subject** field accepts merge tags inline. Click the field and start typing — AcelleMail shows a hint about merge-tag insertion:

![Subject field — merge tag hint visible](https://acellesend.com/plugins/acelle/landing/images/help/kb/merge-tags/01-setup-subject-hint.png)

You can paste a tag directly (`{{ subscriber.first_name }}`) OR open the picker for the full list.

### 2. The merge-tag picker (autosuggest)

Type `{{` in the subject — or open the body builder's right-side panel — and the autosuggest dropdown lists every available tag, scoped to this list's actual fields:

![Merge-tag autosuggest picker](https://acellesend.com/plugins/acelle/landing/images/help/kb/merge-tags/02-merge-tag-autosuggest.png)

Click a tag to insert it. The picker scopes to:

- **Subscriber fields** — every column on your list (`email`, `first_name`, custom fields)
- **System tags** — `{{ unsubscribe_url }}`, `{{ web_view_url }}`, `{{ campaign.name }}`, `{{ send_date }}`
- **Conditional helpers** — `{% if %}` ... `{% endif %}` (Twig syntax — see Advanced)

### 3. Manage the list's custom fields

The picker only shows fields that exist on the list. To add a new field, **Audience → [list] → Fields**:

![List fields configuration](https://acellesend.com/plugins/acelle/landing/images/help/kb/merge-tags/03-list-fields.png)

For each field, set:

- **Tag (the key)** — e.g. `city` becomes `{{ subscriber.city }}` in emails
- **Type** — text / number / date / dropdown / multi-select
- **Required at signup** — appears on the signup form, blocks if blank
- **Default value** — fallback for existing subscribers without this field set

After adding a field, re-run an import (or update via API) to back-fill values for existing subscribers — they won't be back-filled automatically.

## Common UI patterns that work

| What you want | How to write it |
|---|---|
| First name with a fallback if unknown | `Hi {{ subscriber.first_name \| default('there') }}` |
| Full name (joined) | `{{ subscriber.first_name }} {{ subscriber.last_name }}` |
| Date formatting | `{{ subscriber.signup_date \| date('F j, Y') }}` |
| Uppercase a value | `{{ subscriber.city \| upper }}` |
| Truncate a string | `{{ subscriber.bio \| truncate(80) }}` |
| Numeric formatting | `${{ subscriber.lifetime_value \| number_format(2) }}` |

These filters use Twig syntax — the AcelleMail builder renders them at send time. You can test any tag by clicking **Preview** in the campaign builder and toggling the *Use sample subscriber* dropdown:

![Sample subscriber preview](https://acellesend.com/plugins/acelle/landing/images/help/kb/preview/01-campaign-preview.png)

## Common pitfalls + UI fixes

| Symptom | Likely cause | UI fix |
|---|---|---|
| Email shows literal `{{ subscriber.city }}` instead of "Berlin" | Field doesn't exist on this list, OR typo in tag name | Open **Audience → [list] → Fields** — confirm the tag matches exactly |
| First name field empty for half the list | Imported CSV had no `first_name` column | Use `\| default('friend')` filter, OR re-export source + re-import with field mapping |
| Picker doesn't show a field you added | Page cached pre-add | Hard refresh (Cmd-Shift-R / Ctrl-F5), or close + reopen the builder |
| Subject renders blank for some recipients | The field is empty for those subscribers + no `default` filter | Add `\| default('something')` to the merge tag |

<details>
<summary><strong>Advanced: Twig conditionals, loops, and computed merge tags</strong></summary>

AcelleMail's body and subject support the full Twig templating language — including conditionals and loops — for cases the autosuggest picker doesn't cover.

**Conditional content blocks:**

```twig
{% if subscriber.signup_plan == 'enterprise' %}
  <p>Your Enterprise account manager is Sarah — book a call here.</p>
{% elseif subscriber.signup_plan == 'pro' %}
  <p>Pro users get priority support — reply to this email anytime.</p>
{% else %}
  <p>Get faster response times by upgrading.</p>
{% endif %}
```

**Tag-based blocks** (using the multi-value tags field):

```twig
{% if 'vip' in subscriber.tags %}
  <p>VIP early access: <a href="...">claim your slot</a></p>
{% endif %}
```

**Loops over array fields** (when a custom field is JSON-encoded list):

```twig
<ul>
{% for product in subscriber.recent_purchases %}
  <li>{{ product.name }} — ${{ product.price }}</li>
{% endfor %}
</ul>
```

**Computed values inline:**

```twig
{% set discount = subscriber.lifetime_value > 500 ? 20 : 10 %}
Your loyalty discount: {{ discount }}%
```

**Default filter chains** for safety:

```twig
Hi {{ (subscriber.first_name | default(subscriber.email | split('@') | first)) | capitalize }}
```

This says: try `first_name`, fall back to the part of the email before `@`, then capitalize. Bullet-proof against missing fields.

**Per-campaign override fields** — if you need a value computed at send time (e.g. discount code unique per recipient), use the AcelleMail API to populate a custom field just before launching the campaign, OR set up an automation that sets the field when the trigger fires.

For a deeper Twig reference, the upstream syntax docs at [twig.symfony.com](https://twig.symfony.com/doc/3.x/) apply directly — AcelleMail uses Twig 3.x.

</details>

## Related articles

- [Email template design — best practices](https://acellesend.com/kb/articles/email-template-design-best-practices)
- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Setting up UTM parameters for campaign tracking](https://acellesend.com/kb/articles/setting-up-utm-parameters-for-email-campaign-tracking)

---

# Mailchimp → AcelleMail Migration — A Visual Walkthrough

> Export contacts from Mailchimp's Audience screen, import into AcelleMail in 6 clicks. This guide covers the UI on both sides; advanced API-driven migration in the collapsible section for power users.

- Canonical URL: https://acellesend.com/kb/articles/migrating-from-mailchimp-to-acellemail
- Author: Martin Keller, SaaS & Business Editor
- Category: Migration & Comparison — Switching from Mailchimp/Sendinblue, cost comparison, data migration
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-09 · Last updated: 2026-08-15
- Reading time: 5 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Before you start

You'll need:

- A Mailchimp account with audience access (Member, Manager, or Owner role)
- An AcelleMail installation up and running (any version 4.0+)
- ~30 minutes for a list of <50k subscribers; longer for larger or for rebuilding automations

This guide walks the visual flow on both ends. No CLI required for the standard path.

## Export from Mailchimp

In Mailchimp's left sidebar, click **Audience** → **All contacts**. On the contacts page, click **Export Audience** in the top-right toolbar.

Mailchimp prepares a CSV in the background — refresh after ~30 seconds, the **Download** button activates. The file includes: email, every merge field (`FNAME`, `LNAME`, custom), tags (comma-separated), opt-in timestamps, source.

For campaign reports (open/click data), open any campaign → **View Report** → **Export**. This is optional for the migration itself; useful only if you want to import engagement history.
## Import into AcelleMail (the same 6 clicks for every source)

Once you have the CSV exported from your previous platform, the import flow in AcelleMail is identical regardless of where the data came from.

### 1. Open your destination list

In AcelleMail's sidebar, click **Audience** → choose the list that will receive the migrated subscribers (or create a new one — *New list* button top-right).

![Lists index](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/01-lists-index.png)

You'll see a per-list overview card with subscriber counts:

![List overview](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/02-list-overview.png)

### 2. Click "Import" in the list toolbar

The wizard entrypoint is on the list detail page:

![Import entry point](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/03-import-entry.png)

### 3. Upload your CSV

Drop the CSV file from the previous platform into the upload area:

![Upload empty state](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/04-upload-empty.png)

AcelleMail parses the file and confirms detection:

![Upload success](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/05-upload-success.png)

### 4. Map the columns

The wizard auto-detects standard columns (`email`, `first_name`, `last_name`) and shows green *Mapped to EMAIL* chips. Adjust manually for any non-standard column from the source:

![Map columns](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/06-map-columns.png)

### 5. Pick duplicate handling

In the same screen, choose what AcelleMail does when a subscriber already exists in this list:

- **Skip** — keep the existing row, don't overwrite
- **Update** — overwrite name/tags/custom fields with values from the CSV
- **Unsubscribe** — mark existing rows as unsubscribed (rare; used when re-importing an opted-out list)

### 6. Run the import

Click **Start import**. The job runs in the background — close the popup, work elsewhere, return to **Audience → [list] → Import** to see progress:

![Import history](https://acellesend.com/plugins/acelle/landing/images/help/kb/import-csv/07-import-entry-revisit.png)

You'll see *Pending* → *Running* → *Complete* per import job, with rows-imported / rows-skipped / errors counts.

## After the import

- **Verify list count** matches your expected size (rows that failed validation appear in the *Errors* column with a downloadable error CSV).
- **Re-tag if needed** — for behavioural data that doesn't fit a CSV column (e.g. "opened campaign X"), you may need to re-create the tag via segmentation rules.
- **Pause for warm-up** — if the imported list is large (>10k) and your sending IP is new or recently rotated, run a short warm-up campaign to your most-engaged 10% before the full send. See [IP warm-up best practices](https://acellesend.com/kb/articles/ip-warmup-schedule-for-new-sending-servers) for the schedule.

## Re-creating automations

Mailchimp's "customer journeys" map roughly to AcelleMail's **Automation** workflows (sidebar → *Automations*). The trigger types align:

| Mailchimp journey trigger | AcelleMail automation trigger |
|---|---|
| Joins audience | Subscribed to list |
| Tag added | Tag added |
| Campaign sent | Campaign sent |
| Date in field | Date relative |
| API event | Custom event |

Re-build each journey by clicking **Automations → New automation** → pick the equivalent trigger → add the same delays and email steps. Visual builder, no code.

## Merge-tag syntax differences

Mailchimp uses `*|FNAME|*` for merge tags; AcelleMail uses `{{ subscriber.first_name }}`. The AcelleMail email builder has a merge-tag picker (right-side panel in the builder) — click to insert, no syntax to remember.

For raw HTML imports, find/replace patterns:

| Mailchimp | AcelleMail |
|---|---|
| `*|FNAME|*` | `{{ subscriber.first_name }}` |
| `*|LNAME|*` | `{{ subscriber.last_name }}` |
| `*|EMAIL|*` | `{{ subscriber.email }}` |
| `*|UNSUB|*` | `{{ unsubscribe_url }}` |
| `*|ARCHIVE|*` | `{{ web_view_url }}` |

<details>
<summary><strong>Advanced: API-driven migration for large lists or scripted workflows</strong></summary>

For lists >500k or for automation-heavy installs where you want to script the full migration, AcelleMail's REST API supports bulk subscriber creation + tagging.

**Export from Mailchimp via Marketing API:**

```bash
# Mailchimp dc = your datacenter (e.g. us21). API key from Audience → Settings.
curl -X GET "https://us21.api.mailchimp.com/3.0/lists/<list_id>/members?count=10000&offset=0" \
  -u "anystring:<MAILCHIMP_API_KEY>" \
  -o mailchimp-members.json
```

Paginate via `offset` until response `members` is empty.

**Import into AcelleMail via REST API:**

```bash
# AcelleMail API token from /account/api-tokens
ACELLE_TOKEN="..."
ACELLE_LIST_UID="..."

# One-at-a-time create (slow but reliable):
jq -c '.members[] | {email: .email_address, first_name: .merge_fields.FNAME, last_name: .merge_fields.LNAME, tags: [.tags[].name] | join(",")}' mailchimp-members.json \
  | while read subscriber; do
      curl -X POST "https://acellemail.com/api/v1/subscribers" \
        -H "Authorization: Bearer $ACELLE_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"list_uid\":\"$ACELLE_LIST_UID\",\"subscriber\":$subscriber}"
    done
```

For >100k rows, the CSV import path through the UI is still faster (AcelleMail batches the inserts internally) — the API path is for when you need scripted reproducibility (e.g. CI-driven nightly sync from a CRM-of-record).

**Programmatic automation rebuild** is less common — every automation platform has subtly different step semantics, so manual rebuild via the visual builder is usually more reliable than scripting. The AcelleMail automation export/import API exists but mirrors AcelleMail's internal flow JSON, not Mailchimp's journey format.

</details>

## Related articles

- [Importing contacts CSV — best practices and field mapping](https://acellesend.com/kb/articles/importing-contacts-csv-best-practices-and-field-mapping)
- [Building a welcome email series in AcelleMail](https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail)
- [Email list hygiene — clean your list for better deliverability](https://acellesend.com/kb/articles/email-list-hygiene-clean-your-list-for-better-deliverability)
- [Migration from Brevo (Sendinblue) to AcelleMail](https://acellesend.com/kb/articles/migrating-from-brevo-sendinblue-to-acellemail)

---

# Subscription Plan Design Strategy

> The plans you offer drive your revenue more than any other product decision. Too many plans confuse buyers; too few leave revenue on the table. This guide walks the three-tier model (Starter / Growth / Pro) that 80% of email-marketing SaaS products use, the feature-gating principles that maximize upgrade conversions without frustrating low-tier users, the annual-billing discount math, and the free-trial vs freemium decision.

- Canonical URL: https://acellesend.com/kb/articles/subscription-plan-design-strategy
- Author: Martin Keller, SaaS & Business Editor
- Category: SaaS & Multi-tenant — White-label, subscription plans, billing, tenant management, branding
- Content type: guide · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-01-08 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The plans you offer **drive your revenue more than any other product decision**. Too many plans confuse buyers and increase abandonment; too few leave money on the table. This guide walks the **three-tier model** that 80% of email-marketing SaaS products use, the **feature-gating principles** that maximize upgrade conversions without frustrating low-tier users, the **annual-billing math**, and the **free-trial vs freemium** decision.

For the broader GTM context, see [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas).

## The three-tier model

Three plans is the magic number. Two looks under-developed; four+ overwhelms buyers and slows decisions. Three lets you anchor (low-tier), capture (middle "everyone picks this" tier), and upsell (high-tier).

| Tier | Target buyer | Subscribers limit | Price signal | Key feature gate |
|---|---|---|---|---|
| **Starter** | Solo / hobby | up to 1,000 | $15-19/mo | No automation; no custom domain |
| **Growth (THE ANCHOR)** | Small business / in-house marketer | up to 10,000 | $49-79/mo | Automation, segments, A/B testing |
| **Pro / Business** | Agency / SMB at scale | up to 50,000 | $149-199/mo | Priority support, API, dedicated IP option |

The **Growth tier is your anchor** — most buyers will pick it, and you should design it so. Make Starter feel like "I'll outgrow this quickly" + Pro feel like "I'll grow into this someday".

Optional fourth tier (**Enterprise / Custom**) — sales-assisted, no public price. Use when:

- You have customers asking "Do you support 500k+ subscribers?"
- You want a price-anchor that makes Pro feel like a deal
- You have legal/security requirements (SSO, SLA, data residency) that justify custom contracts

## Feature-gating principles

The choice of WHAT to gate at which tier is more important than the prices themselves.

### Good gates (move users to upgrade)

These features are **valuable but not essential** at entry-level. Locking them at Growth+ creates upgrade pressure without breaking the basic experience:

- **Automations** — visible (greyed out) on Starter, locked until Growth. Lets Starter users see "this is here" without using it.
- **Advanced segments** — basic list + recent-engagement segment available everywhere; advanced multi-condition + custom-field segments at Growth+
- **A/B testing** — Growth+
- **Custom sending domain** — Pro+ (deliverability-sensitive operators value this)
- **API access** — Pro+ (engineering-tier customers self-select)
- **Dedicated IP** — Pro+ add-on (justifies $30-50/month extra)
- **Team members / seats** — Starter = 1 seat, Growth = 3, Pro = 10, Enterprise = unlimited
- **Webhook events / Zapier integration** — Growth+
- **White-label / brand removal** — Pro+ (high-margin add-on for agencies)

### Bad gates (frustrate users → churn → bad reviews)

Don't gate features that affect deliverability or basic usability:

- **❌ DKIM signing** — every send needs it; never gate
- **❌ Unsubscribe link** — legal requirement; never gate
- **❌ Bounce processing** — needed for clean lists at any volume
- **❌ Basic templates** — without a starting template, users can't ship anything
- **❌ Basic reporting (opens, clicks)** — minimum needed to evaluate the product
- **❌ Email verification on signup** — anti-spam basic
- **❌ Mobile-responsive templates** — modern minimum

Frustrated free/starter users churn AND leave bad reviews — the negative review damage often exceeds the revenue you'd have captured by force-gating.

## Upgrade-trigger UI placement

Design the in-product experience to surface upgrade prompts at the **right moment**:

| Moment | Trigger | Trigger placement |
|---|---|---|
| Approaching subscriber limit | 90% of plan limit | Dashboard banner + email; "you're at 9,200/10,000 — upgrade to keep adding" |
| User attempts gated action | Click "Create automation" on Starter | Modal with feature preview + upgrade CTA |
| Team-member invite on solo plan | Click "Invite teammate" on Starter | Modal: "Growth plan supports up to 3 teammates" |
| API request from unsupported tier | API call from Starter | API responds with 403 + upgrade URL in error |
| Approaching send-quota | 90% of monthly sends used | Email + in-app warning |
| Trial expiring | T-3 days, T-1 day, expiry | Email sequence with upgrade CTA |

**Don't ambush users.** Show the limit in advance ("you have 800 subscribers; Starter goes to 1,000") rather than blocking them at the limit. Upgrades that feel pressured drive cancellations.

## Annual billing — the 16% discount math

Offer annual billing at **2 months free** = ~16.7% discount. This converts 25-40% of monthly subscribers to annual, with two big wins:

1. **Cash flow** — you collect 10 months upfront instead of 1
2. **Churn reduction** — annual subscribers can't cancel mid-period; effective churn for that year drops to near-zero

Show **annual pricing FIRST** on your pricing page (with a "monthly billing" toggle). The visual default shapes the buying decision:

| | Monthly | Annual (2 months free) |
|---|---|---|
| Starter | $19/mo | **$190/year** (≈$15.83/mo) |
| Growth | $79/mo | **$790/year** (≈$65.83/mo) |
| Pro | $199/mo | **$1,990/year** (≈$165.83/mo) |

Some operators offer 20% off for annual instead of 16.7% — works similarly. Don't go above 25% off — it signals "monthly is overpriced".

## Free trial vs freemium

For email-marketing SaaS, **14-day free trial > freemium**:

### 14-day free trial (recommended)

- No credit card required at signup (lowers conversion friction)
- Full access to Growth tier features for 14 days
- T-3 / T-1 / day-of email reminders to convert
- Auto-downgrades to a generous-but-limited free plan on expiry (catch-and-release)

Why this works for email SaaS: users only get value from sending campaigns to real subscribers. The 14-day window forces them to set up + send their first campaign. Either they get value and convert, or they don't and churn cleanly.

### Freemium (avoid)

- Forever-free tier with limits
- Larger user base but proportionally fewer paying customers
- **Creates support burden** from free users who'll never pay
- **Attracts spammers** who use free tier to abuse your sending IPs

Freemium works for tools where value compounds at low scale (Slack, Notion). Email SaaS doesn't fit that pattern — you'd rather have 500 paying customers than 50,000 free users + 500 paying customers.

If you do offer a free tier, keep it tight:

- < 100 contacts
- < 500 sends/month
- AcelleMail branding visible in sent emails (a "remove branding" upgrade lever)
- No API; no automations; no custom domain
- Card required for trial-to-free downgrade (anti-spam)

## Pricing experiments worth running

Once you have a stable customer base + monthly revenue, A/B test:

1. **Price points** — Growth at $49 vs $79: does the higher price decrease conversion enough to offset the higher revenue per customer?
2. **Anchor pricing** — show Pro first vs show Starter first: which order produces highest conversion?
3. **Feature placement on Starter** — does showing "Automations (Growth+)" greyed out increase Growth conversions vs hiding it entirely?
4. **Annual vs monthly default** — which converts better on pricing-page-first-impression?
5. **Trial length** — 7 vs 14 vs 30 days

Run one test at a time. Need 200+ signups per variant for statistical significance — at low volume, run for weeks.

## Common pitfalls

| Pitfall | Why it bites |
|---|---|
| **Five+ tiers** | Buyers get analysis paralysis; conversion drops |
| **Gating deliverability features** | Frustrated users churn + leave bad reviews; reputation damage |
| **Free tier without credit card** | Spammer signups damage your shared sending IPs |
| **Monthly only (no annual)** | Higher churn, worse cash flow |
| **Pro tier features you can't deliver** | "Priority support" with no actual SLA = churn signal |
| **Free trial > 14 days** | Conversion delays; "I'll get to it eventually" mode |
| **Pricing tied to subscriber count only** | Sends-per-month axis under-monetized |
| **Custom plans for early customers** | Operationally messy; build a real Enterprise tier instead |

## How AcelleMail enforces plan limits

In AcelleMail's plan model (per the [SaaS setup guide](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform#step-3--design-plans)), limits live in the plan's `options` JSON field:

```json
{
  "max_subscribers": 10000,
  "max_emails_per_month": 100000,
  "max_lists": -1,
  "max_automations": -1,
  "max_team_members": 3,
  "max_sending_servers": -1,
  "api_access": true,
  "create_sending_server_enabled": false,
  "custom_domain_enabled": false,
  "automation_enabled": true
}
```

`-1` = unlimited. AcelleMail enforces at send time (campaigns blocked if quota exceeded) + in the admin UI (greyed-out / disabled controls per the toggle flags).

Add custom field-flagged features by extending the `options` shape — see [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code).

## FAQ

**Should the cheapest plan be free?** For email SaaS, no — better as a 14-day trial that catches-and-releases to a tightly-limited free plan. Forever-free attracts spammers.

**Multi-currency pricing?** Each AcelleMail plan has one currency. Create parallel plans per currency (Pro $79 USD, Pro €72 EUR). Customer's currency is locked to the plan they signed up on.

**Per-contact vs per-send pricing?** Per-contact (Mailchimp model) is what email SaaS conventionally uses. Per-send (SendGrid model) suits transactional more than marketing. AcelleMail's plan model supports both via the `max_subscribers` + `max_emails_per_month` options.

**Volume discounts at higher tiers?** Usually built into the tier structure (Pro at $149/mo includes 50k subscribers vs Growth at $79/mo for 10k — implicit discount per subscriber). Don't add separate volume-discount tiers; keep tier count low.

**Promotional pricing for the first month?** Splits revenue per customer over a longer payback; usually not worth the customer-acquisition lift. Better: a 14-day trial that's free.

**Custom plans for individual customers?** Strongly resist. The operational cost of one-off plans (billing reconciliation, support tickets, quota management) is real. Build an Enterprise tier instead, with the most-asked customizations baked in.

## Related articles

- [Setting Up AcelleMail as a SaaS Platform](https://acellesend.com/kb/articles/setting-up-acellemail-as-a-saas-platform) — the technical setup (where the plan model is configured)
- [Building Your Email Marketing SaaS](https://acellesend.com/kb/articles/building-your-email-marketing-saas) — broader GTM context (pricing fits inside the larger pricing-strategy section)
- [White-Label Customization Guide](https://acellesend.com/kb/articles/white-label-customization-guide) — white-label is one of the highest-margin Pro/Enterprise gates
- [Extending AcelleMail Source Code](https://acellesend.com/kb/articles/extending-acellemail-source-code) — for custom plan options + enforcement logic
- [Connecting Stripe for Subscription Billing](https://acellesend.com/kb/articles/connecting-stripe-for-subscription-billing) — Stripe is where pricing is operationalized
- [Webhook Events Reference](https://acellesend.com/kb/articles/webhook-events-reference) — Acelle fires new_subscription / change_plan / cancel_subscription events on plan changes
- [Configuring Amazon SES with AcelleMail](https://acellesend.com/kb/articles/configuring-amazon-ses-with-acellemail) — sending infra that scales with your customer base

---

# A/B Testing Email Subject Lines for Better Open Rates

> AcelleMail's built-in A/B test splits your list, picks the winner and sends it to the rest. This guide walks through the setup and the habits that make a test meaningful.

- Canonical URL: https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Email Marketing — Campaign creation, templates, A/B testing, personalization, scheduling
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-08 · Last updated: 2026-10-21
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## Why A/B test the subject

The subject line decides whether people open the email at all, so it is one of the most useful things to test. A/B testing is the cheapest way to find what works for your audience.

Two things make a subject line test meaningful. First, judge it by clicks as well as opens: Apple Mail Privacy Protection records opens for emails nobody has read ([why](https://acellesend.com/kb/articles/understanding-email-open-rates-in-the-age-of-apple-mpp)). Second, make the groups big enough. How many contacts you need depends on your current rate and on the size of the difference you hope to find. [Email A/B Testing Explained](https://acellesend.com/kb/articles/email-ab-testing-explained) has the numbers.

## Setup in the AcelleMail wizard

### Step 1: Pick A/B test campaign type

In the campaign type selector:

![Select A/B test campaign type](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/01-select-ab-test.png)

The **A/B Test** card is listed with **Regular Campaign** and **Plain Text**. Click **Create** on it.

### Step 2: Configure the test

![A/B test setup screen](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/02-ab-test-setup.png)

Set:

- **Test audience size**: the share of the list that receives the test. Everyone else receives the winner.
- **Assignment strategy**: choose **Hash split**, which mixes old and new contacts in every group. **Exact split** fills the groups in the order contacts were added to the list.

There is no "what to test" setting. Each variant is a complete email, so you decide what to change when you create the variants. Send times cannot be tested inside one A/B campaign, because all variants go out at the same time. The winner rule and the wait before deciding are set in the last step.

### Step 3: Define the variants

![Variants tab — A vs B](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/03-variants-tab.png)

Variant A has one subject line and Variant B has another. You can add more than two variants, but two give clearer results, because each extra variant makes every group smaller.

### Step 4: Configure the winner rule

![Winner rule picker](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/04-winner-rules.png)

Set:

- **Win condition**: choose **Highest click rate**, even for a subject line test. An open-rate winner may only show that its group happened to include more Apple Mail readers.
- **Wait before deciding**: choose a wait that covers most of the clicks your past campaigns received. A short wait judges the variants only on the people who respond quickly. A long wait delays the email for everyone else.

### Step 5: Launch + watch

After launching:

- AcelleMail sends test variants to your sample subscribers
- Tracks opens and clicks for the configured time
- Picks the variant with the higher rate, without checking whether the difference is significant
- Sends the winning variant to the rest of the audience

Post-test, the campaign report shows results:

![A/B report — final winner + delta](https://acellesend.com/plugins/acelle/landing/images/help/kb/ab-test/05-ab-report.png)

## Sample size + statistical confidence

```
List size of 5,000 → test audience 20% = two groups of 500
List size of 50,000 → test audience 20% = two groups of 5,000
List size of 500,000 → test audience 10% = two groups of 25,000
```

What decides whether a test can find a difference is the number of contacts in each group, not the share of the list. For example, at a 20% open rate, detecting a 10% relative lift (20% to 22%) needs about 6,500 contacts per variant, at the usual 5% significance level with 80% power. Two groups of 500 can only detect very large differences. [Email A/B Testing Explained](https://acellesend.com/kb/articles/email-ab-testing-explained) has a table for click rates.

## What to test (and what NOT to)

| Test | Worth doing |
|---|---|
| Direct subject vs Curiosity subject | YES |
| Subject with merge tag vs without | YES |
| Subject length: 30 chars vs 70 chars | YES |
| Adding/removing emoji in subject | YES |
| Subject A vs B (random ideas) | NO — too noisy without a hypothesis |
| Subject + From name simultaneously | NO — can't attribute the winner to one |
| Subject across different list segments | NO — not the same audience |

Test ONE variable at a time. Subject A vs B with a clear hypothesis ("urgency increases opens"). Otherwise the test result is uninformative.

## Interpreting the results

```
Variant A: 22.5% open rate  ← Standard subject
Variant B: 28.1% open rate  ← Variant subject

Lift: 5.6 percentage points (relative 25% lift)
```

Was the lift real? Check the **P-value** on the **Statistical confidence** card of the A/B Report. A p-value below 0.05 is the usual threshold. The card compares the rate your win condition uses (open rates when you chose **Manual selection**).

If the p-value is below 0.05, the result is significant. Keep the winning pattern, and repeat the test once before you make it a rule.

If it is not, treat the result as a tie: the test could not tell the variants apart. Sending either variant to the rest of the list is fine. Redesign the test with a bigger change or a bigger test audience for the next campaign.

## What you learn over time

After ~10 A/B tests, patterns emerge:

- Your audience prefers urgency → use urgency consistently
- Your audience opens for curiosity → lean into mystery subjects
- Emoji doesn't help (or hurts) your audience → drop them
- Personalization in subject lifts X% reliably → make it default

These insights compound. Test, learn, apply, repeat.

## Common UI signals + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| The test waits and no winner is sent | The win condition is **Manual selection**, which waits for you | Open the A/B Report and click **Choose as winner** on a variant |
| Winner = Variant A consistently across 5 tests | Test variants are too similar | Make variants more different (different angle, not different word) |
| The winner does worse with the rest of the list than in the test | The test result was partly luck, or the test groups were not a fair sample (**Exact split** uses the oldest contacts) | Use **Hash split** and a bigger test audience, and check the p-value |
| Test crashes mid-flight | Sending server issue or workers down | Investigate; rerun with same variants |
| Mobile vs desktop opens dramatically different | Variant rendering issue | Preview + Send-test both variants before A/B |
| Test schedule conflicted with another campaign | Multiple campaigns to same list overlap | Sequence campaigns; one A/B at a time |

## Advanced patterns

### More than two variants (A/B/n)

```
A: Direct subject
B: Curiosity subject
C: Personalized subject (with merge tag)
D: Urgent subject (with deadline mentioned)
```

Each variant needs as many contacts as in a two-way test, so the test audience doubles. With six pairs to compare, a false winner also becomes more likely: about 20% at the 5% level when all four variants perform the same. Useful when you genuinely have 4 distinct hypotheses to test. Most senders stick to 2.

### Subject A/B + body A/B simultaneous

```
A1: Subject A + Body Layout 1
A2: Subject A + Body Layout 2
B1: Subject B + Body Layout 1
B2: Subject B + Body Layout 2
```

4-way matrix. Reveals interaction effects (subject A only works with body layout 2). Requires 4× the sample size.

For most senders, single-variable testing wins on simplicity.

### Time-of-day A/B

```
Variant A: Sent Tuesday 9:30am ET
Variant B: Sent Tuesday 2:00pm ET
```

AcelleMail sends all variants of an A/B test at the same time, so it cannot run this test inside one campaign. To compare send times, send the same email as separate campaigns at different times over several weeks, and remember that other things also change between campaigns.

<details>
<summary><strong>Advanced: optimal A/B sample-size math + holdout groups + automated subject rotation</strong></summary>

**Optimal sample-size math:**

For detecting a relative 10% lift in open rate (20% to 22%) with a two-sided test at the 5% level and 80% power:

```
Required sample per variant: about 6,500

With a 20% test audience, two groups of 6,500 need a list of about 65,000 contacts.
With a smaller list, test a bigger change, use the whole list, or repeat the same kind of test over several campaigns.
```

The required sample grows quickly as the lift you want to detect gets smaller. Detecting a 5% relative lift (20% to 21%) requires about 25,600 per variant.

**Holdout groups:**

Beyond A/B (which compares 2 variants), holdout groups test "any campaign vs no campaign":

```
Group A (90%): Receives the campaign
Group B (10%): Receives nothing
```

Compare downstream metrics (revenue, click-through, future-engagement) across the two groups. Tells you the campaign's NET incremental impact — including subscribers who "would have engaged anyway."

This is the gold-standard methodology for measuring email's true revenue contribution.

**Automated subject rotation:**

For very large senders (>1M emails/month), train a multi-armed-bandit on subject preferences:

```
Each campaign:
  - 80% sent with the historically-best subject pattern
  - 20% sent with a slightly varied pattern (exploration)
  - Update the historical winner based on actual results
```

Over months, the system converges on your audience's optimal subject pattern automatically.

Tools: custom-built models, or testing platforms that support this kind of adaptive allocation.

**Time-decay analysis:**

After A/B winner is picked, monitor 72-hour engagement:

```
Hour 0-4:  Variant B opens 25% higher
Hour 4-24: Variant B still leads
Hour 24-72: Variants converge in total opens
```

If the lift evaporates over time (early opens are inflated by curiosity, not value), the apparent winner may not be the true winner.

This is one reason to set **Wait before deciding** long enough to cover most of the clicks your past campaigns received.

**Cross-segment A/B:**

Test subjects across different audience segments:

```
Engaged segment (high open rate):    Subject A wins
At-risk segment (low open rate):      Subject B wins (different angle)
New segment (no engagement history):  Subject A wins
```

The "best subject" varies by segment. Sophisticated senders maintain segment-specific subject formulas. Most stick to one winner.

</details>

## Related articles

- [Email A/B Testing Explained: How to Run a Test You Can Trust](https://acellesend.com/kb/articles/email-ab-testing-explained)
- [Email subject line formulas that work](https://acellesend.com/kb/articles/email-subject-line-formulas-that-work)
- [Subject line formulas that consistently work](https://acellesend.com/kb/articles/subject-line-formulas-that-consistently-work)
- [Email preheader text — the hidden conversion booster](https://acellesend.com/kb/articles/email-preheader-text-the-hidden-conversion-booster)
- [Your first email campaign in AcelleMail](https://acellesend.com/kb/articles/creating-your-first-email-campaign-in-acellemail)

---

# Build a Welcome Email Series in AcelleMail (Visual Flow Builder)

> AcelleMail's automation builder ships with 12 triggers. "Welcome new subscribers" + 5 email nodes + a WAIT + a YES/NO CONDITION gives you a working welcome series in 30 minutes. Walkthrough with the actual flow canvas.

- Canonical URL: https://acellesend.com/kb/articles/building-a-welcome-email-series-in-acellemail
- Author: Sofia Lindqvist, Email Marketing Editor
- Category: Automation — Triggers, workflows, welcome series, drip campaigns, behavioral flows
- Content type: tutorial · Difficulty: beginner
- Tags: Acellemail
- Published: 2026-01-08 · Last updated: 2026-08-15
- Reading time: 9 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

A welcome series is the highest-ROI automation you can build. Every new subscriber gets the right introduction at the right pace, automatically — and your future-self stops doing it manually.

AcelleMail has a **visual flow builder** with 5 node types (Trigger / Send Email / Wait / Condition / Operation) and branching paths. You build the welcome series once, activate it, and every new subscriber to the connected list enters the flow at exactly the right moment.

This walkthrough shows the whole build: from clicking *+ New Automation* to a live 5-email welcome series with engaged-vs-not-engaged branching.

## Before you start

- **A mail list with at least one Subscribed contact.** The automation triggers off list signups.
- **A verified sender** — the welcome series sends real campaign emails. Confirm your sending domain has SPF + DKIM green.
- **5 email designs ready (or willing to design them inline).** Each step is a real email; you'll spend most of the time on the copy, not the flow.

## Step 1 — Open the Automation section

Click **Automation** in the left sidebar. URL: `/automations`.

![Automations dashboard with "Automations" header + "Build automated email workflows triggered by subscriber actions." subtitle. Banner "Put your emails on autopilot — Create automated workflows that send the right message at the right time. Welcome new subscribers, nurture leads, and re-engage contacts without lifting a finger." Stats row: 1 automations · 1 active · 0 inactive. Table with "My Sales Pipeline" automation row showing Webinar Signups list, Active status, "New subscriber" trigger, 2 Emails, Created May 16 2026, and an "Edit workflow" button. "+ New Automation" button top right.](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/01-automations-index.png)

The dashboard lists every automation, the trigger that starts it, and how many subscribers are currently in the flow. From here, **+ New Automation** kicks off the build.

## Step 2 — Pick the "Welcome new subscribers" trigger

AcelleMail ships with **12 built-in triggers**. You pick the trigger first — it determines what event starts the workflow for each subscriber.

![Create Automation page asking "What should trigger this automation?" with subtitle "Choose an event that will start the automation workflow. Each subscriber will enter the workflow when this event occurs." Two-tab header: Trigger (active) and Configure. 12 trigger cards in a 4x3 grid: Welcome new subscribers, Say goodbye to subscriber, Say 'Happy birthday', Subscriber added date, Specific date (highlighted), API 3.0, Weekly recurring, Monthly recurring, Tag based, Remove Tag, Contact Attribute Update, Abandoned Cart Reminder. Each card has an icon + title + short description.](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/02-create-trigger-picker.png)

The 12 trigger types AcelleMail ships with — useful to know all of them since other articles cover the others:

| Trigger | When it fires | Article |
|---|---|---|
| **Welcome new subscribers** | New signup to the connected list | **This article** |
| Say goodbye to subscriber | Subscriber unsubscribes | — |
| Say 'Happy birthday' | Birthday field matches today | [Birthday automations](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations) |
| Subscriber added date | Anniversary of signup date | [Birthday automations](https://acellesend.com/kb/articles/birthday-and-anniversary-email-automations) |
| Specific date | One-time send on a fixed date (e.g. appointment) | — |
| API 3.0 | External trigger via REST API call | [API triggers](https://acellesend.com/kb/articles/using-api-triggers-for-custom-automation-workflows) |
| Weekly recurring | Same day every week | — |
| Monthly recurring | Same day every month | — |
| Tag based | Subscriber gets a specified tag | [Re-engagement](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers), [Lead scoring](https://acellesend.com/kb/articles/lead-scoring-with-email-automation) |
| Remove Tag | Subscriber loses a specified tag | — |
| Contact Attribute Update | A custom field changes to a specified value | [Advanced triggers](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) |
| Abandoned Cart Reminder | WooCommerce-style cart abandonment | [Abandoned cart](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence) |

Click **Welcome new subscribers**, then click the **Configure** tab (top of the page) to set the audience list — pick the list whose new signups should enter the flow.

## Step 3 — Land in the visual flow builder

After confirming the trigger + list, AcelleMail opens the **flow builder** — a drag-and-drop canvas where each node represents a step in the journey.

![Automation flow builder for "My Sales Pipeline" with ACTIVE status badge. Top bar: ← Back / automation name + status badge / Pause / Statistics / Settings / Fullscreen buttons. Canvas shows a vertical flow: TRIGGER node "Welcome new subscribers" at top, then SEND EMAIL "Welcome to the family", then WAIT "1d", then CONDITION "Opened Welcome to the family" with two branches — YES branch leads to OPERATION "Add tag(s): engaged customer", NO branch leads to SEND EMAIL "Just a friendly reminder" then another CONDITION "Opened Just a friendly reminder" with another YES/NO split. Bottom-left hint: "Click + to add node · drag to pan · scroll to zoom". Bottom-right: zoom +/-/fullscreen controls.](https://acellesend.com/plugins/acelle/landing/images/help/kb/welcome-automation/03-flow-builder.png)

The 5 node types you'll use in any welcome series:

| Node | What it does | When to use |
|---|---|---|
| **TRIGGER** | Entry point — every flow has exactly one, set in Step 2 | Always first, fixed |
| **SEND EMAIL** | Sends one campaign email to the subscriber | One per email in your series |
| **WAIT** | Pauses for N hours/days before the next node | Between emails (Day 0 → Day 2 → Day 4 etc.) |
| **CONDITION** | YES/NO split based on a fact about the subscriber (opened the previous email, has a tag, has a field value) | Engagement-based branching |
| **OPERATION** | Mutates the subscriber: add/remove tag, update a field, copy to another list | Tagging "engaged" / "unengaged" for future segments |

To add a node, click the **+** on any existing node's downstream arrow. The picker offers all 5 node types. To edit a node's content, click the node — a side panel opens with the email template / wait duration / condition rules.

## Step 4 — Build the 5-email backbone

The "right" cadence varies by audience, but this is a battle-tested skeleton for a SaaS / B2B welcome series:

```
TRIGGER  → Welcome new subscribers (list: Main List)
SEND EMAIL → "Welcome to [Brand]!"              (sent immediately)
WAIT     → 2 days
SEND EMAIL → "Our story (and how we got here)"  (Day 2)
WAIT     → 2 days
SEND EMAIL → "Our best resource (the one customers always thank us for)"  (Day 4)
WAIT     → 3 days
SEND EMAIL → "What our customers say"            (Day 7) — social proof
WAIT     → 3 days
SEND EMAIL → "Ready to try [Product]?"           (Day 10) — soft offer
```

5 sends across 10 days. Most welcome series under-send rather than over-send; an empty inbox after Day 1 is the failure mode.

**Per-email content focus** — what to write in each SEND EMAIL node:

| # | When | Job | Don't |
|---|---|---|---|
| 1 | Day 0, instant | Confirm subscription, deliver lead magnet if any, set frequency expectations | Pitch product |
| 2 | Day 2 | Brand story — what you stand for, why you exist | Be self-congratulatory |
| 3 | Day 4 | Hand them your best free content (highest-engagement post or guide) | Force a signup |
| 4 | Day 7 | Customer outcomes — quote, testimonial, case study | Generic *"we have great customers"* |
| 5 | Day 10 | First soft offer — trial / consult / discount | High-pressure tactics |

## Step 5 — Add engagement-based branching

The single biggest welcome-series improvement is **branching on opens** — different content for engaged vs unengaged subscribers.

After Email 1 (Welcome), add:

```
WAIT      → 2 days
CONDITION → Opened "Welcome to [Brand]!" ?
            ├─ YES → continue to Email 2 (story)
            └─ NO  → SEND EMAIL "Did you get our welcome?" (re-send variant)
                     WAIT → 1 day
                     CONDITION → Opened the re-send ?
                                  ├─ YES → join the main flow at Email 2
                                  └─ NO  → OPERATION: Add tag "welcome-unengaged"
```

The example "My Sales Pipeline" flow above (screenshot) shows exactly this pattern: a CONDITION after each email checks if the previous one was opened. **YES branch** continues to the next email + tags them. **NO branch** sends a softer re-engagement variant.

Why this matters:

- Engaged subscribers get the full 5-email path
- Unengaged subscribers get a second chance, then get tagged out — preventing the silent treatment from compounding
- The `welcome-unengaged` tag becomes a useful segment for a separate re-engagement campaign

## Step 6 — Activate the automation

When your flow is ready:

1. **Save** — automatic on every node edit, but a manual save via Cmd/Ctrl+S confirms.
2. Open **Settings** (top right of the builder).
3. Confirm the **List** field shows your target list.
4. Toggle status from **Paused / Draft** to **Active**.
5. The status badge at the top of the builder flips to **ACTIVE** (green).

From this moment forward, every new subscriber added to the connected list enters the flow at the TRIGGER node and walks through every step.

> **Test with a fresh subscriber.** Before sharing, sign up to your own list with a never-before-used email. Walk through the flow live — every WAIT shortens to a few minutes in a *test mode*, or just be patient and check your inbox at Day 2, 4, etc.

## Monitor — who's in the flow right now?

From the automation row in the index, click **Statistics** (or open the automation → top bar **Statistics**). The contacts view shows:

- Every subscriber currently in the flow
- Which node they're on
- When they were triggered

If a subscriber should have entered but didn't, the **contacts.trigger_failed_segment** message means they don't match the list's segment filter; **contacts.trigger_failed_paused** means the automation isn't activated yet.

## Common issues

| What you see | What to do |
|---|---|
| Automation is ACTIVE but no one's entering | Confirm the **trigger event matches your audience action.** *Welcome new subscribers* fires on actual list signups — not imports. To enter imported subscribers, change trigger to *Tag based* + tag the imports. |
| Subscriber went through Email 1 then stopped | Check the WAIT node between emails — it might be set to days when you meant hours, OR the CONDITION's YES/NO routing isn't wired. Click into each node to confirm. |
| Emails sent but engagement is low | Audit the SEND EMAIL nodes — each one's subject + preheader. The Welcome series IS your campaign equivalent; same craft applies. See [Email Copy](https://acellesend.com/kb/articles/how-to-write-email-copy-that-converts). |
| Wrong list connected — subscribers from a different list are entering | Open Settings, change the List field. Subscribers already in the flow continue; new entries come from the new list. |
| You added new nodes but they don't fire | Click the parent node — make sure the new node is wired to its downstream arrow. Orphan nodes don't execute. |
| Statistics shows "Not triggered" for everyone | The automation was never activated. Open the builder, top-right toggle from Paused to Active. |

## What NOT to do

- **Don't send 5 emails in 5 days.** The "Day 0 / 2 / 4 / 7 / 10" cadence is calibrated — too tight = unsubscribes, too loose = forget you exist.
- **Don't pitch in Email 1.** The first welcome is a relationship moment. Save sales for Email 5+.
- **Don't skip the WAIT nodes.** *"Email immediately, then 2 hours later, then 4 hours later"* feels like spam.
- **Don't branch unless you're going to use the tag.** Branching adds complexity. Only do it if the resulting `welcome-engaged` / `welcome-unengaged` tags drive a downstream segment.

## After your welcome series is live

- **Watch the contacts view weekly** for the first month — confirm new signups are flowing through.
- **Check per-email reports** — open Statistics → per-node click-through rates. The drop-off between two emails is where most series lose subscribers; tighten that copy.
- **A/B test Email 1's subject line.** The welcome email gets opened 4-5x your average campaign — small subject improvements compound across thousands of subscribers.
- **Iterate quarterly.** Industry norms (and your audience) shift. Rebuild a fresh version every 90 days.

## Related articles

- [Abandoned Cart Email Sequence](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence)
- [Post-Purchase Follow-Up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation)
- [Re-engagement Campaigns: Win Back Inactive Subscribers](https://acellesend.com/kb/articles/re-engagement-campaigns-win-back-inactive-subscribers)
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions)

---

