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:
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.
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: <mailgun-smtp-password>
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:
Check logs for a clean boot with no mail-related warnings:
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.