The Truth About Slow Sending: It's Not Just SMTP, It's FluentCRM's Hardcoded Limits
Hello everyone,
I have been following discussions about speed issues closely, and honestly, I am tired of seeing the same standard responses blaming SMTP providers or Cron configurations. I spent days fighting with FluentCRM support over this exact issue, and I need to expose what is really happening to save you all some time.
I run a beast of a dedicated server (128GB RAM, 16 CPU Cores, NVMe, 30 public IPs) and use Elastic Email via FluentSMTP. Theoretically, I should be flying. In practice? My sending was choked at ~200 emails per minute.
For a long time, support kept telling me to increase PHP max_execution_time, check the server Cron, or blame SMTP latency. Total BS.
The truth that support hides (until you back them into a corner, like I did) is that FluentCRM has HARDCODED LIMITERS.
The issue isn't the SMTP protocol or API. The issue is that the plugin was designed to cap the amount of data it fetches from the database at a time—probably to protect cheap shared hosting—but this kills anyone with professional infrastructure who needs to scale.
After I audited the code myself and proved it to them, support finally admitted it and gave me the list of files where these handbrakes are hidden.
If you want real speed (above 10k/hour), forget about switching SMTPs for a moment. The bottleneck is HERE, inside the plugin folder:
- Slow Automations:
In /wp-content/plugins/fluent-crm/app/Services/Funnel/FunnelProcessor.php, there is a line with ->limit(200). The plugin only processes 200 contacts per automation cycle, no matter if your server can handle 10,000.
I made this change:
$jobs = FunnelSubscriber::whereIn('status', $statuses)
->whereHas('funnel', function ($q) {
return $q->where('status', 'published');
})
->where('next_execution_time', '<=', current_time('mysql'))
->whereNotNull('next_execution_time')
->orderBy('next_execution_time', 'ASC')
->limit(6000)// Increased to process 6000 records at a time
->get();
- Bulk Actions:
In /wp-content/plugins/fluent-crm/app/Http/Controllers/SubscriberController.php, there is a ->limit(400). Trying to tag 50k people? Sit down and wait, because it is going 400 by 400.
I made this change:
$subscribersModel = $subscribersModel->select(['id'])
->limit(3000) // Increased the batch size
->where('id', '>', $lastId)
->get();
- Email Sequences:
In /wp-content/plugins/fluentcampaign-pro/app/Hooks/Handlers/EmailScheduleHandler.php, the query for next sends is done via SequenceTracker::ofNextTrackers()->limit(XXX). The default limit is extremely low. I had to force change the code to ->limit(1000), otherwise it spoon-feeds the queue and the sending never finishes.
I made this change:
$processTrackers = SequenceTracker::ofNextTrackers()->limit(1000)->get();
What makes me angry:
They know this. In my ticket, after I sent a video proving my hardware was idling while the plugin was sleeping, the developer (Masiur) finally said: "You can adjust the values in these files to increase volume, but we don't support that."
In other words, they sell a marketing automation tool but hide the fact that it comes crippled out of the box. I had to manually edit the plugin core (which is terrible for future updates) just to utilize my server's power.
So, answering the general questions about speed: Amazon SES is great, use the API. But if you don't change these internal limits in the FluentCRM code, you could hire a NASA server and the sending will still be slow.
Honestly, I don't know if I caught all the throttles, as the code operates like a black box that the team refuses to document for the community. I still notice slowness in other areas, and I know the standard answer will be to blame my server, but tests prove the software is the brake.
You need to IMMEDIATELY document all these hidden limitations (hard limits) scattered throughout the code—whether in flows, timeouts, or processing batches (contacts processed per minute). Selling an "unlimited" tool that comes throttled out of the box is shameful.
Be warned: stop blaming the SMTP before looking at how the plugin queries the database or hands off emails to the SMTP.
As I was writing this reply, my wife asked me why I was banging on the keyboard so hard.
I hope Shahjahan Jewel reads this.
Best regards.
There has a lots of reason why we had to choose those values. The easy answer is: we have to serve all kinds of customers and servers.
- ->limit(3000) // Increased the batch size => Here is the first problem: Let's say, you have one or more automations connected with the bulk action (tag applied). Now for each contact, the automation need to started, so more database queries. So it may get stuck on that call. It will fail or do a timeout error.
- ofNextTrackers()->limit(1000) => Kind of same. For smaller server, it may make the server super slow or bring the whole server down.
All the automation or email sending logics are super optimized, and it we did 100s of tests on different environments and make sure it work.
Most of the functions are recursive so it works batch by batch.
I hope it clears some of the confusion and you get the behind the story why we choose those numbers.
Shahjahan Jewel Thanks for the explanation, but let's be intellectually honest here. Your argument about "protecting smaller servers" justifies a default setting, but it absolutely does not justify a hardcoded, unchangeable limit.
You mentioned: "Today, we are sending almost 100K BFCM emails with FluentCRM + Amazon SES."
Let's do the math based on the hardcoded limits I found in your code:
If the Email Sequence handler is limited to fetching 50-100 trackers (or even 200) per execution cycle, and the cron runs once per minute:
100 emails/minute = 6,000 emails/hour.
To send your 100,000 emails, it would take approximately 16.6 hours with the default settings.
So, I have two questions for you:
- Did your BFCM campaign really take 16+ hours to finish sending?
- Or did you modify your own plugin (like I had to) to achieve higher throughput?
If you modified it, you are admitting the default product is insufficient for high volume. If you didn't, and you are claiming it was fast, then the math simply doesn't add up with the code I see in the files.
Regarding your concern about timeouts and crashing servers:
I have 128GB of RAM and 16 Cores. I have adjusted my PHP max_execution_time to allow for heavy processing. Why should my server sit idle 90% of the time because you are worried about a user on a $5 shared hosting plan?
If the code is "super optimized" as you say, why relies on hard limits instead of environment variables?
The standard practice in professional WordPress development for this scenario is simple: use apply_filters().
Instead of:
->limit(200)
It should be:
->limit(apply_filters('fluent_crm_process_limit', 200))
This would protect your small users (keeping default at 200) while allowing power users to scale without hacking core files.
Why hasn't this been implemented yet? Why do I have to hack core files to make the software utilize the hardware I pay for? This is a "Black Box" approach that hurts your most advanced customers.
Eduardo Sachs We will definitely add the filter hooks for advanced users. By the way, you are using Campaign or email sequences?
Shahjahan Jewel Hello Shahjahan,
I am glad to hear that you will implement the filter hooks. This is the standard WordPress way of doing things, and it solves the problem for power users without affecting small servers. We appreciate that decision.
To answer your question directly: I use ALL of them.
- Standard Campaigns (Broadcasts).
- Email Sequences (inside Automations).
- "Send Custom Email" action blocks (inside Automations).
- Bulk Actions (Tagging/List management).
The bottlenecks I specifically tracked down and patched were in FunnelProcessor.php, SubscriberController.php, and EmailScheduleHandler.php. However, since I don't have full visibility into your entire codebase, I cannot list every single file that needs attention.
So, my request is simple: Please perform a full audit of your code (both Core and Pro versions). Wherever there is a hardcoded `->limit(X)` or a batch processing size defined, please wrap it in an `apply_filters()`.
Now, I need to circle back to one thing you said earlier, because transparency is critical here.
You mentioned: "Today, we are sending almost 100K BFCM emails with FluentCRM + Amazon SES."
I want to be very honest with you: that statement felt very misleading given the code I found in your plugin. Let's do the math based on the hardcoded limits currently in the public repository:
If the Sequence/Funnel processor is hardcoded to fetch only ~100-200 items per execution cycle (every minute via Cron):
- 200 emails/minute = 12,000 emails/hour.
- To send 100,000 emails, it would take approximately 8.5 hours.
So, regarding your 100k BFCM campaign, only two scenarios are possible:
Scenario A: You are using a modified version of FluentCRM internally where you increased these limits (just like I did manually). If this is the case, it proves my point that the public version is crippled.
Scenario B: You actually took 8 to 10 hours to send that campaign. In the world of high-volume email marketing, taking 8+ hours to blast a list is not "fast" — it is a bottleneck. If I have a time-sensitive offer, I need to flush 100k emails in 1 or 2 hours max, which my hardware and SES account can easily handle, but your software prevents.
That is why I was so frustrated. It felt like you were telling the community "it works fine for us," while omitting the fact that either you have a special configuration, or you have a very different definition of what "fast sending" means compared to enterprise standards.
Please ensure that when you add the hooks, you cover ALL processing points (Campaigns, Sequences, Custom Emails inside Automations, and Bulk Actions). We need full control over the throughput.
I look forward to the update.
Shahjahan Jewel when specifically will this be added to the plugin please?
Following!
Following this
We have a relatively beefy server (32 cores / 128gb ram), and would very much want to send at least 75k emails in an hour, using Amazon SES. It seems like this wouldn't be an option with the current config. Is that right Shahjahan Jewel?
We're slowly migrating more of our sending to FluentCRM, and want to be prepared. Happy to do some custom coding if needed.
Jacob Jans Give up on this shit. FluentCRM wasn’t made for this. I’m a solo entrepreneur, so I had no way to change the implementation, I literally spent a month and a half integrating FluentCRM. And I got fucked. Because once I started using it, I found all these shitty limitations in FluentCRM’s own code. And now I can’t change it, because I’d have to drop everything and waste another month or more to rebuild it. My setup is massive, multiple digital products, over 2.8 million leads, tons of automations, tags, lists, webhooks… The whole thing is a massive mess to manage. And the FluentCRM team? They don’t give a shit. Their attitude is basically: “fuck you.”
Obviously they’re not literally saying ‘fuck you,’ but that’s exactly how it feels.
Eduardo Sachs --- Maybe Shahjahan Jewel can reassure both of us!? It's not too late!? Shahjahan Jewel ???
Jacob Jans I've already lost hope.
Eduardo Sachs -- I do wish software companies were more responsive. I know its tough to run a business with a variety of customer needs, but....as someone that runs such a business, I know how valuable listening to customers can be. It's helped us grow tremendously. Shahjahan Jewel
How long will a campaign with 10,000 subscribers take to send with the default settings using Amazon SES?
Will the upcoming FluentCRM 3.0 give more fine-tuning to the send rates?
I've been relucatant to switch from Mailchimp since speedy send rates is crucial for us.
Matty OhOh About 50 minutes based on the maths the others mention here
I gave up on using FluentCRM on a medium size list some time back (circa 25k). I just couldn't get it to send quick enough after trying multiple times, and meanwhile this is the reason.
Really disappointing and not the way I would expect the Fluent team to handle things i.e. not properly document this
I can understand the concern that changing the settings would cause things to fail on less powerful servers - but this could be explained
These really should just be settings - but even filters are fine and not a big deal.
I'm starting to get pretty upset by this. I distinctly remember reading some reviews and responses from WP Manage Ninja before purchasing FluentCRM. They said there are many customers handling lists of 100k with no problems. It seems to me that taking 8 hours to send to a list of 100k is very much a problem. It also seems clear that it's hardcoded to only send up to 12k emails per hour. (200 per minute.) This is not acceptable. Honestly, I'm feeling deceived at this point, but maybe there's a solution? Shahjahan Jewel
Matty OhOh Zunaid Khan Jacob Jans
I want you all to notice something very important. Look at my previous replies to Shahjahan Jewel and look at his silence.
His silence regarding the math is deafening.
He threw out a statement: "Today, we are sending almost 100K BFCM emails with FluentCRM." It sounds impressive, right? But when I confronted him with the hard evidence found inside his own code—proving that with the default hardcoded limits, sending 100k emails would mathematically take 8 to 16 hours—he completely ignored it.
He refused to answer the two most critical questions I asked:
- Did their "successful" BFCM campaign take an entire day to finish sending?
- Or do they use a modified "internal" version of the plugin that lifts these limits, while selling us the throttled version?
Jacob Jans , you are absolutely right to feel deceived. When they sell a product as "Marketing Automation for WordPress" and claim it handles big lists, but hardcode a limit(200) that forces a 128GB RAM server to perform like a $5 shared hosting plan, that is misleading. The "Unlimited" label is marketing; the code tells the real story.
Matty OhOh , to answer your question: With the default settings hidden in the code (processing ~200 emails/minute via cron), a 10,000 subscriber list will take roughly 50 minutes to an hour to process. If you are doing a time-sensitive flash sale, your last subscribers will get the email an hour late, even if you are using Amazon SES and a dedicated server.
Zunaid Khan , you weren't crazy. You left because the software was the bottleneck. It’s frustrating to know that support likely told you to "check your cron" or "optimize your server" when the brake pedal was welded to the floor inside the plugin files the whole time.
Shahjahan Jewel , the community is still waiting. You cannot mention a 100k email campaign as a "success story" and then ignore the mathematical proof that your public software is incapable of sending that volume in a reasonable timeframe.
Are you going to admit that 100k emails take 8+ hours on the public version, or are you going to admit you use a different version than your customers?
Stop dodging the math.
Eduardo Sachs you found an interesting point, but it's sad to see you frustrated and angry not sure it's going to foster a conversation here. What seems to be behind your request is a demand for advanced settings to be override for some users who have special setups. You're pointing communication issues leading to frustration, point taken. Your pointing a company choice with limits to a software that needs to pass many uses cases and their transparency about it. You're pointing marketing wording to sell their stuff (ok!). But as a solution, what I understand you want and where we could easily get behind is to have more options for larger setup. We haven't jumped yet on the wagon because we have 1.9M emails for a client and we're scared to go from Klavyo to FluentCRM for the reasons your mentioning. Shahjahan Jewel would be nice indeed to add it!
We just ran our own tests with this and found something interesting.
Setup:
FluentCRM version 2.9.80
FluentSMTP
SMTP2GO paid plan (200,000/hr max rate)
DigitalOcean Basic Droplet, 2x Shared CPU / 8 GB RAM
PHP memory_limit = 256M
WP_CHRON = false
Server chron interval = 1 min
FluentCRM max rate setting = 50/s (about 180,000/hr)
FluentCRM multithread sending = enabled
Email Campaign 8400 emails
Email size = 40k
Real send:
Sending takes 2h 50m
Send rate = 3,600/hr = 60/m = 1.00/s
Server CPU hits 30% max up from about 20% baseline (used +10% CPU).
This is way too slow. I can see in both the FluentSMTP logs and the SMTP2GO logs the rate is only about 1 email per 1-4 seconds.
Test send:
We set FluentSMTP to Email Simulation/Disable sending all emails and resent the campaign.
Results:
Done in 3 minutes.
Send rate = 174,540/hr = 2,909/min = 39/sec
Server CPU hit 67% max up from about 20% baseline (used +47% CPU)
This is much better! However this is just a test send, no actual emails were delivered.
So for us the problem doesn't seem to be in the FluentCRM's plugins limits or with any server limit. Even a 2 CPU/ 8GB RAM server has enough capacity. The issue appears somewhere downstream, maybe in FluentSMTP or in server chron? It really looks like the send rate is equal to chron rate, as if each chron run has an email batch size of 1.
Cayden Mak welcome to the club...
Eduardo Sachs We've enabled system logging and will report back when we discover the source of the slowdown.
I can't imaging the SMTP API is very server intensive. It should not be throttled like this.
Eduardo Sachs Here's what logging tells us. We extracted all the "Sent" logs and calculated sending rate in emails/second.
4841 2026-01-12 13:53:59 Handler::handle: Sent 40 61 seconds via cron 0.66
4846 2026-01-12 13:54:52 Handler::handle: Sent 40 53 seconds via ajax 0.75
4851 2026-01-12 13:55:59 Handler::handle: Sent 40 68 seconds via ajax 0.59
4857 2026-01-12 13:57:16 Handler::handle: Sent 40 78 seconds via ajax 0.51
4862 2026-01-12 13:58:19 Handler::handle: Sent 40 63 seconds via ajax 0.63
4867 2026-01-12 13:59:22 Handler::handle: Sent 20 63 seconds via ajax 0.32
4873 2026-01-12 14:00:47 Handler::handle: Sent 24 85 seconds via ajax 0.28
4891 2026-01-12 14:10:07 Handler::handle: Sent 20 63 seconds via cron 0.32
4892 2026-01-12 14:10:18 Handler::handle: Sent 4 12 seconds via ajax 0.33
This suggests it really is taking 1-4 seconds to send one email to SMTP2GO.
The logs don't suggest where the slowdown exists. There's also no indication why some emails are send via cron and some via AJAX, but the speed seems more or less the same.
Again, we have a ton of unused server capacity. We're only using 10% extra CPU load to send at roughly 60 emails per minute.
Cayden Mak -- I wonder if the system is waiting for a response from the email delivery provider before moving on to sending another email; if so, this would explain the slow sending. I had a similar problem with different email system; when our SMTP server started responding slowly, it drastically decreased sending time. Fixing the SMTP slowdown fixed the issue. The send rate in test mode suggests this may be the problem.
A properly implemented queuing system would prevent this issue, as it could send emails in parallel, without waiting on a response from the server. I haven't looked at any of the code, so I don't know if there's a Wordpress email delivery plugin that does this. Just a thought.
Jacob Jans As best as we can tell this is exactly the issue. Emails are sent sequentially; every email must complete the entire cycle from prep, to HTTP API call, through confirmation and logging, before the next email can be sent. This can take 1-2 seconds per email.
### Per-Email Timing Breakdown
**Current (HTTP API):**
FluentCRM prep: ~0.05 sec
wp_mail() call: ~0.02 sec
HTTP request setup: ~0.05 sec
Network latency out: ~0.15 sec
SMTP2GO API process: ~0.50 sec
Network latency back: ~0.15 sec
FluentSMTP response: ~0.08 sec
Logging/cleanup: ~0.05 sec
Rate limiter check: ~0.05 sec
────────────────────────────────
Total per email: ~1.10 sec (theoretical)
Actual observed: ~1.60 sec
Missing time: ~0.50 sec (network variance, API delays)
This means that while SMTP2GO, for example, can handle 200,000/hr, this capacity is totally wasted because FluentSMTP is only sending sequentially.
We're writing a two part work-around now: first, switch FluentSMTP from "SMTP2GO" API as the connection method to generic SMTP as the connection method. Second, we're writing a plugin that will keep the SMTP connection open for multiple emails. This means we can open and port and flood it with sequential SMTP sends without closing and reopening a new HTTP API connection. This is exactly what the SMTP protocol was invented to handle, but FluentSMTP won't natively keep the SMTP connection open.
Multi-thread sending should solve this issue, but the current implementation's limits seem to impact concurrency.
FCRM's multi-thread sending is not a true system-level multithread, but a pseudo multithreading managed by FCRM. Only the first batch is initiated by cron, all other batch processes are spawned by AJAX calls. The number of concurrent AJAX calls is limited by a RAM check:
protected function memoryExceeded()
{
$memory_limit = fluentCrmGetMemoryLimit() * 0.70;
$current_memory = memory_get_usage(true);
$memory_exceeded = $current_memory >= $memory_limit;
return apply_filters('fluentcrm_memory_exceeded', $memory_exceeded, $this);
}
How this works on servers with NGNIX and PHP-FPM:
Each new AJAX batch process will be handled by the first available PHP worker. Each batch process will check to see if the current FluentCRM memory usage has hit 70% of max memory, and if it hasn't, then a new AJAX batch process will spawn.
In theory this should mean that batch processes will spawn and run in parallel until 70% of system memory is used. But that's doesn't appear to be happening; our server (yes, it's tiny) never cracks 50% RAM usage (up from our 40% baseline).
This is because each batch process is only checking the memory_limit for an individual PHP worker, and if that individual worker is at 70% of RAM usage, no additional AJAX batch processes will spawn and the remaining PHP workers will be idle.
Now comes the conundrum: we could increase the memory_limit and each worker will now have more memory allowing more AJAX batch processes to spawn. But because the server RAM is fixed, this means we need to set a lower number of max available PHP workers. We will spawn more batches, but we'll have fewer workers to handle the additional batches, and while workers can work in parallel with other workers, each individual worker handles each batch sequentially.
PHP workers are like grocery store cashiers — each one has a single line of customers waiting. FluentCRM is seeing that this cashier's lane is almost full so it won't spawn more customers even though we have idle cashiers in other lanes. We only have a fixed RAM budget to pay all cashiers. We can reduce the total number of cashiers and pay the remaining ones more RAM, and this will tell FluentCRM that we have capacity and it will spawn more customers, but now we don't have as many cashiers to process those customers.
TLDR; FluentCRM seem to only checks the current worker for memory_limit, but it needs to be checking the system's memory for memory limits, because the true limit is not one child's max, but rather pm.maxchildren * memory_limit
How's this working in the real world?
Looking at our logs above you can see there is very little batch concurrency. Each batch is being generated 30-70 seconds after the previous one, and takes 60-90 seconds to complete. There's a slight overlap, which means we're using up to four workers at once (out of twenty workers available). Each batch has a rate of about 0.5 emails per second, and after multi-thread does its magic we average about 0.95 email per second.
SMTP2GO offers up to 55 emails per second.
Just curious if anyone has any news on this discussion. We're looking at alternatives to our current mass email service and are considering FluentCRM. We have a contact list of approximately 170,000. Most email campaigns are sent to between 50 and 2000 contacts but we do send 2 or 3 a week that go to approximately 120,000. I'd prefer that it didn't take forever to send those 120,000 emails. We would likely use Amazon SES.
So, is FluentCRM still hardcoded for a max sending rate or are there filters now to change the max?
Thanks.
I totally agree. The speeds after upgrade are disastrous. Currently it processes 10-20 mails per minute. When sending time dependant mail campains it takes days for a lust of tens of thousands of subscribers
Confirming above s analysis with hard numbers from a different provider and region - and
pointing at the one fix that would actually help.
We hit the exact same wall. Setup: Cloudways (DigitalOcean Sydney, 2 vCPU, 3.8GB), PHP 8.1,
FluentCRM 3.1.0 + Campaign Pro 3.1.0 + FluentSMTP 2.2.95, Mailgun (US region), ~80k list.
Our numbers match cayden's almost exactly:
- ~30-40 emails/minute on a real campaign (~0.5-0.65/sec). Peak 4/sec, average far lower.
- Sending is sequential, one email fully completing prep -> API call -> confirmation -> log
before the next starts, exactly as cayden described. - CPU sits ~88% idle. Not CPU-bound, not memory-bound, not SMTP-throttled. Mailgun accepts
100% with zero failures.
What we add that the thread doesn't have yet - WHY each call is slow:
We timed the Mailgun API call directly with curl, 5 consecutive calls. Each is ~0.5s, of
which ~0.35s is the TLS handshake, because FluentSMTP opens a fresh HTTPS connection for
every single email and never reuses it. From a Sydney server to Mailgun's US region, that
handshake is pure geographic round-trip cost, paid 80,000 times.
This is the key point for the team: for anyone whose server isn't right next to their
provider, the hardcoded limit() values and batch size are the WRONG lever. The dominant
cost is the per-email connection setup. Raising batch sizes does nothing if each email
still pays a fresh TLS handshake. We tested it - raising mailer_multi_thread_chunk_size 20
to 250 and action_scheduler_queue_runner_concurrent_batches 1 to 2 changed nothing.
The fixes that would actually matter:
- HTTP keep-alive / connection reuse across a sending chunk. Reusing one connection would
drop ~0.35s off most sends and roughly triple throughput on distant servers, with zero risk
to small hosts. - True parallel sending (jpjans' point) instead of pseudo-multithreading capped at ~4
memory-gated workers.
And the filter hooks jewel promised on 23 November 2025 still aren't shipped, six months
on. Even those would let advanced users self-serve.
Happy to share full curl timings and send logs. This is a solvable problem and the
community has now diagnosed it twice over.
Fabienne Wintle Possibly a dumb question but would using a different email sending plugin instead of FluentSMTP resolve the TLS issue (assuming the different plugin doesn't also have the TLS issue) and ultimately increase FluentCRM sending speed?
Thomas Oates I don't know - maybe I should use the mailgun plugin directly? according to my research in the code it would not change and I suspect many people much smarter than me would have already given this a try? I have also sent a support ticket so I will report here.
Confirming the regression on Cloudways Autonomous (managed Kubernetes) — and why "enable multi-threading" is not the answer
Same wall as Fabienne and Angus, plus a finding specific to autoscaling/Kubernetes hosts that I haven't seen spelled out yet — and some frustration, because sending speed is not a nice-to-have, it's the whole point of a campaign tool.
Setup: Cloudways Autonomous (managed Kubernetes autoscaling, not a fixed droplet — the pod reports 4 vCPU / 15GB via nproc, but the underlying model is containerized with separate pods for web and cron, which turns out to matter a lot). PHP 8.1, FluentCRM 3.1.0 + Campaign Pro 3.1.0 + FluentSMTP 2.2.95, Elastic Email API, EU region (same region as the server, ~0.05s API latency — the connection itself is not the issue), ~28k campaigns.
Here is what stings. On 2.x, same hosting, same SMTP, same everything, we sent at ~220/min, smooth and continuous — a 28k campaign cleared in roughly two hours. After upgrading to 3.x: ~40/min with 8 parallel CLI workers, and ~6/min on plain cron. Nothing changed except the plugin version. A campaign that used to take 2 hours now takes most of a day. I've been running FluentCRM for nearly 4 years, and this is the first time I'm seriously pricing external senders again — because right now it would cost me less time and money to go back to a dedicated ESP than to keep fighting this on every single send.
On the "just enable multi-threading" advice that keeps getting repeated: we tested it exhaustively, and on this kind of host it's a mirage. Multi-threading fires a few strong bursts — genuine peaks of 500-760/min for two or three minutes — then dies, falls back to ~5/min, and won't restart. The reason is the loopback/AJAX spawning model. On a Kubernetes host the cron pod and the web pod are separate containers, and the loopback requests that spawn the parallel batches get reaped before they finish. Emails get claimed into "processing," the spawned worker dies, they fall back to pending, and the cycle repeats without ever sending. CPU sits ~80% idle the whole time. So multi-threading looks like it's working for the first couple of minutes, then silently stops carrying the campaign — which is arguably worse than slow-but-steady, because it hides the failure.
We also confirmed what others reported: raising mailer_multi_thread_chunk_size, action_scheduler_queue_runner_concurrent_batches, the AS time limit, batch size — none of it moves the needle. The bottleneck is per-email connection cost and the spawning model, not batch size. Exactly Fabienne's TLS-handshake point.
The only thing that reliably sends on 3.x for us is parallel CLI workers (wp fluent_crm cli_send), which is what the docs quietly recommend over concurrency anyway — but that needs SSH and a persistent babysat process, which is absurd as the "supported" path for a campaign you should be able to fire from wp-admin and walk away from. On autoscaling hosts it's even worse, because processes started by the scheduler get reaped when the pod cycles.
@Shahjahan Jewel — the honest question: what regressed in the sending path between 2.x and 3.x? 2.x did 220/min on this exact setup. If the fix is connection reuse / HTTP keep-alive, please say so and put it on the roadmap, alongside the filter hooks promised back in November. A lot of long-time users are at the point of leaving over this, and we'd genuinely rather not.
Nikola Belopitov This is genuinely disrespectful to the consumer.
After an unsuccessful upgrade to 3.1.0, I had to roll back to 2.9.86 — and I want to share why, honestly, with real numbers.
Same server, same SMTP (Elastic Email, EU), same configuration. The only variable was the plugin version.
On 3.1.0 — my large campaigns (~28,000 subscribers) sent at roughly 5–20 emails per minute. A single 28k campaign dragged on for many hours, in the worst case spreading across days before finishing. Even with multiple CLI workers (unique option keys, staggered offsets, exactly as recommended) I could only reach ~40/min in short bursts, never sustained.
On 2.9.86 — a 25,000+ subscriber campaign completed in 39 minutes. Measured directly, start to last email sent.
Same machine. Same provider. ~28k on 3.1 = hours to days. ~25k on 2.9.86 = 39 minutes.
I did extensive diagnostics before concluding anything: CPU sat 79–88% idle during sends, EU→EU latency to my ESP was ~0.05s, the provider accepted 100% of submissions, memory was never a constraint. The bottleneck was clearly per-email processing overhead in the 3.x sending path — not my server, not my network, not the provider. At high send rates 3.1 also generated hundreds of cURL error 28 (connection timeout) entries, consistent with a new HTTPS connection being opened per email rather than reused.
I'll leave the conclusions to each user. For me: if you genuinely depend on this system for production work, staying on the older version is the safer choice right now.
And to the developers — with full and genuine respect for the enormous work you've put in — I don't believe it's right to push from something that still behaves like a beta into production-critical projects while regressions this serious remain. In my honest opinion, 3.1 is not yet ready for general rollout.
Happy to share my full diagnostics if it would help the team.