How Ghost Sends Emails and Which Email Providers Are Supported
Ghost delivers email through a nodemailer-based dual-path architecture: transactional messages (magic links, verifications) route through the GhostMailer class supporting direct, SMTP, and Mailgun transports, while bulk newsletters utilize the EmailServiceWrapper with MailgunEmailProvider as the sole provider implementation.
Ghost (TryGhost/Ghost) manages email via two specialized pipelines built on nodemailer. Understanding how Ghost sends emails and which email providers are supported ensures reliable delivery for both member authentication flows and mass newsletter campaigns.
Ghost Email Architecture Overview
Ghost separates email delivery into distinct transactional and bulk paths. Transactional emails cover one-off system messages like login magic links, member verifications, and error reports. Bulk emails handle newsletter distributions to thousands of members simultaneously. This separation allows optimized transport strategies for each use case.
Transactional Email Path
GhostMailer and Nodemailer Integration
The GhostMailer class in ghost/core/core/server/services/mail/ghost-mailer.js creates a nodemailer transport based on the mail.transport configuration setting. This class instantiates the appropriate transport driver and dispatches single-recipient messages immediately.
When initialized, GhostMailer reads config.mail.transport and constructs the transport using nodemailer’s factory methods. For Mailgun-specific transports, it automatically injects Mailgun headers (h:*) and tags (o:tag) into the message payload.
Supported Transactional Transports
Ghost supports four distinct transport modes for transactional mail:
- direct – Built-in nodemailer direct transport that attempts delivery without external relay. Used as a fallback when no provider is configured, primarily for development environments.
- SMTP – Standard SMTP relay supporting any compliant server (Postfix, Amazon SES, SendGrid). Configuration passes through
config.mail.options. - mailgun – Uses
nodemailer-mailgun-transportto forward messages via Mailgun’s HTTP API rather than SMTP. - stub – No-op transport used exclusively by the test suite in
ghost/core/core/server/services/mail/ghost-mailer.test.js.
If mail.transport is omitted from configuration, Ghost automatically falls back to the direct transport.
Bulk Email (Newsletter) Path
EmailServiceWrapper Orchestration
For newsletter distributions, Ghost employs the EmailServiceWrapper located at ghost/core/core/server/services/email-service/email-service-wrapper.js. This wrapper wires together the EmailRenderer, BatchSendingService, and an IEmailProviderService implementation to handle high-volume delivery.
The wrapper manages recipient segmentation, HTML rendering with member-specific variables, and provider delegation. Unlike the transactional path, bulk email requires a concrete provider implementation that supports batch operations and event tracking.
MailgunEmailProvider Implementation
Currently, MailgunEmailProvider in ghost/core/core/server/services/email-service/mailgun-email-provider.js is the sole implementation of the bulk email interface. This provider delegates HTTP requests to MailgunClient (ghost/core/core/server/services/lib/mailgun-client.js), which:
- Constructs Mailgun-compatible message payloads
- Enforces batch size limits (default 1000 recipients per request)
- Manages domain warming strategies
- Fetches delivery events and suppression list data
The presence of Mailgun configuration keys (mailgun_api_key, mailgun_domain) automatically enables this bulk-email path, even if the transactional transport uses SMTP.
Configuration Examples
SMTP Configuration for Transactional Mail
Configure any SMTP server in config.production.json:
{
"mail": {
"from": "info@myblog.com",
"transport": "SMTP",
"options": {
"host": "smtp.sendgrid.net",
"port": 587,
"auth": {
"user": "SG.username",
"pass": "SG.password"
}
}
}
}
Mailgun Settings for Bulk Email
Mailgun requires additional settings beyond the transport configuration:
mailgun_api_key– Your Mailgun API keymailgun_domain– Verified sending domainmailgun_base_url– Optional API endpoint override
These settings activate the MailgunEmailProvider for newsletter delivery regardless of the transactional transport setting.
Code Implementation Examples
Sending Transactional Email with GhostMailer
const GhostMailer = require('@tryghost/ghost-mailer');
(async () => {
const mailer = new GhostMailer();
await mailer.send({
subject: 'Welcome to Ghost!',
html: '<p>Hello, thanks for joining.</p>',
to: 'newmember@example.com',
from: 'noreply@example.com'
});
console.log('Transactional message queued');
})();
Dispatching Bulk Newsletters via EmailServiceWrapper
const EmailServiceWrapper = require('@tryghost/email-service-wrapper');
(async () => {
const wrapper = new EmailServiceWrapper();
wrapper.init();
const email = await wrapper.service.create({
subject: 'Monthly Update',
html: '<h1>Our news...</h1>',
newsletter_id: 1,
email_provider_id: null
});
await wrapper.service.send(email.id);
console.log('Newsletter batch scheduled');
})();
Adding Custom Tags to Bulk Emails
await wrapper.service.send(email.id, {
tags: ['campaign-spring-2025']
});
The MailgunEmailProvider sanitizes these tags (maximum 10 allowed) and prefixes them as o:tag parameters in the Mailgun payload.
Key Source Files
ghost/core/core/server/services/mail/ghost-mailer.js– Transactional mailer creating nodemailer transports and sending one-off messages.ghost/core/core/server/services/email-service/email-service-wrapper.js– Orchestrates bulk-email pipeline, wiring renderer, batch sender, and provider.ghost/core/core/server/services/email-service/mailgun-email-provider.js– Implements bulk email provider interface for Mailgun.ghost/core/core/server/services/lib/mailgun-client.js– Low-level HTTP client handling Mailgun API communication, batching, and event fetching.ghost/core/core/server/services/email-service/sending-service.js– Facade delegating to configured provider’ssendmethod.
Summary
- Ghost uses nodemailer as the foundation for all email delivery.
- Transactional emails support four transports: direct, SMTP, mailgun, and stub, managed by
GhostMailer. - Bulk newsletters currently support only Mailgun via
MailgunEmailProviderandMailgunClient. - Configuration is driven by
config.mail.transportfor transactional mail and Mailgun-specific settings (mailgun_api_key,mailgun_domain) for bulk operations. - Without explicit configuration, Ghost falls back to the direct transport for transactional mail.
Frequently Asked Questions
What email providers does Ghost support for newsletters?
Ghost supports Mailgun exclusively for newsletter delivery. The MailgunEmailProvider class in ghost/core/core/server/services/email-service/mailgun-email-provider.js is currently the only implementation of the IEmailProviderService interface, handling batch sending through the Mailgun REST API.
Can I use SMTP instead of Mailgun for bulk emails in Ghost?
No. While SMTP works for transactional emails via GhostMailer, the bulk email path requires Mailgun. The EmailServiceWrapper specifically instantiates MailgunEmailProvider, which relies on the HTTP API rather than SMTP protocols, enabling features like batch size management and delivery event tracking.
How do I configure Mailgun for both transactional and bulk emails?
Set config.mail.transport to "mailgun" for transactional messages, and ensure your Ghost settings include mailgun_api_key and mailgun_domain. These settings automatically enable the bulk email path. Alternatively, you can use SMTP for transactional mail while keeping Mailgun configured for newsletters.
What is the GhostMailer class used for?
GhostMailer handles transactional one-off emails such as member invitations, login magic links, and error notifications. Located at ghost/core/core/server/services/mail/ghost-mailer.js, it creates nodemailer transports based on configuration and sends immediate, single-recipient messages without batching logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →