RE:NODE

Ghost email setup: SMTP relay, Mailgun, SPF and DKIM

By RE:NODE Team
16 min read


Ghost sends email through two separate paths, and you configure them separately. One-to-one messages - member sign-in links, staff invitations, password resets - go out through the mail block of your configuration, normally over SMTP, and any SMTP relay will do. Bulk newsletters go through Mailgun, set up in Ghost Admin or in the bulkEmail block, and Ghost does not send them over SMTP at all. Whichever path carries the message, it only reaches an inbox if the sending domain publishes SPF and DKIM records that cover the service doing the sending. Most "Ghost email is broken" reports come down to one of those three things being missing.

This guide goes deeper on mail than the general Ghost hosting guide: the exact keys, a worked relay setup, the Mailgun side, the DNS records, how to test, and what the errors mean.

Two kinds of mail, two paths#

It helps to be strict about the vocabulary.

Transactional mail is triggered by one person doing one thing, and goes to that one person. In Ghost that means:

  • The magic link a member receives to sign in or confirm a subscription. Ghost members have no password, so this link is the front door.
  • Staff invitations and password resets for the admin area.
  • Notification mails to staff, such as a new member signing up, when you have them switched on.

Bulk mail is the newsletter: one post, sent to every subscriber on a list, often thousands of messages in a few minutes.

The two have different needs. Transactional mail is small in volume but time-critical - a sign-in link that arrives after ten minutes has already lost the reader - so it needs a reliable submission server and good reputation. Bulk mail is high in volume and needs a service built for fan-out: batching, per-recipient tracking, unsubscribe handling and bounce processing. That is the job Ghost hands to Mailgun.

transactionalnewsletterone messagebatched sendGhostone Node.js processSMTP relaymail blockMailgunbulkEmail or AdminMember inboxsign-in linkSubscribersnewsletter
Which path each kind of Ghost mail takes

Because the paths are independent, one can work while the other is broken. A site can send newsletters perfectly and still never deliver a sign-in link, and the reverse. Test both.

Why Ghost needs a relay for transactional mail#

If you do not configure the mail block, Ghost falls back to delivering directly from the server itself. That means connecting to each recipient's mail server on port 25 from your server's IP address. On a typical hosting network this fails twice over. Many networks block outgoing port 25 to stop spam, and even where it is open, a fresh IP with no reverse DNS and no sending history is treated with suspicion by the big mailbox providers. The mail either never leaves or lands in spam.

An SMTP relay fixes both. Ghost connects to a relay on a submission port (587 or 465), logs in, and hands over the message. The relay delivers it from an address that already has a reputation, and signs or authenticates it for your domain. SMTP relay for outgoing mail explains the general idea from the mail server's side; for Ghost it is simply "the thing the mail block points at".

Where the relay comes from is your choice:

  • A transactional email provider. Services built for application mail give you an SMTP host, a username and a key, and a dashboard that shows what was delivered and what bounced. This is the usual answer. Transactional email from your app compares what to look for.
  • The outgoing server of an existing mailbox. Works for a personal blog at low volume. Check the provider's daily sending limit, and note that some mailbox providers rewrite or restrict the From address.
  • Your own mail server. RE:NODE sells a Mail Server line running Stalwart, with SMTP, IMAP and JMAP and a web admin for domains, mailboxes and DKIM. Ghost can submit to it like any other relay. Stalwart mail server setup covers the server; self-hosted mail server guide is honest about what running one involves.
  • Mailgun's SMTP endpoint. Since you may be creating a Mailgun account for newsletters anyway, you can use its SMTP credentials for transactional mail as well. That keeps everything on one dashboard, at the cost of one vendor for everything.

Ghost only ever connects out to a submission port. It never needs port 25, and it never receives mail.

The mail block, key by key#

Ghost reads its configuration from config.production.json in the install folder. The mail block looks like this for a typical relay:

config.production.json
{  "mail": {    "from": "'Example Blog' <noreply@example.com>",    "transport": "SMTP",    "options": {      "host": "smtp.relay.example",      "port": 587,      "secure": false,      "auth": {        "user": "smtp-username",        "pass": "smtp-password"      }    }  }}
KeyExampleMeaning
mail.transportSMTPUse an SMTP server rather than direct delivery
mail.from'Name' <noreply@example.com>The default sender address
mail.options.hostsmtp.relay.exampleThe relay hostname
mail.options.port587 or 465Submission port
mail.options.securefalse or truetrue only for port 465
mail.options.auth.userloginSMTP username
mail.options.auth.passpassword or keySMTP password

