--- title: Ghost Email Configuration with Mailgun domain: selfhosting category: services tags: - ghost - mailgun - smtp - email - docker - newsletter status: published created: 2026-04-18 updated: 2026-04-18T11:13 --- # Ghost Email Configuration with Mailgun ## Overview Ghost uses **two separate mail systems** that must be configured independently. This is the most common source of confusion in Ghost email setup — configuring one does not configure the other. | System | Purpose | Where configured | |--------|---------|-----------------| | **Newsletter / Member email** | Sending posts to subscribers | Ghost Admin UI → Settings → Email (stored in DB) | | **Transactional / Staff email** | Magic links, password resets, admin notifications | `docker-compose.yml` environment variables | Both should route through Mailgun for consistent deliverability and tracking. ## Prerequisites - A Mailgun account with a verified sending domain - DNS access for your sending domain - Ghost running in Docker (this guide assumes Docker Compose) ## Step 1 — DNS Records Add these records to your sending domain before configuring Ghost. Mailgun will verify them before allowing sends. | Type | Name | Value | |------|------|-------| | TXT | `@` | `v=spf1 include:mailgun.org ~all` | | TXT | `pdk1._domainkey` | *(provided by Mailgun — long DKIM key)* | | CNAME | `email` | `mailgun.org` | The tracking CNAME (`email.yourdomain.com`) enables Mailgun's open/click tracking. Ghost's EmailAnalytics feature requires it. After adding records, verify in Mailgun → Sending → Domains → your domain → DNS Records. All records should show green. ## Step 2 — Newsletter Email (Mailgun API) Configure in **Ghost Admin → Settings → Email newsletter**. Ghost stores these settings in its database `settings` table — not in the compose file. | Setting | Value | |---------|-------| | Mailgun region | US (api.mailgun.net) or EU (api.eu.mailgun.net) | | Mailgun domain | `yourdomain.com` | | Mailgun API key | Private API key from Mailgun dashboard | Ghost uses the Mailgun API (not SMTP) for newsletter delivery. This enables open tracking, click tracking, and the EmailAnalytics dashboard. > **Verify via DB:** If Ghost is MySQL-backed, you can confirm the settings landed: > ```bash > docker exec mysql -u root -p ghost \ > -e "SELECT key_name, value FROM settings WHERE key_name LIKE 'mailgun%';" > ``` ## Step 3 — Transactional Email (SMTP via Mailgun) Configure in `docker-compose.yml` as environment variables. Ghost's default transport (`Direct`) attempts raw SMTP delivery, which is blocked by most hosting providers and treated as spam. Mailgun SMTP is the reliable path. ```yaml services: ghost: image: ghost:6-alpine environment: # ... other Ghost config ... mail__transport: SMTP mail__from: noreply@yourdomain.com mail__options__host: smtp.mailgun.org mail__options__port: 587 mail__options__auth__user: postmaster@yourdomain.com mail__options__auth__pass: ``` The SMTP password is separate from the API key. Find it in Mailgun → Sending → Domains → your domain → SMTP credentials → `postmaster@yourdomain.com`. After updating the compose file, restart Ghost: ```bash cd /root/ && docker compose up -d ``` Check logs for a clean boot with no mail-related warnings: ```bash docker logs 2>&1 | grep -i mail ``` ## Verifying the Full Stack **Newsletter:** Send a test post to members (even with 1 subscriber). Check Ghost Admin → Posts → sent post → Email analytics. Delivered count should increment within minutes. **Transactional:** Trigger a staff magic link (Ghost Admin → sign out → request magic link). The email should arrive within seconds. **Mailgun logs:** Mailgun → Logs → Events shows all API and SMTP activity. Filter by domain to isolate Ghost sends. ## Common Issues **Newsletter sends but staff emails don't arrive (or vice versa):** The two systems are independent. Check both configurations separately. **`transport: Direct` in config:** Ghost writes a `config.production.json` inside the container. If `mail.transport` shows `Direct`, the environment variables didn't apply — verify the compose key names (double underscores for nested config). **Mailgun API key vs SMTP password:** These are different credentials. The API key (starts with `key-`) is for the newsletter system. The SMTP password is for the transactional system. Don't mix them. **Domain state: `unverified` in Mailgun:** DNS records haven't propagated or are wrong. Use `dig TXT yourdomain.com` and `dig TXT pdk1._domainkey.yourdomain.com` to verify from outside your network. ## See Also - [ghost-emailanalytics-lag-warning](../../05-troubleshooting/ghost-emailanalytics-lag-warning.md) - [docker-healthchecks](../docker/docker-healthchecks.md) - [watchtower-smtp-localhost-relay](../docker/watchtower-smtp-localhost-relay.md)