Hi all!
Finally today I released the first version of a clone of Mailgun’s REST API!
The main working API is the API that send emails. Stats and advanced features are not yet supported, but you can send newsletters :-P
This way, all the users disappointed that you couldn’t use your own SMTP server, you can use this web application, configure it with your own SMTP server and send newsletters from Ghost making them think they are using Mailgun, but they actually use the same API but through their own free web service.
The only (little) issue is that you have to make a manual change on MySQL to your installation to tell Ghost what the address of your own web service is. You need to edit in the settings table by modifying the record with key=‘mailgun_base_url’.
To avoid this `hack’, I ask the Ghost team to put a third option in Ghost’s Mailgun settings, where you currently choose the US or EU zone, adding CUSTOM or something similar, so that you can enter the URL of your “fake” Mailgun server. This will make many users very happy without making heavy code changes to use a pure SMTP server :-)
PS: if you like the project, based on this complexity, you can make a donation (read the README) :-D
Please do not edit the MySQL database for this. If you’re already self-hosting, you have a configuration somewhere. Either environment variables (Docker) or a config.[environment].json.
Use that.
For environment varibles set mail__options__hostbulkEmail__mailgun__baseUrl to the URL.
For the JSON file, set mail.options.hostbulkEmail.mailgun.baseUrl to the URL.
as a ghost pro user, its an included feature, no headaches
if self hosted I have to setup Mailgun
Though I can always sync members to an external solution and take it from there (passing on the build in posting/sending capabilities … which at least is for me a strong selling point:))
I managed to get this up and running by setting this up on Docker with SMTP2GO as the SMTP server and connecting it via Tailscale to my Ghost instance. However, I noticed that while testing with a single recipient usually worked successfully, for sending a newsletter to more people, the newsletters would end up being duplicated (causing a newsletter to be sent to a recipient at least 7 times) and I was starting to get errors like:
[2026-01-01 14:45:03] ERROR ECONNABORTED: timeout of 60000ms exceeded ECONNABORTED: timeout of 60000ms exceeded "Mailgun Error 400: timeout of 60000ms exceeded" "``https://ghost.org/docs/newsletters/#bulk-email-configuration``" Error ID: 74fe54c0-e720-11f0-b8b9-adfa512a8ab5 Error Code: BULK_EMAIL_SEND_FAILED
and also:
[2026-01-01 14:45:03] INFO [BULK_EMAIL_DB_RETRY] save batch 6956845108beef000143458c -> failed - Started (1st try) [2026-01-01 14:45:04] INFO [BULK_EMAIL_DB_RETRY] save batch 6956845108beef000143458c -> failed - Finished (after 1st try) [2026-01-01 14:45:04] INFO [BULK_EMAIL_DB_RETRY] save EmailRecipients 6956845108beef000143458c processed_at - Started (1st try) [2026-01-01 14:45:04] INFO [BULK_EMAIL_DB_RETRY] save EmailRecipients 6956845108beef000143458c processed_at - Finished (after 1st try) [2026-01-01 14:45:04] ERROR Error sending email 6956845008beef000143458b Error sending email 6956845008beef000143458b Error ID: 7556d500-e720-11f0-b8b9-adfa512a8ab5 Error Code: BULK_EMAIL_SEND_FAILED ---------------------------------------- EmailError: An unexpected error occurred, please retry sending your newsletter. at BatchSendingService.emailJob (/var/lib/ghost/versions/5.130.5/core/server/services/email-service/BatchSendingService.js:167:32) at BatchSendingService.sendBatches (/var/lib/ghost/versions/5.130.5/core/server/services/email-service/BatchSendingService.js:403:19) at async BatchSendingService.sendEmail (/var/lib/ghost/versions/5.130.5/core/server/services/email-service/BatchSendingService.js:213:9) at async BatchSendingService.emailJob (/var/lib/ghost/versions/5.130.5/core/server/services/email-service/BatchSendingService.js:158:13) at async /var/lib/ghost/versions/5.130.5/node_modules/@tryghost/job-manager/lib/JobManager.js:260:25 at async JobManager.worker (/var/lib/ghost/versions/5.130.5/node_modules/@tryghost/job-manager/lib/JobManager.js:16:22)
I’ll attempt to increase the timeout on the API side to see if that fixes it, but if anyone can provide a better fix, that would be very helpful.
Also note for anyone else setting this up, the auth password in the API config ('password' => 'key-test123456789') should be the same as what’s in the API key field or bulkEmail__mailgun__apiKey. Took a couple hours struggling through Unauthorized errors only to find out that the username isn’t supposed to be included in the API key.
I checked the code quickly. I tries to send mails to every recipient one-by-one, with the request comes from Ghost. But sending mail with SMTP takes so much time. With a large recipient list, it can’t fit the timeout limit of Ghost (60 seconds, normally Mailgun responds under 1 second).
This is a confirmation of why sending bulk mail with SMTP is a bad idea. To achieve this, you need to have a distributed SMTP server, which can send mails concurrently from multiple servers at the same time. And you will need a queue mechanism to handle this in the background, while monitoring the failures.
These are the things Mailgun (or any other bulk mailing service) handle for you.
TLDR; don’t try to send newsletters with an SMTP server. At least use something like an AWS SES proxy, if you want to achieve a cheaper solution than Mailgun.
I was investigating building something similar, so I’m pleased to find this thread. I’m running a couple very small Ghost installations (personal blogs, etc.) and have been having deliverability issues with Mailgun as of the last couple months. Mailgun obviously uses a shared IP for their free tier, so sometimes you get unlucky and your mails get sent out from an IP that’s recently burnt its reputation. Meanwhile I’ve self-hosted email for decades and have a highly reputable MTA IP. I also believe strongly in resisting the general consolidation of email infrastructure.
From @mashumarusero‘s report, it seems this implementation of libre-mail-api suffers from a poor timeout/retry interaction with Ghost:
Ghost sends an email to libre-mail-api which takes too long to respond
Ghost times out and tries again, causing a second invocation of libre-mail-api
This happens a few times until one of them responds to Ghost before the timeout, or Ghost gives up.
All invocations of libre-mail-api eventually complete their sending via SMTP
Recipient gets multiple copies of the email
This should be solvable by making libre-mail-api spool the email temporarily, immediately respond to Ghost, then send the emails via SMTP at its own pace. To further mitigate duplicates, it could record the message ID and deduplicate based on that. These two improvements might be difficult to implement in a language like PHP, but would be quite straightforward in a language like Go.
On the other hand, this may also be mitigated by ensuring that libre-mail-api is only used with a local SMTP server running on the same machine, that can spool the messages and acknowledge quickly (and then later relay them to their destination).
Maybe it worths to mention that, when I say “don’t send bulk email with SMTP”, I mean the methodology, not the SMTP technology itself. Because SMTP is the communication protocol between mail servers. Even Mailgun uses SMTP to send emails in the end. But trying to send hundreds or thousands of emails from a single SMTP server don’t scale and would get many headaches. Bulk Email services like Mailgun or AWS SES;
Uses queue mechanism
Rate limits per recipient domain
Uses hundreds of SMTP servers
Spread the load between warm and cold sender IPs, by continuously monitoring their reputation
Track bounces, failures, complaints and act according to them (like breaking deliver in the middle if there are too many failures)
If you do all of those by yourself, or use a tool/service doing them for you (like an MTA), then of course you can send with SMTP (if you name it sending with SMTP). Otherwise, trying to send your newsletter by just using an SMTP server, will simply fail. (And of course sending newsletter here means sending same email to more than hundred email addresses, not a few people)
Curious. Do you think there would be a better outcome if someone could modify the codebase and send it through listmonk?
It seems that program is pretty widely used and favored by some newsletter organizations that are reluctant on using Mailgun.
I don’t know much about it since I’ve just stuck to Ghost.
I think phplist is an alternative to listmonk as well.
If listmonk also just uses a single SMTP server behind it…meh. No real benefit.
The problem isn’t what sits between Ghost and your mail server, it’s what happens after. No matter if you route through listmonk, the Mailgun API clone, or anything else, if you’re pointing at your own SMTP on a random VPS, you’re still facing the same issues. Dodgy IP reputation, no feedback loops with Gmail/Microsoft, etc., and the risk of ending up on blocklists within a few days.
Sending a newsletter to your 10 friends? Fine. Sending to 50k subscribers from a single self-hosted SMTP? Not so fine. The necessary throttling alone already makes it unbearable.
Where listmonk could make sense is if you pair it with something like Amazon SES as the backend. Then you get proper infrastructure with the cost savings. But at that point you could also just use the existing SES proxies that exist (see Murat’s reply earlier).
The middleware doesn’t matter much. What matters is whether there’s a real ESP at the end or just your lonely mail server hoping Gmail doesn’t notice.
I haven’t used them, I just see people bring it up all the time when someone brings in Mailgun complaints to arguments with Ghost.
Thanks for answering my question!
The idea is to be able to funnel to different providers, even stay within their free tier and have load balancing and failovers. Let me know if those interest you. https://mailpunch.app/