Skip to content

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.

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

  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

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

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.

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.

Advanced: server-side checks for the operator

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):

# 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):

supervisorctl restart acelle-queue-worker:*

If you're running the worker manually without Supervisor:

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:

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:

crontab -l | grep acelle:run
# Expected: * * * * * php /path/to/acelle/artisan acelle:run >/dev/null 2>&1

Inspect failed jobs:

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.

Related articles

23 bình luận

16 bình luận

  1. priya.iyer.ops
    question: in step 4, the campaign log line about 'force resuming' — does that show up in laravel.log or only the per-campaign log file? our laravel.log seems silent on this
    1. admin
      Suppression list import via CSV captures all opt-outs including preference-center ones if you exported with the right field set. The export filter defaults exclude some — check the 'include unsubscribed' checkbox on Mailchimps export wizard.
  2. ahmed.hassan.c…
    Confirming the campaign:rerun auto-fix actually works. We had a worker OOM mid-batch last week, walked away thinking we'd need to manually intervene, came back in 15 minutes and it had recovered itself.
  3. tnovak.cz
    Pro-tip: set `pm.max_children` on your PHP-FPM pool to at least 2x your supervisor worker count. Otherwise even ack-fast endpoints choke when the queue rate goes up.
  4. lucas.bernard.…
    Adding to this: we had a campaign stuck for 6 hours one time. Turned out the running_pid was alive but the worker was deadlocked on a slow MySQL query. ps showed it as running, kill -9 was the only fix. Now we monitor for stale running_pid > 30 min.
  5. joel.anders.se
    sent this to my whole os team. Should be required reading before anyone touches the prod queue anyway
    1. admin
      Appreciate it. If anything in this needs updating, ping us — we revisit articles every few months...
  6. m.schmidt78
    Bookmarking this. Wish I had it last month when our queue backed up on a Sunday night.
  7. emma.whitaker
    Cause #2 (dead supervisor) hit us after a kernel-upgrade reboot. The systemctl enable bit was missing. Took 2 hours to figure out because nothing was logging.
  8. cw.dev.sh
    Thanks for grounding this in actual source — much better than the generic Laravel advice you find on Stack Overflow.
  9. linhpm.devs
    i think the recommendation to wait 10-15 minutes is too patient for production. we alert at 5 minutes and our ops team triages. stuck status > 5 min usually means something real.
    1. admin
      Good point. The audience-segment split makes sense for time-sensitive campaigns. We'll add a note on when to deviate from random. lol
  10. ravi.kumar.del…
    Curious if the 200-row / 32-advance bounds are configurable. We have one customer with very large automation flows and I wonder if they hit this.
    1. admin
      we don't recommend that approach in production. it works in dev but has subtle race conditions under concurrent load. stick with the documented pattern.
  11. sobrien.kw
    We hit cause #5 last quarter — SES sandbox limits we didn't know about. The 'wait it out' advice is right. We tried aggressive retries first and it just made things worse. lol
  12. v.petrova.ru
    One more thing worth adding: if you use Cloudflare in front, make sure to whitelist the worker IPs for outbound — we had retries failing because Cloudflare was rate-limiting our own outbound traffic to SES.
    1. admin
      Yep, same pattern works for us. Thanks for sharing.
  13. phuong.mai.hn
    when you say to bump wait_timeout to 86400, does that need a MySQL restart or is it dynamic? Were on RDS so restarts are expensive.
    1. admin
      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...
  14. akira.tnk88
    is there a way to detect cause #6 (lost DB connection) before workers wedge? Looking for a heartbeat metric to alert on
    1. admin
      Good question — and one that comes up often enough we should add an FAQ section. Short answer: yes for the common case; the exception is when you're running custom plugins that override the default behavior
  15. bos.devops
    If you're on Ubuntu 22.04 with the default cron package, the time zone is UTC even if /etc/timezone says otherwise. Bit us once — the scheduled job timing was 7 hours off
  16. d.cohen.tlv
    This article saved me about 4 hours of debugging today. The diagnostic order at the top is exactly the workflow I needed.

More in Troubleshooting