Sending newsletters without vendor lock-in
  • TypeScript 56%
  • PHP 40.9%
  • CSS 1%
  • Dockerfile 0.8%
  • JavaScript 0.7%
  • Other 0.6%
Find a file
2026-07-23 09:54:14 +04:00
.github/workflows chore: add related stuff 2026-05-17 21:32:24 +04:00
app feat: add logged no-op stubs for mailgun suppression-removal endpoints (#31) 2026-07-23 09:54:14 +04:00
bootstrap feat: initial commit 2026-04-29 18:30:47 +04:00
config chore: packages and routes 2026-05-01 00:14:35 +04:00
database feat: initial commit 2026-04-29 18:30:47 +04:00
docker/fpm chore: add related stuff 2026-05-17 21:32:24 +04:00
public feat: initial commit 2026-04-29 18:30:47 +04:00
resources refactor: refactor front 2026-05-01 00:10:22 +04:00
routes feat: add logged no-op stubs for mailgun suppression-removal endpoints (#31) 2026-07-23 09:54:14 +04:00
storage feat: initial commit 2026-04-29 18:30:47 +04:00
tests feat: add logged no-op stubs for mailgun suppression-removal endpoints (#31) 2026-07-23 09:54:14 +04:00
.dockerignore chore: add related stuff 2026-05-17 21:32:24 +04:00
.editorconfig feat: initial commit 2026-04-29 18:30:47 +04:00
.env.example feat: initial commit 2026-04-29 18:30:47 +04:00
.gitattributes feat: initial commit 2026-04-29 18:30:47 +04:00
.gitignore feat: initial commit 2026-04-29 18:30:47 +04:00
.lerd.yaml feat: initial commit 2026-04-29 18:30:47 +04:00
.node-version feat: initial commit 2026-04-29 18:30:47 +04:00
.npmrc feat: initial commit 2026-04-29 18:30:47 +04:00
.prettierignore feat: initial commit 2026-04-29 18:30:47 +04:00
.prettierrc feat: initial commit 2026-04-29 18:30:47 +04:00
artisan feat: initial commit 2026-04-29 18:30:47 +04:00
bun.lock chore(deps): update dependency @rollup/rollup-win32-x64-msvc to v4.60.4 (#8) 2026-05-17 23:28:20 +04:00
components.json chore: packages and routes 2026-05-01 00:14:35 +04:00
composer.json chore: add license 2026-07-22 11:46:07 +04:00
composer.lock chore(deps): update dependency laravel/sail to v1.59.0 (#12) 2026-05-17 23:27:32 +04:00
docker-compose.yml chore: add docker compose setups 2026-05-17 21:35:22 +04:00
Dockerfile chore: add related stuff 2026-05-17 21:32:24 +04:00
eslint.config.js feat: initial commit 2026-04-29 18:30:47 +04:00
LICENSE chore: add license 2026-07-22 11:46:07 +04:00
package.json chore(deps): update dependency @rollup/rollup-win32-x64-msvc to v4.60.4 (#8) 2026-05-17 23:28:20 +04:00
phpstan.neon feat: initial commit 2026-04-29 18:30:47 +04:00
phpunit.xml feat: initial commit 2026-04-29 18:30:47 +04:00
pint.json feat: initial commit 2026-04-29 18:30:47 +04:00
README.md chore: add license 2026-07-22 11:46:07 +04:00
rector.php feat: initial commit 2026-04-29 18:30:47 +04:00
renovate.json chore: add renovate.json (#1) 2026-04-29 21:19:52 +04:00
tsconfig.json feat: initial commit 2026-04-29 18:30:47 +04:00
vite.config.ts feat: initial commit 2026-04-29 18:30:47 +04:00

Mailgun Proxy for Ghost

This is Mailgun-compatible proxy built specifically for Ghost newsletters. Ghost sends newsletter mail through the proxy as if it were Mailgun; the proxy sends through Laravel and exposes Mailgun-shaped events back to Ghost analytics.

Supported Providers

  • SMTP
  • Sendmail
  • Postmark
  • Amazon SES
  • Resend
  • Mailbox (faking a mailbox without sending it for real, great for local testing)
  • And more if you install other packages by the community

What it does

  • Accepts Ghost's Mailgun POST /v3/{domain}/messages requests.
  • Sends each recipient through Laravel's configured mailer.
  • Records per-recipient deliveries and webhook events.
  • Accepts Resend webhooks at POST /api/webhook/resend.
  • Exposes Ghost-compatible Mailgun events at GET /v3/{domain}/events.

Laravel environment

Add these values to the proxy .env:

APP_URL=https://newsletter-proxy.domain.tld

MAIL_MAILER=resend
OUTBOX_PROVIDER=resend

MAILGUN_API_KEY=change-this-shared-api-key
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Notes:

  • MAILGUN_API_KEY is the shared key Ghost uses as its Mailgun API key.
  • RESEND_API_KEY is used by Laravel's Resend mailer.
  • RESEND_WEBHOOK_SECRET is the Svix/Resend webhook signing secret.
  • Set the Resend webhook URL to:
https://newsletter-proxy.domain.tld/api/webhook/resend

Enable Resend domain tracking for opens and clicks:

https://resend.com/docs/dashboard/domains/tracking

Ghost configuration

In Ghost's config file, point bulk email Mailgun settings to this proxy:

{
  "bulkEmail": {
    "mailgun": {
      "baseUrl": "https://newsletter-proxy.domain.tld/",
      "apiKey": "change-this-shared-api-key",
      "domain": "domain.tld"
    }
  }
}

Notes:

  • baseUrl must be the proxy URL and should include the trailing slash.
  • apiKey must match MAILGUN_API_KEY in the proxy .env.
  • domain should be the Ghost newsletter sending domain.

Docker Compose setup

cp .env.example .env
# Edit .env with your configuration

docker compose up -d --build
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate

The app will be available at http://localhost:8080.

Local setup

composer install
bun install
cp .env.example .env
php artisan key:generate
php artisan migrate

Run the app and queue worker:

composer run dev
php artisan queue:work

Ghost analytics flow

Ghost polls Mailgun events from this proxy. The proxy returns stored delivery events in the shape Ghost expects, including:

  • delivered
  • opened
  • clicked
  • failed
  • rejected
  • complained

email.sent webhooks from Resend are ignored because Laravel's MessageSent event records the initial accepted state.

Dashboard

The proxy includes a Laravel/Inertia dashboard for inspecting newsletter requests, attempts, deliveries, webhook events, and retrying failed requests.

/dashboard

License

Copyright (C) 2026 Bruno Bernard

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

Because the AGPL-3.0 covers network use, anyone who runs a modified version of this proxy as a network service must make the corresponding source available to its users.