- ghost-smtp-mailgun-setup: two-system email config (newsletter API + transactional SMTP) - firewalld-fleet-hardening: Fedora fleet firewall audit-and-harden pattern with Ansible - clamav-fleet-deployment: fleet deployment with nice/ionice throttling + quarantine - ansible-check-mode-false-positives: when: not ansible_check_mode guard for verify/assert tasks - ghost-emailanalytics-lag-warning: submitted status, lag counter, fetchMissing skip explained
4.9 KiB
title, domain, category, tags, status, created, updated
| title | domain | category | tags | status | created | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Ghost Email Configuration with Mailgun | selfhosting | services |
|
published | 2026-04-18 | 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:
docker exec <db-container> mysql -u root -p<password> 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.
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:
cd /root/<stack-dir> && docker compose up -d
Check logs for a clean boot with no mail-related warnings:
docker logs <ghost-container> 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.