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.

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.
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 for the receiving side.

Related articles

13 Kommentare

4 Kommentare

  1. femi.adeyemi
    If you go dedicated IP — request the warmup support from your provider too. SES and SendGrid both have specific warmup features that help. 👀
    1. admin
      yep, same pattern works for us. Thanks for sharing...
  2. emma.whitaker
    Dedicated IP made a measurable difference for us. We moved from shared SES to a dedicated IP at ~80k subs and saw ~8% lift in inbox placement within 6 weeks. :)
    1. admin
      Thanks for the numbers. Worth pulling into a follow-up post on volume-tier sizing.
  3. linhvu.dev
    has anyone tried postgresql instead of mysql for the acellemail db? curious if the indexes behave better at scale.
    1. admin
      We tested this with up to 1M subscribers on a $40/mo VPS. Past that you start needing query optimization. Below that, the defaults are fine
    2. admin (bearbeitet)
      Honest answer: it depends on your provider. SES handles it gracefully; Mailgun is stricter. Well add a provider-by-provider table in the next revision.
    3. admin (bearbeitet)
      That config is exposed in 5.2+. For older versions you'll need to edit the config file directly. We'll add a version-matrix in the article.
  4. ravi.kumar.del…
    Confirming: at 500k+/day, MySQL becomes the bottleneck before workers. Moving to Redis queue freed up DB headroom for everything else. tbh
    1. admin (bearbeitet)
      Thanks for the breakdown. Saving for our customer-success team's reference library...
    2. admin (bearbeitet)
      great real-world detail. your point about stale running_pid > 30 min as an alert is something we should add to the diagnostic flow.
    3. admin (bearbeitet)
      useful field report. the 'kill -9 was the only fix' edge case is rare but real — well note it as a fallback.
    4. admin (bearbeitet)
      thanks for the detail — adding the kernel-reboot edge case to the article on the next update

More in Email Infrastructure