# How Ghost Newsletters and Automated Emails Work: A Technical Architecture Guide

> Explore Ghost's four-layer architecture for newsletters and automated emails. Learn how it manages subscriptions and delivers campaigns via configurable mail transports.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: architecture
- Published: 2026-05-18

---

**Ghost's newsletter system uses a four-layer architecture—data models, service logic, REST APIs, and an email pipeline—to create verified newsletters, manage member subscriptions, and deliver automated email campaigns through configurable mail transports.**

Ghost's newsletter functionality in the TryGhost/Ghost repository provides a full-stack solution for content creators to build and distribute email campaigns directly from their publishing platform. Understanding how Ghost newsletters and automated emails work requires examining the interaction between data persistence layers, business logic services, and the email delivery pipeline that renders and transmits each campaign to subscribers.

## The Four-Layer Architecture

Ghost implements newsletters through four distinct layers that handle everything from database schema to mail delivery.

### Data Models and Relationships

The foundation resides in [`ghost/core/core/server/models/newsletter.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/models/newsletter.js), which defines the **Newsletter** record with properties including `name`, `status`, `sender_email`, `sender_reply_to`, `schedule`, and brand colors. The system maintains a many-to-many relationship between newsletters and members through the `member_newsletters` join table, implemented in [`ghost/core/core/server/models/member-newsletter.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/models/member-newsletter.js). This junction table enables individual members to subscribe to multiple newsletters while allowing each newsletter to maintain its own subscriber list.

### Service Layer Business Logic

