How to Use Amazon SES with Ghost CMS: Transactional Email and Newsletter Setup
Quick answer: Ghost CMS uses Amazon SES for transactional email (password resets, invites) via SMTP config in config.production.json. For bulk newsletters, you need the ghost-cms-amazon-ses-adapter or a Mailgun-compatible proxy like mailgun-ses-proxy. Neither is plug-and-play, but both work reliably once set up correctly.

The Ghost forum thread says it all: 25 replies, 11 participants, and a lot of frustration. Mailgun's free tier caps at 100 emails per day, their minimum paid plan runs $35/month, and their API is the only native newsletter transport Ghost officially supports. For a self-hosted blog sending one email a month to 500 subscribers, that's a rough deal.
Amazon SES costs $0.10 per 1,000 emails. If you send 500 newsletter emails a month, that's $0.05. The economics are obvious. The setup? Not so much.
This guide covers both use cases: using SES for transactional email (the simple part) and sending newsletters via SES (which requires an adapter or proxy). You'll get working config examples and the commands to run.
What's the difference between transactional and newsletter email in Ghost?
Ghost splits outgoing email into two buckets:
| Type | Examples | Transport |
|---|---|---|
| Transactional | Password reset, staff invite, member welcome | SMTP (any provider) |
| Bulk / Newsletter | Post emails sent to subscribers | Mailgun only (natively) |
This split is why so many people hit a wall. The Mailgun alternatives post covers the broader landscape. This one focuses on SES specifically.
For transactional email, Ghost accepts any SMTP server. For bulk newsletters, its codebase calls the Mailgun API directly — unless you intercept that call with an adapter or proxy.
How do I configure Amazon SES for Ghost transactional email?
This is the straightforward path. You edit config.production.json and restart Ghost. That's it.
Prerequisites:
- AWS account with SES enabled
- A verified sending identity (your domain or email address)
- SMTP credentials generated in the SES console (IAM → SMTP credentials)
- SES out of sandbox mode if you're sending to unverified addresses
Edit your Ghost config file (usually at /var/www/yoursite/config.production.json):
{
"url": "https://yourdomain.com",
"server": {
"port": 2368,
"host": "127.0.0.1"
},
"database": {
"client": "mysql",
"connection": {
"host": "localhost",
"user": "ghost_db_user",
"password": "your_db_password",
"database": "ghost_prod"
}
},
"mail": {
"from": "noreply@yourdomain.com",
"transport": "SMTP",
"options": {
"service": "SES",
"host": "email-smtp.us-east-1.amazonaws.com",
"port": 465,
"secure": true,
"auth": {
"user": "AKIAIOSFODNN7EXAMPLE",
"pass": "your-ses-smtp-password"
}
}
},
"logging": {
"transports": ["file", "stdout"]
},
"process": "systemd",
"paths": {
"contentPath": "/var/www/yoursite/content"
}
}Replace us-east-1 with your actual SES region (e.g. eu-central-1 for Frankfurt, ap-southeast-1 for Singapore). The SMTP username is the 20-character IAM key, not your AWS Access Key ID used for API calls.
Then restart Ghost:
ghost restartTo verify it worked, go to Ghost Admin → About Ghost. Under the Mail header, it should show SMTP.
How do I use Amazon SES for Ghost newsletters (bulk email)?
This is where it gets more involved. Ghost's newsletter sender calls Mailgun's API directly. To route those calls through SES, you have two options: the SES adapter or a Mailgun API proxy.
Option 1: The ghost-cms-amazon-ses-adapter
The ghost-cms-amazon-ses-adapter sits between Ghost and SES. It's an npm package that patches Ghost's internal email sender. The upside: no separate server to run. The downside: it's a code-level patch that may break across Ghost versions.
A known issue (reported in the forum): unsubscribe links and subscription details can break when using this adapter. Check the GitHub issue #8 before going this route with newer Ghost versions.
Option 2: mailgun-ses-proxy (recommended)
The mailgun-ses-proxy runs a small local server that mimics the Mailgun API. Ghost thinks it's talking to Mailgun; the proxy forwards everything to SES.
Setup:
# Clone and install
git clone https://github.com/tilak999/mailgun-ses-proxy
cd mailgun-ses-proxy
npm install
# Create .env
cat > .env << 'EOF'
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=us-east-1
FROM_EMAIL=newsletter@yourdomain.com
PORT=3001
EOF
# Start the proxy
npm startThen configure Ghost to point at your local proxy instead of Mailgun. In Ghost Admin → Settings → Email:
- Mailgun domain:
yourdomain.com - Mailgun API key: any non-empty string (the proxy ignores it)
- Mailgun API base URL:
http://localhost:3001
Run the proxy as a persistent service with pm2 or as a systemd unit.
This approach is used by several Ghost Forum members and avoids the unsubscribe link issue of the adapter. If you're already familiar with self-hosting Ghost on Ubuntu, setting up systemd for the proxy is the same pattern.
How do I get SES out of sandbox mode?
By default, SES sandbox mode only lets you send to verified email addresses. For newsletter use, you need production access.
In the AWS Console: SES → Account dashboard → Request production access. Fill out the use case form. AWS typically approves within 24 hours if your domain is verified and the explanation is clear (e.g., "sending opt-in blog newsletter to subscribers who signed up via Ghost CMS").
Required before requesting production access:
- Verified domain with DKIM records (SES adds them automatically)
- SPF record for your sending domain
- DMARC policy in DNS
Good email deliverability setup is related to your overall Ghost site health — a trusted domain performs better in both search and inbox placement.
What are the SES SMTP regions and endpoints?
| AWS Region | SMTP Endpoint | Port |
|---|---|---|
| US East (N. Virginia) | email-smtp.us-east-1.amazonaws.com | 465 / 587 |
| US West (Oregon) | email-smtp.us-west-2.amazonaws.com | 465 / 587 |
| EU (Ireland) | email-smtp.eu-west-1.amazonaws.com | 465 / 587 |
| EU (Frankfurt) | email-smtp.eu-central-1.amazonaws.com | 465 / 587 |
| AP (Singapore) | email-smtp.ap-southeast-1.amazonaws.com | 465 / 587 |
| AP (Sydney) | email-smtp.ap-southeast-2.amazonaws.com | 465 / 587 |
Use port 465 with "secure": true, or port 587 with "secure": false and STARTTLS. Port 465 is more reliable in practice for Ghost's nodemailer-based transport.
Does Ghost natively support SendGrid or other providers for newsletters?
No. Ghost's native newsletter sending is Mailgun-only at the API level. For SMTP transactional email, any provider works: SES, SendGrid, Postmark, Brevo, or even your own mail server.
For bulk newsletter sending without Mailgun, your options are:
- The SES proxy approach described above
- Brevo (formerly Sendinblue) — also has a Mailgun-compatible API in some implementations
- A self-hosted Mailgun clone (MailHog is for testing; for production, see this forum thread)
The Ghost Mailgun alternatives guide covers these options in depth, including cost comparisons.
Why does Ghost restrict newsletter sending to Mailgun?
Ghost's team built the newsletter infrastructure around Mailgun because it handles deliverability concerns that SMTP alone doesn't — tracking opens, managing unsubscribes at the API level, handling bounces automatically, and maintaining dedicated IP reputation.
Direct SMTP sending for bulk email is riskier: your IP gets flagged faster, bounce handling is manual, and ISPs treat high-volume SMTP from unknown IPs with suspicion. That's the real reason for the restriction, not a commercial partnership.
SES partially addresses this — AWS maintains high-reputation IPs, handles bounce/complaint feedback loops if you configure SNS notifications, and scales well. But the integration work is on you.
For context, the Ghost CMS usage statistics show most Ghost installs are self-hosted, making this cost equation relevant for a large portion of the user base.
How do I test the SES transactional email setup?
After configuring and restarting Ghost, go to:
- Ghost Admin → Staff → Invite staff member (this triggers a transactional email)
- Or use the Ghost API to request a password reset for your own account
If the email arrives, your SMTP config works. If not, check:
# Ghost log
ghost log
# Or systemd journal
journalctl -u ghost_yourdomain -n 50 --no-pagerCommon errors include authentication failures (wrong SMTP credentials — remember these are different from AWS API keys), port blocked by firewall, or domain not verified in SES. The Ghost restart troubleshooting guide covers the systemd restart pattern if ghost restart itself fails.
For newsletter delivery, send a test post to yourself first before blasting your full list. Check spam folder, inspect headers for DKIM pass, and verify the unsubscribe link resolves correctly before enabling for all subscribers.
Related Ghost Guides
- Ghost CMS Mailgun Alternatives — full cost comparison of Mailgun replacements including Brevo and SendGrid
- Ghost Installation on Ubuntu — self-hosting setup from scratch
- Ghost CMS Restart Not Working — fix systemd and CLI restart failures
- Ghost CMS 500 Internal Server Error — common causes and fixes
- How to Update Ghost — keeping your install current without breaking things
- Ghost PageSpeed Score — site performance and Core Web Vitals
Frequently Asked Questions
Can Ghost CMS send newsletters through Amazon SES?
Not natively. Ghost's newsletter sender calls the Mailgun API. To use SES for newsletters, you need either the ghost-cms-amazon-ses-adapter or a Mailgun API proxy like mailgun-ses-proxy that forwards requests to SES.
What SMTP credentials do I use for Amazon SES?
Generate dedicated SMTP credentials in the AWS SES console under "SMTP settings." These are different from your AWS IAM access keys. The SMTP username is a 20-character string starting with AKIA.
Which SES port should I use — 465 or 587?
Port 465 with "secure": true is generally more reliable with Ghost's nodemailer transport. Port 587 requires STARTTLS and can have timing issues. Start with 465.
Does SES sandbox mode block Ghost newsletter sending?
Yes. Sandbox mode limits you to verified recipient addresses only. You must request SES production access before sending newsletters to general subscribers.
How much does Amazon SES cost compared to Mailgun?
SES charges $0.10 per 1,000 emails. Sending 500 newsletters/month costs $0.05. Mailgun's paid tier starts at $35/month. For small lists, SES is significantly cheaper — the difference compounds as your list grows.
Will the ghost-cms-amazon-ses-adapter break after Ghost updates?
Potentially. It patches Ghost internals, so major version updates can break compatibility. The proxy approach (mailgun-ses-proxy) is more stable since it operates externally and doesn't touch Ghost's code.
How do I handle bounce and complaint feedback with SES?
Set up Amazon SNS notifications for SES bounce and complaint events. Route them to an HTTP endpoint or Lambda function. Ghost doesn't handle SES bounce webhooks natively, so you'll need a separate script to suppress bounced addresses from your Ghost member list via the Admin API.
Can I use SES with Ghost Pro (hosted Ghost)?
No. Ghost Pro uses Mailgun for all newsletter delivery and you can't modify the email transport configuration on managed hosting. SES integration is only possible on self-hosted Ghost installs.
The bottom line
For transactional email: add the SMTP block to config.production.json, point it at your SES regional endpoint, and restart Ghost. This takes 10 minutes and works reliably.
For newsletter sending via SES: the proxy approach (mailgun-ses-proxy) is the safer bet over the adapter, especially on Ghost 5+ or 6+. Run it as a systemd service alongside Ghost. Request SES production access before you need it — approvals can take 24 hours and you don't want that delay right before a send.
If the proxy setup feels like too much, check the Mailgun alternatives guide — some providers like Brevo have their own Ghost-friendly paths that may be simpler depending on your list size.