Guide

Email Builder architecture

Maintainer overview of authoring CPTs, HTML conversion, and delivery integrations.

Content model

ObjectRole
prc_email_campaignMailchimp-linked campaigns
prc_email_txnMandrill bulk + dynamic system emails
prc_newsletter_listProduct taxonomy; optional audience/segment, From, accent, campaign pattern, and archive preview campaign term meta

prc_newsletter_list also attaches to block_module for archive block areas. List archives still query prc_email_campaign only (Rewrites::restrict_list_archive_to_campaigns). On a list archive, Latest_Campaign_Query uses prc_newsletter_list_preview_campaign_id when that campaign is still previewable and assigned to the list. Empty or invalid meta keeps the latest published campaign.

_post_visibility and workflow-status bind to campaigns (and workflow-status to transactional emails) so Hide on Publications Archive and pipeline stage persist through the block editor REST API. Dynamic system emails are keyed by post_name (slug); legacy prc_email_system_email_key migrates via wp prc email migrate-system-keys.

Admin DataViews lists

Campaigns and Transactional lists register on the shared prc-wp-admin-dataview shell. They keep shell Edit, View, and Trash. Transactional lists hide View because that post type is not public. Domain code adds fields, header actions, and query mapping. It does not replace the shell action array.

HTML pipeline

Block content converts through the deterministic Email_Block_Converter stack (includes/email/), with style/preset resolution, merge tags, dark-mode registry, and cached HTML. Templates live under templates/ (default, transactional, newsletter shell).

Audience builders

Mandrill recipient lists are built as async Firebase jobs. Email Builder owns the hub UI and a generic job runner (Audience_Job). Quiz Builder and Datasets register builders on prc_email_builder_register_audience_builders (init priority 5).

WordPress POSTs to an enqueue Cloud Function (HTTP 202). Firebase writes audienceBuildJobs/{jobId}, scans Auth or Realtime Database, and stores a Cloud Storage artifact. WordPress polls the ledger, then imports emails into prc_email_audience_* options. REST job views never include recipient addresses.

BuilderFormOption prefixJob id
Email domaindomain-queryprc_email_audience_auth_domain_ad_
Quiz group creatorssource-entity (quiz)prc_email_audience_quiz_group_owners_{id}_{verification}qz_
Dataset downloaderssource-entity (dataset)prc_email_audience_dataset_{id}_{verification}ds_
CSV uploadcsv-uploadprc_email_audience_csv_cs_

CSV upload is local. WordPress parses the file and writes the audience option immediately. It does not call Firebase.

Sync HTTP functions (buildDatasetAudience, buildQuizGroupOwnersAudience, buildEmailDomainAudience) stay deployed for rollback. Admin, inspector, and quiz/dataset CLIs use the enqueue path. The auth-domain CLI still calls buildEmailDomainAudience.

Delivery

PathIntegration
Campaign publish / send / draft recoveryMailchimp (includes/mailchimp/)
Bulk transactional sendMandrill (includes/mandrill/). prc_email_mandrill_message filters the payload before messages/send.
Dynamic system sendSystem_Email_Sender + form action / REST; durable recipient log table

Engagement reports for Mailchimp campaigns sync on a daily Action Scheduler schedule and on demand into the Engagement sidebar. Mandrill bulk and system emails stamp reserved message metadata (email_post_id) after prc_email_mandrill_message, then store webhook events in {prefix}prc_email_mandrill_events (salted recipient hash, no raw email). The ledger is the full-audience source. CRM still stores per-contact history and only gap-fills the report envelope when that ledger has no engagement, labelled crm_contacts.

Slack follow-ups

When Campaign_Status_Sync caches Mailchimp status as sent, two Slack replies go to the campaign's publish announcement thread (notify_in_post_thread):

  1. An immediate "campaign has been sent" notice (Campaign_Status_Sync).
  2. A first-day Mailchimp stats snapshot 24 hours later (First_Day_Campaign_Stats). The job pulls a fresh aggregate report (opens, clicks, bounces, top links), writes it to Report_Store, and posts in the same thread.

Both handlers are production-gated and idempotent per Mailchimp campaign ID. Unlink clears both Slack markers. Ops can run wp prc email first-day-stats <post_id> without waiting 24 hours.

Source: includes/mailchimp/class-campaign-status-sync.php, includes/mailchimp/class-first-day-campaign-stats.php.

Automations

Dynamic system emails store prc_email_automation_config (send window + steps). Enrollment hooks prc_email_builder_system_email_sent. A recurring dispatcher (prc_email_automation_process_due, every 15 minutes) processes due enrollments from {prefix}prc_email_automation_enrollments.

Retired Newsletter Glue path

NGL Pro and the automated NGL → prc_email_* migration engine were removed. Archive posts with _migrated_from_ngl_id remain read-only via Migration::is_migrated().

Key files

PathRole
includes/class-post-type.phpCPT registration
includes/class-rest-api.phpEditor / library / send / audience-job routes
includes/audiences/Registry, generic job runner, auth-domain adapter
includes/email/Converter and templates
includes/mailchimp/Campaign linkage, status sync, first-day Slack stats
includes/automations/Drip enrollment and scheduler
includes/ai/Subject, preview, links abilities
src/sidebar/Campaign and transactional sidebars
src/admin-dataview/Shell provider: fields, action compose, SlotFills
src/library/Shared library fields, types, and header UI

Was this helpful?