Self-hosted email marketing with full source code. Pay once, own forever. Get AcelleMail — $74 →

Sending Throttling Strategies in AcelleMail

Per-server throttling, per-list throttling, time-of-day throttling, and adaptive backoff. The AcelleMail throttle config + the SendingServer fields that gate each pattern.

"Send everything as fast as possible" is the wrong policy. ISPs throttle senders that exceed reasonable per-hour and per-recipient rates; sustained bursts trip rate-limit detectors that erode reputation before they trip the more visible block. The best-deliverability senders pace consistently below the throttle threshold of every ISP they target — not because they need the slack, but because predictable cadence is itself a positive reputation signal.

This article covers the four throttling patterns AcelleMail supports, when each is appropriate, and the field-level configuration to implement them.

Pattern 1 — Per-server throttling

The simplest pattern: limit the rate at which a sending server emits messages. AcelleMail's SendingServer model has these fields:

Field Purpose
sending_quota Max messages per quota_period
quota_period One of: minute, hour, day, week, month
sending_quota_value (per second) Hard SMTP-rate ceiling — how fast the worker dispatches

Daily quota caps the total volume per server per day; per-second rate caps the burst. Configure both:

Admin → Sending Servers → <server> → Edit
   Sending quota:        500,000
   Quota period:          1 day
   Throttle (per second): 100      (= 360,000/hour ceiling, well under daily cap)

The interplay matters. With quota = 500k/day and throttle = 100/sec, the worker can drain the quota in ~83 minutes — too fast for some ISPs to digest cleanly. Spreading across the working day (target ~10/sec for a 500k/day program) gives Gmail/Microsoft adaptive throttle room to learn engagement before the next message arrives.

Pattern 2 — Per-list throttling (timing for engagement)

A list mailed all at once has a sharp engagement spike (everyone opens within 4 hours, then nothing). A list mailed over 8 hours has a flatter spike — opens spread, click-through is roughly 15-25 % higher, and the receiver-side spam filters see your traffic over a longer window which improves classification.

In AcelleMail:

