A backend service that connects the Ghost publishing platform with Veil Mail for member sync, welcome emails, and newsletter delivery — with automatic PII protection and CASL/GDPR compliance out of the box.
Use this if you run a Ghost publication and want to send newsletters through Veil Mail instead of Mailgun (Ghost's default) or another provider. Veil Mail adds automatic PII scanning, CASL consent tracking, A/B testing for newsletters, and subscription topic management — features Ghost's built-in Mailgun integration doesn't offer.
Related: Veil Mail product · Node.js SDK · Mailgun migration guide
- Member Sync - Automatically sync Ghost members to Veil Mail audiences
- New Post Emails - Send email notifications when new posts are published
- Welcome Emails - Send welcome emails to new subscribers
- Paid Member Support - Option to only sync paid members
- Bun 1.1+
- Ghost instance with Admin API access
- Veil Mail account
cd integrations/ghost
bun installCreate a .env file:
# Server
PORT=3002
APP_URL=https://your-ghost-integration.com
# Config storage path (optional, defaults to ./data/configs.json)
CONFIG_PATH=/data/configs.json# Development
bun run dev
# Production
bun run build
bun run start- Go to your Ghost Admin → Settings → Integrations
- Click "+ Add custom integration"
- Name it "Veil Mail"
- Copy the Admin API Key
curl -X POST http://localhost:3002/api/sites \
-H "Content-Type: application/json" \
-d '{
"ghostUrl": "https://your-site.ghost.io",
"ghostAdminApiKey": "your-admin-api-key",
"veilApiKey": "your-veil-api-key",
"veilAudienceId": "your-audience-id",
"settings": {
"syncMembers": true,
"syncPaidOnly": false,
"sendNewPostEmails": true
}
}'The response includes webhook URLs to configure in Ghost.
- Go to Ghost Admin → Settings → Integrations → Veil Mail (your integration)
- Add webhooks:
| Event | Target URL |
|---|---|
| Member added | {APP_URL}/webhooks/member.added?siteId={id} |
| Member updated | {APP_URL}/webhooks/member.updated?siteId={id} |
| Member deleted | {APP_URL}/webhooks/member.deleted?siteId={id} |
| Post published | {APP_URL}/webhooks/post.published?siteId={id} |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/sites |
List configured sites |
| GET | /api/sites/:siteId |
Get site details |
| POST | /api/sites |
Create site configuration |
| PATCH | /api/sites/:siteId |
Update site configuration |
| DELETE | /api/sites/:siteId |
Delete site configuration |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/sites/:siteId/sync-members |
Sync all members to Veil Mail |
| POST | /api/sites/:siteId/test-webhook |
Test webhook configuration |
| Method | Endpoint | Description |
|---|---|---|
| POST | /webhooks/member.added |
Handle new member |
| POST | /webhooks/member.updated |
Handle member update |
| POST | /webhooks/member.deleted |
Handle member deletion |
| POST | /webhooks/post.published |
Handle new post |
{
// Ghost configuration
ghostUrl: string; // Your Ghost site URL
ghostAdminApiKey: string; // Ghost Admin API key
ghostContentApiKey?: string; // Ghost Content API key (optional)
// Veil Mail configuration
veilApiKey: string; // Veil Mail API key
veilAudienceId?: string; // Audience ID for member sync
// Settings
settings: {
syncMembers: boolean; // Sync members to Veil Mail
syncPaidOnly: boolean; // Only sync paid members
sendNewPostEmails: boolean; // Send emails for new posts
postEmailTemplateId?: string; // Veil Mail template for posts
welcomeEmailTemplateId?: string; // Veil Mail template for welcome
}
}FROM oven/bun:1.2-alpine
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
VOLUME /app/data
CMD ["bun", "start"]fly launch
fly secrets set APP_URL=https://your-app.fly.dev
fly volumes create data --size 1
fly deploysrc/
├── server.ts # Express server setup
├── config.ts # Configuration store
├── routes/
│ ├── webhooks.ts # Ghost webhook handlers
│ └── api.ts # Management API
└── services/
├── veil.ts # Veil Mail API client
└── ghost.ts # Ghost Admin API client
When a member is synced, the following data is sent to Veil Mail:
- Email address
- Name (split into first/last)
- Ghost member ID
- Membership status (free/paid)
- Subscription details
- Creation date
- Verify the webhook URL is accessible from the internet
- Check Ghost webhook settings
- Review Ghost error logs
- Verify API keys are correct
- Check that
syncMembersis enabled - If
syncPaidOnlyis true, verify member has paid status
- Verify
sendNewPostEmailsis enabled - Check that post is not marked as email-only
- Verify there are subscribed members
MIT - Copyright (c) 2025-present Resonia Inc