A few details cause most of the trouble.

Port and `secure` go together. Port 465 uses TLS from the first byte, so it needs "secure": true. Port 587 starts in plain text and upgrades with STARTTLS, so it wants "secure": false - the upgrade still happens, the flag only describes the very first byte. Mixing them gives a timeout or a TLS handshake error, not a helpful message.

The `from` address is a promise. The relay will usually only send as addresses or domains you have verified with it, and receivers compare that address to your SPF and DKIM records. Use an address on a domain you control and have set up with the relay. noreply@ is fine for transactional mail, but a real, monitored address is kinder to a reader who replies to a sign-in email.

Quote the display name. The single quotes inside the JSON string in from are deliberate: "'Example Blog' <noreply@example.com>" keeps the name and the address together. A bare Example Blog <noreply@example.com> also works in most versions, but the quoted form is the one Ghost's own documentation shows.

Some providers have a named service. The underlying mail library accepts options.service with a well-known provider name instead of host and port, and Ghost's own documentation shows this for Mailgun. If your provider documents a service name, it saves you from typing the host. When in doubt, spell out host, port and secure, which always work.

Setting it without editing the file

On a machine you manage with Ghost-CLI, you can set a key from the shell, and Ghost-CLI writes it into the config file for you:

bash
$ ghost config mail.transport SMTP$ ghost config mail.options.host smtp.relay.example$ ghost config mail.options.port 587$ ghost restart

Ghost also reads environment variables, with a double underscore standing in for each level of nesting, so mail__transport sets mail.transport:

env
mail__transport=SMTPmail__from='Example Blog' <noreply@example.com>mail__options__host=smtp.relay.examplemail__options__port=587mail__options__secure=falsemail__options__auth__user=smtp-usernamemail__options__auth__pass=smtp-password

Environment variables win over the file. That is useful here for a specific reason: an SMTP password is a secret, and keeping it in an environment variable rather than in a file you might paste into a forum or commit to Git is a good habit. Environment variables and secrets covers the habit in general. If an edit to the file seems to do nothing, check whether an environment variable is overriding it, and remember that Ghost reads configuration only when it starts.

On a RE:NODE Ghost server, the config file is an ordinary file in the file manager and over SFTP, and the Startup tab holds environment variables. Either route works; use the one you will remember. Restart the server after any change.

Newsletters and Mailgun#

Newsletter sending is a different mechanism. Ghost takes a published post, renders it into an email, splits your subscriber list into batches, and submits those batches to the Mailgun API. Mailgun does the actual delivery to the inboxes, reports bounces and complaints back to Ghost, and Ghost uses that to mark addresses as undeliverable.

There are two ways to configure it.

In Ghost Admin. Under the settings for the newsletter email, you enter your Mailgun region, the sending domain and a Mailgun private API key. This is the normal route and what Ghost's own documentation describes for self-hosted sites.

In the configuration file. A bulkEmail block holds the same three values, and Ghost's documentation lists it as the way to set Mailgun from config:

config.production.json
{  "bulkEmail": {    "mailgun": {      "baseUrl": "https://api.mailgun.net/v3",      "apiKey": "your-mailgun-private-key",      "domain": "mg.example.com"    }  }}
KeyExampleMeaning
bulkEmail.mailgun.baseUrlhttps://api.mailgun.net/v3The Mailgun API for your region
bulkEmail.mailgun.apiKeyprivate keyAuthorises Ghost to send
bulkEmail.mailgun.domainmg.example.comThe sending domain registered at Mailgun

Mailgun has a US and an EU region, and an account lives in one of them. A domain created in the EU region needs https://api.eu.mailgun.net/v3 as the base URL; using the US address for an EU domain fails with an authorisation error that looks like a wrong key but is a wrong region. When both are set, the config file takes precedence over what is saved in the admin, which can be confusing if someone changes one and not the other.