Customers → <customer> → Campaigns → New → "Send Settings"
   Send rate: 10,000/hour    (the campaign's max throughput)
   Schedule: Best Time per Subscriber Timezone     (optional, see below)

The campaign's send rate is bounded by its sending-server's quota AND the campaign's own configured rate. Setting both gives you fine-grained control: a server might be capable of 100/sec, but you cap a particular campaign at 50/sec so it doesn't dominate the queue.

Pattern 3 — Time-of-day shaping

For B2C senders, send timing measurably affects opens. AcelleMail's "Send by recipient timezone" mode (controlled per subscriber via the timezone attribute):

Campaign → "Schedule by Subscriber Timezone"
   Window: 09:00 - 18:00 recipient local
   Use offset: subscriber.timezone (or list default)

The worker dispatches per-subscriber based on each recipient's local time. Implementation: AcelleMail computes the dispatch time per subscriber and queues the SES/Mailgun API call accordingly. This pattern shifts the daily peak from "your timezone 09:00" to "many smaller peaks distributed across all recipient timezones," which tends to flatten the throughput curve and reduce momentary throttling.

The cost: throughput is no longer "as fast as the worker can run" — it's gated by recipient-timezone-distribution. For a list mostly in 4 timezones, expect 4 distinct throughput peaks across 24 hours.

Pattern 4 — Adaptive backoff (auto-pause on signals)

Per the WarmupStrategy data model, AcelleMail can auto-pause sending when bounce or complaint thresholds are crossed:

// app/Dto/WarmupStrategyData.php — relevant fields
public bool $enableSafetyChecks;
public bool $pauseOnNegativeSignals;
public float $maxBounceRate;        // 0.05 = 5%
public float $maxComplaintRate;     // 0.003 = 0.3%

When pauseOnNegativeSignals is on and either rate is exceeded over the rolling window, the queue worker stops dispatching for the affected sending server until an admin un-pauses it. This is the safety system protecting reputation from a content disaster (a campaign that triggers a complaint storm).

Don't disable this. The cost of a 4-hour pause that prevents a 24-hour deliverability hit is a strict win.

Configuring the worker for sustained throughput

Two queue-worker tuning levers:

# /etc/supervisor/conf.d/acellemail-worker.conf
command=/usr/bin/php /var/www/acellemail/artisan queue:work \
    --sleep=3 --tries=3 --max-time=3600 \
    --memory=256
numprocs=4    # one process per ~25k/day; tune by tier

--max-time=3600 recycles the worker every hour to bound memory leaks; --memory=256 recycles when memory exceeds 256 MB (also leak protection). At Medium-tier and above, run more numprocs rather than longer-lived workers.

For SES sending, configure SES's own per-account rate limit at parity with AcelleMail's per-server rate — don't have AcelleMail dispatch faster than SES will accept; SES will start returning Throttling.Send errors that AcelleMail logs as failed jobs.

Throttle response to provider 421/4.7.0

When the SMTP transaction returns a 421 or 4.7.0 (rate-protected defer), AcelleMail's bounce handler logs a soft bounce. The Laravel queue retries the message after 5/15/60 minutes. For a single recipient this is fine; for many simultaneous recipients (a reputation event), you want to:

  1. Auto-pause the sending server (Pattern 4 will do this if bounce rate exceeds threshold).
  2. Reduce the per-second rate by 50 % when a 421 spike is detected.
  3. Resume at the reduced rate; bump back up only after 12 hours clean.

AcelleMail doesn't auto-implement step 2 (yet); you can do it manually via cron + an artisan command that polls bounce rate and updates sending_quota_value per server. For most ops, the pattern-4 auto-pause is sufficient.

Diagnostic queries

Throughput per sending-server-per-hour, last 24h:

SELECT
  ss.name,
  HOUR(tl.created_at) as hour,
  COUNT(*) as sends
FROM tracking_logs tl
JOIN sending_servers ss ON tl.sending_server_id = ss.id
WHERE tl.created_at > NOW() - INTERVAL 24 HOUR
GROUP BY ss.id, hour
ORDER BY ss.name, hour;

Find sending-servers approaching their per-day quota:

SELECT name, sending_quota,
  (SELECT COUNT(*) FROM tracking_logs
   WHERE sending_server_id = ss.id
     AND created_at > CURRENT_DATE) as today_sent
FROM sending_servers ss
WHERE quota_period = 'day'
ORDER BY today_sent / sending_quota DESC;

Alert when ≥ 90 % of any quota.

Related reading

FAQ

What's the right per-second rate?

Depends on tier and recipient distribution. Rules of thumb:

  • 50k/day program → 5/sec sustained
  • 500k/day program → 25-50/sec sustained
  • 5M/day program → 200-500/sec sustained, distributed across multiple sending servers

Below ~5/sec, throttling is overhead; above ~500/sec from a single IP, you're flirting with most ISPs' rate-protection.

Can I send faster on weekends when ISPs are quieter?

Marginally. ISP rate-protection is roughly the same 24/7. The actual constraint is recipient receipt timing — sending Saturday 02:00 produces poor open rates regardless of throughput.

What if my sending server's quota is "unlimited"?

You still want a per-second throttle. "Unlimited daily quota" with no per-second cap means a 1M-message campaign tries to send all 1M in the first minute, which trips every receiver's rate-protection.

Does throttling apply to API-based sending (SES, Mailgun)?

Yes. The per-second rate caps how fast AcelleMail makes API calls to SES/Mailgun. Exceeds the provider's account-level rate limit → API-level rate-limit errors → soft bounces in AcelleMail.

Throttle progression chart for new sending servers

When introducing a new sending server (warmup or capacity expansion), use this progression:

Phase Daily cap Per-second Spread (campaign duration) Acceptance
Day 1-2 100 1 Single 8-hour window during business day All sends accepted, 0 deferrals
Day 3-7 1,000 5 8-hour spread Bounce rate < 2 %, no soft-bounce burst
Week 2 10,000 20 12-hour spread covering 2 timezones SNDS GREEN, Postmaster appearing
Week 3 50,000 50 12-hour spread Postmaster IP rep ≥ Medium
Week 4+ 100,000+ 100+ Per recipient timezone (24h shaped) Postmaster IP rep High; full rotation

The "spread" column matters as much as the daily cap. A 50,000-message campaign sent in 2 hours from a fresh IP is dramatically worse for reputation than the same volume over 12 hours.

Reading the AcelleMail dashboard for throttle effectiveness

The per-server hourly graph (Admin → Sending Servers → → Stats) should show roughly flat hourly throughput during the campaign window. Spike patterns indicate problems:

  • Single tall spike at hour 0 — your throttle isn't working; messages are bursting from the worker.
  • Sawtooth pattern — workers are recycling rapidly, possibly due to memory limits. Bump --memory=512.
  • Plateau then sudden drop — you hit the daily quota mid-campaign; bump the quota or split across more servers.
  • Plateau with a dip mid-window — receiver-side throttling (often Microsoft RP-002). Investigate SNDS for the affected IP.

The healthy pattern is a flat rectangle. Hour-to-hour variance < 20 % means the throttle is doing its job.

More in Sending & Deliverability