Campaign provides a foundation for building multi-channel messaging campaigns in Drupal.
It models the campaign itself — what is sent, to whom, and when — on Drupal's Entity API, and delegates actual delivery to channel-specific integrations. This module models campaigns; it does not transmit them.
Status: prototype. Everything described below is built and covered by tests, but the quality bar is a prototype's, not a production module's. There is no stable release, no delivery integration, and the entity model may still change. User experience is the next priority — the admin UI is functional rather than considered, and near-term work is going there before anything else.
Entity model
Two revisionable, fieldable content entity types, each bundleable through a config entity:
- Campaign (
campaign/campaign_type) — the container and the audience: an ordered list of messages, recipient include/exclude sets, a scheduling selection, track-opens and track-clicks flags, and a forward-only lifecycle status (draft → scheduled → sending → sent). - Campaign message (
campaign_message/campaign_message_type) — one piece of outgoing content. Each message type binds to a Channel plugin.
The core is channel-neutral. Base fields carry only what every channel shares; channel-specific fields live on the bundle, not the base schema. The shipped Email type adds subject, preview text, from name/email and body fields; the shipped SMS type adds its text body. A new channel is a plugin plus a bundle — no schema change.
Four extension points
- Channel — the contact-point type a channel addresses (
email,sms, …) and the body artifacts it expects, mapped to bundle fields. No delivery logic. Ships: Email, SMS. - Recipient — resolves to recipient value objects exposing named contact points and data fields, not bare addresses, and declares what it can supply. Ships: literal email list, single direct recipient, and an Entity Segment-backed source (hidden unless that module is installed) that can draw several segments into one include set.
- Composition — owns the authoring UI for a message body, declares its supported channels, and renders its configuration into the channel's body artifacts on save.
- Scheduling — resolves a concrete send time from a context (campaign, optional message, optional prior-step completion), shaped for per-message and relative timing from the start. Ships: immediately, and at a selected date and time.
Audiences
Campaigns compose recipient include and exclude sets: the resolver computes union(includes) − union(excludes), de-duplicated per canonicalized contact point. Validation is entity-level and happens before anything is saved — every message's channel must be addressable by the audience, and every "Sent to" personalization placeholder must be satisfiable by the declared data fields.
Message authoring
The base module ships a plain body editor for email and an SMS body editor with live GSM-7/UCS-2 character and message-part counting.
Four drag-and-drop editors ship as optional submodules, so a site installs only what it uses. Each needs a site-installed browser library that this module does not ship, and each stays hidden — with a status-report warning naming the missing files — until that library is present:
| Submodule | Editor |
|---|---|
campaign_grapesjs |
GrapesJS Newsletter and GrapesJS MJML |
campaign_mosaico |
Mosaico responsive email designer |
campaign_emailbuilder |
EmailBuilder.js block editor |
campaign_easy_email |
easy-email block editor |
All four compile to HTML in the browser at save time — there is no server-side Node or MJML dependency. Each stores its own design document alongside the rendered output, so messages stay re-editable rather than becoming one-way exports.
A fifth submodule, campaign_inline_messages, authors a campaign's messages inline on the campaign form using the Inline Entity Form widget, so messages can be created, edited and reordered without leaving the campaign.
One asset surface
Every editor shares a single asset surface owned by the base module. Images uploaded through any of them become Drupal managed files under public://campaign/images/ rather than being inlined as base64, and all go through the same service with the same limits: raster web formats only (png jpg jpeg gif webp — no SVG, a stored-XSS vector when served from the public file system), 5 MB per file, and a 4000×4000 dimension ceiling.
Uploading requires the restricted Upload campaign assets permission — being able to author a message is deliberately not enough, because uploading places files on the server. Files start temporary and are promoted to permanent only when a saved message references them, in any revision, so editing an image out of a message does not strand older revisions.
Direct sends
A single message to a single recipient is still a campaign — of the shipped Direct send type, carrying one message, one direct-recipient include item and an Immediately schedule. Two surfaces absorb the ceremony: a one-page Quick send form, and the campaign.direct_send_builder service, the canonical programmatic entry point, which validates everything before persisting anything.
Accountability
Full revision history on campaigns and messages. The lifecycle is forward-only, and once a campaign's stored status is sent it and its messages lock — access control forbids update and delete, and entity constraints reject modifying saves — so the revision history provably matches what went out.
Each message has a public permalink at /campaign/message/{slug}. The slug is stored and prefixed with the message id, and the route resolves only on an exact full-slug match: a wrong tail is a 404, never a redirect, so message URLs cannot be enumerated by incrementing an id.
Requirements
Drupal ^11.3. No hard contrib dependencies in the base module.
Optional: Entity Segment for the segment-backed recipient source; Inline Entity Form for campaign_inline_messages.
Alternatives and related modules
Campaign is early, and every module below is more mature. Several are a better answer today for a specific job.
| Module | What it does | How Campaign differs |
|---|---|---|
| Simplenews (~28,000 sites) |
Newsletter publishing end to end: User subscriber lists with confirmed opt-in for anonymous users, nodes sent as newsletter issues, multiple newsletter categories, sending driven by cron. | Simplenews owns the whole path — subscribers, issues and delivery — and is email-only. Campaign has no subscriber management and does not send; it models the campaign and treats channels and recipient sources as plugins. If you want a working email newsletter today and are okay with Users being the recipients, use Simplenews. |
| Easy Email (~10,000 sites) |
HTML email templating as a fieldable entity: token replacement across fields, overriding core and contrib transactional email, attachments, logging, ECA integration. | Easy Email is strongest on templated and transactional mail. Campaign has no transactional override and no token layer; its composition plugins are drag-and-drop editors bound to a channel. The two are closer to complementary than competing. Note the name collision: this module's campaign_easy_email submodule embeds the unrelated easy-email JavaScript editor by Zalify, not the Easy Email Drupal module. |
| Message stack (~13,000 sites) |
Message entities for activity streams and system events, with message_notify forwarding them, message_subscribe handling per-user subscriptions and message_digest aggregating them. |
Message models the record of an event that happened. Campaign models a deliberate outbound send with a composed audience and a schedule. The vocabulary overlaps; the job does not. |
| DANSE (~230 sites) |
Subscription and notification framework — events, subscriptions, notifications, audit — explicitly not handling delivery, which it routes through Push Framework to email, SMS, Slack and others. | The same delegation instinct as Campaign, with the opposite trigger: DANSE is event-driven and per-subscriber, Campaign is authored and audience-driven. Worth watching if you need notifications rather than campaigns. |
| Mass Contact (~1,200 sites) |
One-off bulk email to site users selected by role or taxonomy, with BCC batching and opt-out categories. No scheduling and no templating. | Closest in spirit to Campaign's Quick send, but scoped to site users. Campaign's recipient sources are not limited to users, and it keeps a revisionable record of what was sent. |
| Mailer Plus / Symfony Mailer (~45,000 sites) |
Mail transport and delivery: full HTML mail, attachments, embedded images, third-party delivery integrations, load balancing and async sending. | Not an alternative — a complement. Campaign deliberately does not transmit, and this is the layer it is designed to delegate to. |
The short version: for newsletters use Simplenews, for templated transactional mail use Easy Email, and for delivery use a mailer module. Campaign is for the case none of them target — one authored campaign, an audience composed from several sources, and more than one channel — and it is not ready for that in production yet.
Out of scope, for now
Delivery, the open/click tracking implementation (only the two flags are stored), A/B testing, multi-step automations, and per-recipient engagement events. Each will be its own issue, built on the contracts this module defines.
AI usage
This module was developed with lots of help from strikethroo 3.14 using Claude Fable 5 and Opus 5.
Project information
- Project categories: Content display, Content editing experience, Site structure
- Ecosystem: Member Platform
1 site reports using this module
- Created by jmlavarenne on , updated
Stable releases for this project are covered by the security advisory policy.
There are currently no supported stable releases.
Releases
Development version: 1.0.x-dev updated 18 Aug 2026 at 16:36 UTC
