PRC Email Builder
Native WordPress email authoring and Mailchimp/Mandrill delivery for PRC Platform.
by Seth Rubenstein · github.com/pewresearch/prc-email-builder · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/pewresearch/prc-email-builder/archive/refs/heads/trunk.zipReadme
PRC Email Builder
Canonical docs: docs/plugins/prc-email-builder/
Native WordPress email authoring and Mailchimp/Mandrill delivery for PRC Platform.
What it does
- Registers two custom post types —
prc_email_campaign(Mailchimp campaigns) andprc_email_txn(Mandrill bulk + dynamic system emails) — plus aprc_newsletter_listtaxonomy for organizing email products (The Briefing, Notifications, etc.) - Binds the
_post_visibilitytaxonomy toprc_email_campaignso Hide on Publications Archive persists through the block editor REST API (the campaign CPT registers afterprc-publication-listingwires visibility support to participating post types) - Provides editor panels for subject line, preview text, and newsletter list assignment (see Editor sidebars)
- Converts block content to email-safe HTML via the deterministic
Email_Block_Converterpipeline - Creates and sends a Mailchimp campaign automatically on publish (or when a scheduled campaign goes live) when Automatically send on publish is enabled
- Registers the "The Briefing" block pattern as a starting-point template
- Supports a
dynamicdelivery channel: a newsletter is published as a reusable, per-recipient template sent on demand (e.g. from aprc-block/formvia thesendSystemEmailaction), with block-bits resolving merge fields per send - Settings page under Newsletters → Settings for Mailchimp API key, From Name, From Email, and Automatically send on publish
- Email Library admin page (
Newsletters → Library) — DataViews listing of campaigns and transactional emails with filters for type, newsletter list, combined Mailchimp/Mandrill send status, and sortable open/click-rate columns for sent campaigns - Engagement reporting for Mailchimp campaigns — daily Action Scheduler sync plus on-demand refresh; sidebar Engagement panel on campaign posts shows opens, clicks, bounces, unsubscribes, and click-by-URL breakdown
- Slack first-day stats — 24 hours after Mailchimp marks a campaign sent, a threaded Slack reply posts sent/open/click/bounce totals and top links (same publish announcement as the sent notice)
- Scheduled automations for dynamic system emails — configure follow-up (drip) steps that send X calendar days after the initial email in a fixed daily send window (see Scheduled automations)
- System email audience tooling — durable per-recipient send log plus WP-CLI builders for Mandrill activity exports and log-derived bulk audiences (see System email audiences)
Newsletter Glue (retired)
The vendored Newsletter Glue Pro plugin and the automated NGL → prc_email_* migration engine (Action Scheduler jobs, WP-CLI importers, and editor hooks that assumed NGL was installed) were removed from the monorepo. New sites should author campaigns and transactional emails only through this plugin.
Archival migrated posts still carry _migrated_from_ngl_id post meta. PRC\Platform\Email_Builder\Migration::is_migrated( $post_id ) is the single guard used across send, Mailchimp sync, engagement reporting, and editor sidebar enqueue — those posts are treated as read-only archives (no Mailchimp send, no Engagement panel). Historical release notes in docs/release-notes/1.7/ describe the original migration; they are not a runbook for new environments.
Editor sidebars
Email posts split producer controls across the document panel (always visible) and a pinned plugin sidebar (toolbar send icon):
| Post type | Document panel ("Email Settings") | Plugin sidebar |
|---|---|---|
prc_email_campaign |
Subject, preview text, newsletter list picker | Campaign Setup — Mailchimp audience/segment/template overrides, draft recovery, and send status |
prc_email_txn |
Subject and preview text only | Transactional Setup — delivery type, Mandrill recipient list or dynamic system email slug, automations, and send actions |
Campaign Setup (src/sidebar/send/) is available before and after publish. When a prc_newsletter_list term is assigned, audience/segment fields in Campaign Setup show "Locked by newsletter list" (values copied from term meta on save).
Transactional Setup hosts:
- Dispatch Info (collapsible) — transactional type (
mandrillbulk vsdynamicper-recipient), Mandrill recipient list picker, or dynamic system email slug - Automations (collapsible, dynamic only) — follow-up step configuration (moved from the document panel)
- Send / draft actions and delivery status notices
Dynamic system emails are identified by their post slug (post_name), not a separate meta field. The slug control in Transactional Setup is the lookup key forms and other plugins use when sending (e.g. typology-loyal-liberals). Legacy prc_email_system_email_key meta is migrated with wp prc email migrate-system-keys.
Publish flow
- Author creates a
prc_email_campaignpost and sets subject, preview text, and (optionally) newsletter list in the document panel - (Optional) Assign a
prc_newsletter_listterm — when present, audience and segment post meta are overwritten from the term's Mailchimp settings on save (see Newsletter lists). Advanced audience/segment/template overrides live in Campaign Setup. - (Optional) Open Preview in the Email Content document panel to verify rendered HTML
- Author publishes or schedules the campaign in WordPress — when Automatically send on publish is enabled (the default) and the post reaches
publish, email HTML is rendered synchronously, a Mailchimp campaign is created for the configured audience/segment, and Mailchimp sends it immediately. When the setting is off, publish only saves the WordPress post; use Send to Mailchimp in Campaign Setup to deliver. - Use WordPress scheduling (
future→publish) to control when the send happens; authors do not finalize the send in the Mailchimp UI
Recovery and draft updates
If auto-dispatch fails, or Automatically send on publish is off, Send to Mailchimp in Campaign Setup calls
POST /prc-email-builder/v1/campaigns/create-draft (create-and-send; route kept for
compatibility). When a campaign was created but send failed (status save), the
same control retries send only. Recovery send still works when auto-send is off.
While status is still save, Update Mailchimp draft can push revised content
via POST /prc-email-builder/v1/campaigns/update-draft with { "post_id": <id> }.
Constraints (enforced server-side):
- A Mailchimp campaign must already exist (
prc_email_mailchimp_campaign_idmeta) - Mailchimp campaign status must be
save(draft) — sent/scheduled campaigns return409 - Stored audience and segment must still match the linked Mailchimp campaign
- Post must have renderable email HTML
Unlinking and recovering a deleted Mailchimp draft
When the linked Mailchimp campaign is missing (deleted in Mailchimp or never created after publish), Campaign Setup shows a warning and offers Unlink from Mailchimp. Unlinking clears WordPress linkage meta (prc_email_mailchimp_campaign_id, admin URL, status, and any stored engagement report) but does not delete the remote Mailchimp campaign.
After unlink, Create Mailchimp draft appears for published, non-migrated campaigns. This calls POST /prc-email-builder/v1/campaigns/create-draft with { "post_id": <id> } and mints a fresh Mailchimp draft from the current email HTML and audience settings.
| Action | REST route | Notes |
|---|---|---|
| Unlink | POST /prc-email-builder/v1/campaigns/unlink |
Idempotent; strong confirm when Mailchimp status is sent, schedule, or sending |
| Create draft | POST /prc-email-builder/v1/campaigns/create-draft |
Requires publish status; returns 409 when still linked or when Migration::is_migrated() |
Migrated NGL archive posts (Migration::is_migrated()) cannot create a new Mailchimp draft from this panel.
Email Library
Newsletters → Library renders a @wordpress/dataviews table backed by
GET /prc-email-builder/v1/library. Filters:
| Query param | Values | Notes |
|---|---|---|
post_type |
all, campaign, txn |
Campaign vs transactional emails |
newsletter_list |
comma-separated term slugs | prc_newsletter_list taxonomy |
mailchimp_status |
campaign:<status> values |
e.g. campaign:save, campaign:sent |
mandrill_status |
txn:<status> values |
Mandrill send status for transactional posts |
search, orderby, order, page, per_page |
standard | Pagination via X-WP-Total headers |
Send-status filter labels are merged from Mailchimp and Mandrill option sets in
Send_Status::library_filter_options().
Newsletter lists
prc_newsletter_list taxonomy terms can store default Mailchimp targeting:
| Term meta key | Purpose |
|---|---|
prc_newsletter_list_audience_id |
Default Mailchimp audience for campaigns on this list |
prc_newsletter_list_segment_id |
Default saved segment within that audience |
When a campaign is saved with one or more list terms assigned,
Newsletter_List::override_campaign_audience_from_list() (priority 9 on
rest_after_insert_prc_email_campaign):
- Keeps only the first assigned term if multiple are set
- Overwrites
prc_email_mailchimp_audience_idandprc_email_mailchimp_segment_idpost meta from the term
In the editor, audience/segment fields in Campaign Setup show "Locked by newsletter list" when
a list term drives the values. Term admin UI (src/term-admin/) loads segments
dynamically when the audience changes.
System email audiences
Bulk transactional sends (prc_email_txn + prc_email_delivery_mode = mandrill) target a recipient list stored in wp_options as prc_email_audience_{key} with companion {key}_meta (label, count, built_at, source). The Transactional Setup sidebar lists available audiences built via WP-CLI or the Emails → Transactional Build audience wizard.
On Emails → Transactional, Build audience opens a hub of stored lists plus a builder picker (email domain, CSV upload, quiz group creators, dataset downloaders). CSV upload writes the list immediately from the file. Domain, quiz, and dataset builds can keep running after the dialog closes. The list appears in the catalog and recipient picker when the scan finishes. WordPress polls the job without exposing recipient addresses through REST. Operators can still run wp prc email audience build-from-auth-domain for domain matching, and quiz/dataset CLIs for those builders.
The Firebase functions are an operational dependency. Operators must deploy the enqueue HTTP functions and the RTDB worker with firebase/bin/deploy-audience-functions.sh. Installing or deploying this plugin does not deploy Cloud Functions.
Every dynamic system email send is also logged durably in {prefix}prc_email_system_email_recipients (system_email_key, post_id, email, first/last sent timestamps, send count). The key is the newsletter post slug.
WP-CLI
# List / create / delete test audiences
wp prc email audience list
wp prc email audience create --emails=you@example.com,qa@example.com --key=qa --label="QA test"
wp prc email audience delete --key=qa --yes
# Build from Mandrill activity export (API or local CSV)
wp prc email audience build-from-mandrill \
--key=typology-2026-requesters \
--date-from=2026-05-01 \
--label="Typology system-email requesters" \
--dry-run
# Build from the durable send log (exact key, comma-separated, or prefix wildcard)
wp prc email audience build-from-log \
--system-email-key=typology-2026-* \
--key=typology-2026-requesters \
--dry-run
# Build from Firebase Auth email domains (substring of the domain only, not the local-part).
# Calls buildEmailDomainAudience. Default verification is verified. Does not create a
# draft post unless --create-post. Deploy the CF first:
# cd firebase && ./bin/deploy-audience-functions.sh staging /path/to/sa.json
wp prc email audience build-from-auth-domain --domain-contains=k12 --dry-run
wp prc email audience build-from-auth-domain \
--domain-contains=k12 \
--label="K-12 school domains (verified)"
wp prc email audience build-from-auth-domain --domain-contains=k12 --create-post