The sending domain is usually a subdomain such as mg.example.com or news.example.com rather than your main domain. This keeps the DNS records Mailgun asks for separate from your normal mail, and means a deliverability problem with newsletters does not drag down the address you write business email from. The address newsletters appear to come from can still be on the main domain; Ghost lets you set a sender name and address per newsletter, subject to what Mailgun will allow for that domain.

Before you rely on it, send a test. Ghost's newsletter editor can send a preview to your own address, which proves the API key, the region and the domain agree. Then send a real post to a small segment, such as a label you have given to a handful of friendly members, before the first full send.

Do you actually need Mailgun?

Only if you want to email posts. A site that publishes to the web, lets members sign in and never sends a newsletter needs only the mail block. If you are not ready to set up Mailgun, you can still collect subscribers and send the newsletter later. What you cannot do is replace Mailgun with your SMTP relay for bulk sends: pointing the mail block at a relay does not enable newsletter delivery, however generous the relay's limits are.

SPF and DKIM for a Ghost domain#

Mail from your domain is checked by the receiving server against DNS records you publish. Two matter most for Ghost, and a third ties them together.

SPF is a TXT record on the domain that lists which servers may send mail for it. A receiver looks at the envelope sender, fetches that domain's SPF record, and checks the connecting IP against it.

DKIM is a cryptographic signature. The sending service signs each message with a private key; you publish the matching public key in a TXT record at selector._domainkey.yourdomain. The receiver fetches the key and verifies the signature, which proves the message was not altered and that someone with the key sent it.

DMARC says what to do when checks fail, and requires that the domain which passed SPF or DKIM matches the visible From domain. This is called alignment, and it is the reason "SPF passes" is not always enough. SPF, DKIM and DMARC explained goes through all three in order.

Because Ghost uses two services, you usually end up with records for both:

NameTypePurpose
example.comTXTSPF for the transactional relay
selector._domainkey.example.comTXTDKIM key for the relay
mg.example.comTXTSPF for Mailgun
krs._domainkey.mg.example.comTXTDKIM key for Mailgun
_dmarc.example.comTXTPolicy and report address

The exact selector names and key values come from each provider's dashboard; do not guess them. The krs above is only an example of the shape - copy what Mailgun shows you.

A minimal SPF record that authorises one relay looks like this:

TXT record on example.com
v=spf1 include:_spf.relay.example ~all

Rules worth knowing:

  1. One SPF record per name. If you already have one, merge the new include: into it. Two SPF records on the same name make the check fail with a permanent error.
  2. Ten DNS lookups at most. Each include: costs at least one, and nested includes cost more. Pile on services and you cross the limit, after which SPF fails for everything.
  3. `~all` versus `-all`. ~all means "soft fail anything not listed" and is the safe starting point. Switch to -all once you are sure every sender is listed.
  4. A separate sending domain gets its own SPF. If Mailgun sends from mg.example.com, the SPF record Mailgun asks for goes on mg.example.com, not on the root.

The DMARC record starts in monitoring mode:

TXT record on _dmarc.example.com
v=DMARC1; p=none; rua=mailto:dmarc@example.com

p=none changes nothing about delivery; it only asks receivers to send you aggregate reports so you can see who is sending as your domain and whether they pass. Read those reports for a few weeks, fix anything legitimate that fails, then tighten the policy. DMARC reports and policy rollout walks through that sequence, and DKIM keys and rotation covers key sizes and replacing a key later.

Add records in your DNS provider, wait for them to propagate, and use the verify button in the relay's and Mailgun's dashboards. Both will refuse to call a domain verified until the records resolve. DNS records explained is the primer if TXT, CNAME and TTL are new to you. Some providers ask for CNAME records instead of TXT for DKIM; that is a delegation to their key and is fine, as long as you copy the name and target exactly. Many DNS panels append your domain automatically, so a name you paste in full can end up doubled as selector._domainkey.example.com.example.com. Look at the saved record, not what you typed.

Testing and reading the failure#

Test in this order, because each step depends on the one before it.

  1. Restart Ghost so it reads the new configuration.
  2. Trigger a real transactional mail. In a private browser window, sign up as a member with an address you can read. The sign-in or confirmation link should arrive within seconds. Alternatively, invite a staff user to an address of yours.
  3. Look at the raw message headers in your mail client ("show original"). You want to see spf=pass, dkim=pass and dmarc=pass in the Authentication-Results header. A pass for SPF with a fail for DMARC usually means alignment: the relay passed SPF for its own domain, not yours, and there is no DKIM signature for yours.
  4. Send a newsletter preview and check the same headers. The Mailgun side has its own domain, so the result may differ from step 3.
  5. Send to more than one provider. A mailbox at a large provider and one at a small host disagree often enough to be worth the extra minute.

The Ghost log is the first place to look when nothing arrives. Errors from the mail layer mention the SMTP response from the relay, which is far more specific than anything shown in the browser. Read the log line before changing settings.

Common errors and what they mean#

Nothing arrives, no error in the log. Ghost is still on direct delivery or the mail block was not read. Check mail.transport is SMTP, confirm there is no environment variable overriding it, and restart.

Connection timeout. The relay host or port is wrong, or the network blocks the port. If the network blocks outgoing port 25, that is a reason to use 587 or 465 to a relay rather than anything to do with Ghost. Port 25 and outbound mail blocks explains the difference between the ports.

TLS or "wrong version number" errors. Port and secure disagree. 465 needs true; 587 needs false.

Authentication failed. The username or password is wrong, or the relay needs an application key rather than your account password. Some providers use the literal word apikey as the username and the key as the password. Read the relay's SMTP page rather than assuming.

Sender address rejected. The from address or its domain is not verified with the relay. Verify the domain there, then use an address on it.

Mail arrives but lands in spam. Records are missing or do not align. Check the headers as above. Also check that the domain does not have two SPF records and that DKIM is switched on in the relay, not only published in DNS.

Newsletter option greyed out, or a Mailgun error on send. The API key, region or domain is wrong or unset. Check the baseUrl region first; a mismatch looks like a bad key.

Newsletter sends but members report nothing. Mailgun accepted the batch; delivery then failed or was filtered. Look at Mailgun's logs for the recipient, which show whether the message was delivered, bounced or marked as spam.

Sign-in links work for some addresses and not others. Often a receiver that rejects mail on SPF or DKIM failure while others are lenient. Fix the records rather than blaming the recipient's provider.

Running Ghost on a RE:NODE app server#

Ghost is one of the one-click app lines. The server installs and builds Ghost on its first start and keeps its data in SQLite on the server, so mail is the main thing left to arrange. You edit config.production.json in the file manager or over SFTP, or set environment variables on the Startup tab, and restart from the console. One-click apps: WordPress, Ghost and Strapi describes what the installer does.

For the relay you have three honest options: a transactional provider, Mailgun's own SMTP, or the Stalwart Mail Server line if you want to run mail yourself. The Mail Server line has a documented way to deal with a network that blocks outgoing port 25: you set a relay in its web admin. You add the MX, SPF, DKIM and DMARC records yourself in either case. Receiving mail from the internet on that server needs port 25 forwarded by support, but Ghost does not need to receive anything - it only submits.

FAQ#

Can I send Ghost newsletters over SMTP instead of Mailgun?

No. Ghost's bulk email path is built on the Mailgun API. The SMTP settings carry transactional mail only, so a relay will deliver sign-in links but will not enable the option to email a post.

Which port should I use for Ghost SMTP, 465 or 587?

Either, as long as secure matches: 465 with "secure": true, or 587 with "secure": false. Use whichever your relay recommends. Avoid port 25; it is for server-to-server delivery and is commonly blocked.

Because DMARC checks alignment, not just a pass. If SPF passes for the relay's own domain and nothing is DKIM-signed for yours, the From domain does not align. Turn on DKIM for your domain in the relay and publish the key it gives you.

Do I need a separate domain for newsletters?

No, but a subdomain such as mg.example.com is the usual pattern. It keeps Mailgun's DNS records apart from your normal mail and protects your main domain's reputation if a send goes badly.

Where should the SMTP password live?

In an environment variable such as mail__options__auth__pass rather than in a file you might share. Either way it is read at start, so restart Ghost after changing it. Rotate it if it ever appears in a log or a screenshot.

Is it worth running my own mail server just for Ghost?

Usually not. Ghost sends little transactional mail, and a good relay gives you reputation and a delivery dashboard that a new server takes weeks to earn. Run your own mail if you want mailboxes you control, not to send a few sign-in links.

Was this helpful?