All domain logic lives in [`ghost/core/core/server/services/newsletters/newsletters-service.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/newsletters/newsletters-service.js). The **NewslettersService** class encapsulates creation, editing, validation, email-address verification, and bulk subscription workflows. This service acts as the single source of truth for newsletter operations, isolating business rules from API controllers and database models.

### REST API Endpoints

The API layer exposes endpoints through [`ghost/core/core/server/api/endpoints/newsletters.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/api/endpoints/newsletters.js) for administrative operations and [`ghost/core/core/server/api/endpoints/newsletters-public.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/api/endpoints/newsletters-public.js) for public access. These controllers validate requests, enforce permissions, and delegate execution to the service layer. Key endpoints include `POST /newsletters` for creation and `GET /newsletters/verify/:token` for address verification.

### Email Pipeline and Delivery

The delivery mechanism renders Handlebars templates from [`ghost/core/core/server/services/mail/templates/newsletter.html`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/mail/templates/newsletter.html), constructs MIME payloads, and transmits messages via **GhostMailer**. This pipeline integrates with Nodemailer-compatible transports including SMTP and SendGrid, handling both HTML and plain-text fallback rendering for each recipient.

## Creating a Newsletter with Verification

Creating a newsletter initiates a multi-step workflow that enforces rate limits and validates sender identities before activation.

When the Admin UI posts to `POST /newsletters`, the controller invokes `NewslettersService.add`, which performs the following operations:

1. **Enforces plan limits** via `limitService` to check against the `newsletters` resource cap
2. **Prepares attributes** using `prepAttrsForEmailVerification` to strip unverified `sender_email` or `sender_reply_to` values
3. **Persists the record** with a calculated `sort_order` for display organization
4. **Handles opt-in subscriptions** when `opt_in_existing: true` by calling `MemberModel.fetchAllSubscribed` and executing `subscribeMembersById` within the same database transaction
5. **Triggers verification emails** via `respondWithEmailVerification`, which invokes `sendEmailVerificationMagicLink` for each address requiring confirmation

```javascript
await api.newsletters.add({
    newsletters: [{
        name: 'Weekly Update',
        status: 'active',
        sender_email: 'news@myblog.com',
        sender_reply_to: 'support',
        opt_in_existing: true
    }]
});

```

## Verifying Sender Addresses via Magic Links

Ghost implements sender verification through cryptographically secure magic links to prevent email spoofing and ensure deliverability.

The `prepAttrsForEmailVerification` method checks each sender address against the **Email Address Service** (`emailAddressService.service.validate`). Addresses requiring verification are removed from the model attributes and added to an `emailsToVerify` collection.

The `sendEmailVerificationMagicLink` function generates a one-time **MagicLink** token encoding `{id, property, value}` and dispatches a verification message through `ghostMailer`. When administrators click the verification link (formatted as `/settings/newsletters/?verifyEmail=…`), the endpoint routes to `NewslettersService.verifyPropertyUpdate`, which validates the token and commits the pending property change to the database, marking it in `meta.email_verified`.

```javascript
// POST /newsletters/verify/:token
const updated = await newslettersService.verifyPropertyUpdate(token);
// `updated.meta.email_verified` now contains the verified property name

```

## Managing Member Subscriptions

Member relationships are maintained through the `member_newsletters` join table, enabling granular subscription management independent of general site membership.

When creating a newsletter with `opt_in_existing: true`, the service queries existing subscribed members via `MemberModel.fetchAllSubscribed`, then bulk-inserts associations through `newsletter.subscribeMembersById`. This operation executes within a database transaction to ensure consistency between the newsletter creation and subscription records. The join table structure allows members to selectively unsubscribe from individual newsletters while remaining subscribed to others.

## Rendering and Sending Emails

The actual email transmission combines template rendering with personalized delivery logic.

Ghost reads the Handlebars template from [`ghost/core/core/server/services/mail/templates/newsletter.html`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/mail/templates/newsletter.html), injecting variables such as `{{blog.title}}`, `{{newsletter.interval}}`, and featured post content. The rendered HTML (plus optional text fallback) passes to **GhostMailer**, which iterates through the newsletter's member associations to assemble personalized messages including unique unsubscribe links.

The mailer dispatches asynchronously through the configured transport layer, logging each transmission and outputting text versions to the console in non-production environments (as seen in [`newsletters-service.js`](https://github.com/TryGhost/Ghost/blob/main/newsletters-service.js) lines 46-48).

## Automation and Scheduling

Automated newsletter delivery relies on Ghost's generic job scheduler rather than cron-specific implementations.

Each newsletter stores an **interval** field (`daily`, `weekly`, or `monthly`) and a calculated **send_at** timestamp. The background worker process ([`core/server/services/worker/worker.js`](https://github.com/TryGhost/Ghost/blob/main/core/server/services/worker/worker.js)) polls active newsletters where `status = 'active'` and `send_at <= now`, then triggers the same rendering and mailer pipeline used for manual sends. This design allows newsletters to function as standard job types within Ghost's unified queue system.

```javascript
// Simplified scheduler logic
const newsletters = await newslettersService.getAll({status: 'active'});
for (const nl of newsletters) {
    if (nl.send_at <= Date.now()) {
        await sendNewsletter(nl); // renders template + GhostMailer loop
    }
}

```

## Security and Permissions

Access control protects newsletter operations through Ghost's permission framework.

All CRUD endpoints specify `permissions: true`, requiring authenticated admin access. The verification link route (`verifyPropertyUpdate`) specifically requires "edit" permissions on newsletters, ensuring only authorized administrators can approve sender address changes. The **Email Address Service** enforces additional domain whitelists and validation rules before permitting verification emails to transmit.

## Summary

- Ghost newsletters operate through four layers: data models ([`newsletter.js`](https://github.com/TryGhost/Ghost/blob/main/newsletter.js), [`member-newsletter.js`](https://github.com/TryGhost/Ghost/blob/main/member-newsletter.js)), service logic ([`newsletters-service.js`](https://github.com/TryGhost/Ghost/blob/main/newsletters-service.js)), REST APIs ([`newsletters.js`](https://github.com/TryGhost/Ghost/blob/main/newsletters.js)), and the email pipeline (`GhostMailer`).
- Sender verification uses magic links generated by `sendEmailVerificationMagicLink` and validated through `verifyPropertyUpdate` to ensure email deliverability.
- Member subscriptions utilize the `member_newsletters` join table, with bulk opt-in support via `fetchAllSubscribed` and `subscribeMembersById`.
- Email rendering uses Handlebars templates ([`newsletter.html`](https://github.com/TryGhost/Ghost/blob/main/newsletter.html)) processed through GhostMailer with personalized unsubscribe links for each recipient.
- Automated scheduling relies on Ghost's job worker polling `send_at` timestamps and `status` fields to trigger delivery.
- All operations enforce permissions through the `permissions: true` API framework, with specific edit rights required for address verification.

## Frequently Asked Questions

### How does Ghost verify newsletter sender email addresses?

Ghost verifies sender addresses through a magic-link system implemented in [`newsletters-service.js`](https://github.com/TryGhost/Ghost/blob/main/newsletters-service.js). When creating or updating a newsletter, the `prepAttrsForEmailVerification` method checks addresses against the Email Address Service. Unverified addresses trigger `sendEmailVerificationMagicLink`, which generates a single-use token encoding the newsletter ID and pending value. Administrators must click the verification link to invoke `verifyPropertyUpdate`, which commits the address to the database only after confirmation.

### What happens when I check "opt in existing members" when creating a newsletter?

When `opt_in_existing: true` is passed to `NewslettersService.add`, the system queries all currently subscribed members via `MemberModel.fetchAllSubscribed`, then bulk-inserts records into the `member_newsletters` join table within the same database transaction. This immediately subscribes existing members to the new newsletter without requiring them to opt-in manually.

### How does Ghost schedule automatic newsletter delivery?

Ghost stores `interval` (daily/weekly/monthly) and `send_at` fields on each newsletter record. A background job worker polls for newsletters where `status = 'active'` and the current time exceeds `send_at`. When conditions match, the worker invokes the standard email pipeline—rendering the Handlebars template and dispatching through GhostMailer—then recalculates the next send date based on the interval.

### Can members subscribe to multiple newsletters independently?

Yes. Ghost uses the `member_newsletters` join table ([`member-newsletter.js`](https://github.com/TryGhost/Ghost/blob/main/member-newsletter.js)) to maintain many-to-many relationships between members and newsletters. This architecture allows members to subscribe or unsubscribe from individual newsletters while maintaining their general site membership, with each subscription tracked as a separate record in the junction table.