Finally a clone of Mailgun API! (aka you can send newsletters with your SMTP)

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

6 Likes

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__host bulkEmail__mailgun__baseUrl to the URL.
For the JSON file, set mail.options.host bulkEmail.mailgun.baseUrl to the URL.

2 Likes

This option is useful for transational emails, but bulk emails for newsletters ignore these mail settings as documented :-(

LOL my bad.

It’s bulkEmail__mailgun__baseUrl and bulkEmail.mailgun.baseUrl respectively.

2 Likes

Very interesting, I will try it, thanks a lot!

2 Likes

This means, Mailgun is still the only offical supported provider? I can not just use a service like Resend?

Yes, correct:

Thanks, so to wrap it up:

  1. as a ghost pro user, its an included feature, no headaches
  2. if self hosted I have to setup Mailgun
  3. 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:))

Yes, that’s a pretty good summary. Plus, most other managed hosting providers apart from Ghost(Pro) also include the email setup

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.

1 Like

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.

4 Likes

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:

  1. Ghost sends an email to libre-mail-api which takes too long to respond
  2. Ghost times out and tries again, causing a second invocation of libre-mail-api
  3. This happens a few times until one of them responds to Ghost before the timeout, or Ghost gives up.
  4. All invocations of libre-mail-api eventually complete their sending via SMTP
  5. 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).

2 Likes

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)

2 Likes

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.

1 Like

Better outcome in what regard?

Better outcome compared to the SMTP plugin made above. Or is all apples to apples still?

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.

3 Likes

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!

Thanks for explaining why a simple SMTP is not suitable for sending newsletters to a large number of email addresses.

Has anybody tried to put postalserver (GitHub - postalserver/postal: 📮 A fully featured open source mail delivery platform for incoming & outgoing e-mail · GitHub) in front of his own SMTP server? It claims to be a self-hosted alternative for Mailgun, so maybe it will behave better than a simple SMTP server, when integrated with Ghost (trying to play the role of Mailgun).

I am currently working on an OSS version and a manage service called https://mailpunch.app/. Essentially like @dashamir.hoxha mentionned mere SMTP won’t do it, so you have to use platform. Here is the OSS version: GitHub - eznix86/mailgun-proxy-for-ghost: Sending newsletters without vendor lock-in · GitHub

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/