Developers

Architecture Overview

Last updated July 7, 2026

This is a technical map of how Super Speedy Emails (SSE) is built — the components, what each is for, and the design decisions worth knowing about. It is aimed at developers extending or debugging the plugin (and at an AI assistant handed the codebase). End-user how-tos live in the other KB articles; this is the engineering view.

Component summary

ComponentFile(s)Purpose
Send pipelineclass-sse-campaigns.php (sse_send()), class-sse-email-log.php, class-sse-email-queue.php, class-sse-sender.phpThe spine. Everything that sends an email funnels through it: log row → queue → cron drainer → provider.
Suppression gateclass-sse-suppression-gate.php, class-sse-suppressions.phpThe single chokepoint that decides “may we send this category to this address?” Checked at enqueue and at send time.
Provider abstractionclass-sse-provider.php, class-sse-providers.php, providers/*, class-sse-webhook-dispatcher.phpPluggable sending back-ends. The sender is provider-agnostic; providers only add tracking headers + parse their webhooks.
Segment engineclass-sse-segment.phpEvaluates a JSON predicate tree to “who matches this audience,” with automatic suppression masking. Single source of truth for audiences.
Newsletterclass-sse-newsletter.phpPost-publish instant alerts + scheduled digest, with per-subscriber category prefs and frequency.
Automationsclass-sse-automations.php, class-sse-automation-scheduler.phpTriggered, time-offset email sequences (e.g. post-purchase drips). Enroll → tick → send.
Support emailsclass-sse-support-emails.php, class-sse-support-rules.php, class-sse-support-context.phpFilter-driven product-update emails: per-record product checklist, fan-out per product, per-day guard. Engine is generic; the changelog reader is a separate companion plugin (ssp-changelog-emails).
Templatesclass-sse-templates.phpEditable plain-text email templates with {{token}} substitution and two-pass rendering.
Forms & captureclass-sse-forms.php, class-sse-ajax.php, class-sse-rest.php, class-sse-shortcodes.php, class-sse-popup.php, blocks/signup-form/Signup forms (block/shortcode/popup), the REST + AJAX capture endpoints, spam controls.
Ultra AJAX fast pathmu-plugins/sse-fast-ajax.php, class-sse-mu-installer.phpA must-use plugin that handles signup submissions before the full WP stack loads — the “speed” differentiator.
Product ownershipclass-sse-product-owners.php, class-sse-bundles.phpWooCommerce order → cache of “who owns which product,” including bundle expansion.
Preference centretemplates/manage-subscriptions.php, class-sse-woocommerce.php, class-sse-unsubscribe.phpWhere subscribers self-manage: shortcode, WooCommerce My Account tab, and the tokenised /sse/u/ unsubscribe handler.
Migrationclass-sse-migrate.php, class-sse-migrate-mailpoet.php, class-sse-migrate-mailerlite.phpOne-way importers (MailPoet, MailerLite, WooCommerce backfill) via WP-CLI.
Rate limitingclass-sse-rate-limit.phpPer-IP throttle for the public capture/preference/unsubscribe endpoints.
Lifecycleclass-sse-activator.php, uninstall.php, defines.php, super-speedy-emails.phpSchema, cron registration, version upgrades, boot order, constants.

Interesting / unusual aspects (read these first)

A few design choices set SSE apart from a typical WordPress email plugin and are worth understanding before diving in:

  • One send funnel, gated twice. Nothing sends directly. Every path — campaigns, newsletter, automations, support emails, the public sse_send() API — creates a log row, enqueues it, and lets the cron drainer do the actual send. The suppression gate is checked both at enqueue and again at send time, so an unsubscribe between queueing and delivery is still honoured. This makes “did we send it, and were we allowed to?” auditable in one place (sse_email_log).
  • The queue is a real locking work-queue, not a loop. The drainer claims a batch with an atomic UPDATE ... SET locked_by (worker token + lock expiry), so multiple overlapping cron runs never double-send. Failures retry with exponential backoff (1m, 4m, 16m, 64m, 256m) up to 5 attempts. This is closer to a job runner than the “fetch rows and email them” loop most plugins use.
  • Audiences are a JSON predicate tree, not hard-coded queries. SSE_Segment evaluates a small set-algebra language (all_of / any_of / not over leaf predicates like owns_product, purchased_in_last_days, prefers_category). Each predicate returns subscriber IDs; combinators set-op them. The top-level resolver automatically subtracts the category-appropriate suppressions, so a caller physically cannot forget the opt-out gate.
  • Support emails are filter-driven, with the engine knowing nothing about changelogs. The engine asks two questions via filters — should this send? and what does it say? — and a separate bundled class answers them by reading changelogs. Swap the filter implementation and the same engine sends anything. (See the developer guide.)
  • The “Ultra AJAX” mu-plugin bypasses full WordPress. Signup submissions are intercepted by a must-use plugin that loads before the plugin/theme stack and short-circuits the two visitor-facing routes (subscribe, update-subscriptions) with a minimal WP load. That’s the speed claim, and it’s why those handlers live in a plain class (SSE_Ajax) callable from three contexts (mu-plugin, REST, admin-ajax) rather than being tied to the REST framework.
  • Separate opt-out scopes, not one “unsubscribe” flag. Suppression is an (email, scope) store with arbitrary scope strings (global, marketing, product_support, category:<id>, list:<id>, product_marketing:<id>). A marketing unsubscribe does not silence a product-update email, and vice-versa — encoded in the gate, not bolted on.
  • Identity for self-service is possessed-credential only. The preference centre identifies a subscriber from the logged-in session or their own server-issued cookie — never a typed-in email — while logged-out users act through a 128-bit tokenised link. (This was a deliberate hardening.)

Boot order & lifecycle

super-speedy-emails.php is the bootstrap. Load order matters in a few places:

  1. defines.php first — provider visibility constants (SSE_PROVIDERS_ALL/TESTED/VISIBLE_MODE) must exist before the registry boots.
  2. Core classes are require_once‘d top to bottom; the send pipeline (Phase 2) loads before providers, and class-sse-rest.php loads after the webhook dispatcher so its routes can register.
  3. Providers are registered into SSE_Providers at boot (all of them), but webhook routes are only registered for the tested + active providers (a security gate — see Provider abstraction below).
  4. Singletons init on hooks: SSE_Email_Queue::init(), SSE_Newsletter::init(), SSE_Automation_Scheduler::init(), SSE_Unsubscribe::init(), SSE_Support_Emails::init() at load; SSE_Shortcodes/Cookie/REST on init; WooCommerce integration only when class_exists('WooCommerce').

Activation (SSE_Activator::activate()) creates all tables via dbDelta, schedules cron, flushes rewrite rules, and installs the mu-plugin. maybe_upgrade() runs on admin_init and re-creates tables + reschedules cron whenever the stored sse_db_version is behind the DB_VERSION constant — so schema/cron changes ship without a manual reactivate.

Database tables (15)

sse_lists, sse_subscribers, sse_subscriber_lists, sse_forms, sse_suppressions, sse_newsletter_prefs, sse_campaigns, sse_automations, sse_automation_steps, sse_automation_enrollments, sse_email_log, sse_email_events, sse_email_queue, sse_product_owners, sse_confirmation_tokens.

Cron events (4)

HookScheduleDoes
sse_drain_email_queueevery minute (sse_every_minute, a custom schedule)drains the send queue
sse_automation_tickevery minutesends due automation steps
sse_newsletter_digest_tickhourlyfires the digest when a scheduled slot matches
sse_support_email_checkdailyoptional support-email run (off by default)

Component architecture

Send pipeline (the spine)

Three stages, deliberately decoupled:

  1. Enqueue. A caller builds an sse_email_log row (recipient, category, subject, a JSON context blob holding body/tags/unsubscribe URL, and a legal basis) and calls SSE_Email_Queue::enqueue($log_id, $priority). The public sse_send() function (in class-sse-campaigns.php) is the documented entry point for other plugins — it runs the gate, ensures a subscriber row exists for transactional categories, writes the log, and enqueues, returning the log ID or a WP_Error.
  1. Drain. SSE_Email_Queue::drain() runs every minute. It atomically claims up to 50 rows (locked_by = a random worker token, locked_until = now + 300s), ordered by priority then age, skipping rows still in backoff or past max attempts. For each claimed row it re-checks the suppression gate (state may have changed since enqueue), rebuilds send args from the log context, and calls the sender.
  1. Send + outcome. On success the log row goes to sent (webhooks later progress it to delivered/opened/etc.) and the queue row is deleted. On a WP_Error it increments attempts and reschedules with exponential backoff, or marks the log failed after 5 tries. A suppressed-at-send row is marked suppressed and dropped.

SSE_Sender::send() is the thin wrapper around wp_mail(): it applies the per-category sender identity (sse_from_name__<category> etc.) via temporary wp_mail_from* filters, injects the List-Unsubscribe header, converts plain text to simple linked HTML, and lets the active provider attach its tracking headers through phpmailer_init. Test modes (live / log_only / redirect) are enforced here — log_only skips the wp_mail() call entirely and returns a synthetic success.

Suppression gate (single chokepoint)

SSE_Suppression_Gate::check($email, $category, $context) returns {allowed, reason}. The rules are a matrix keyed on category:

  • global blocks everything (hard bounce, spam complaint, full opt-out).
  • transactional / onboarding — global only (service mail).
  • product_support — global or the dedicated product_support scope (NOT marketing).
  • marketing-class (newsletter, list_broadcast, product_marketing) — global, marketing, and their specific scope (category:<term>, list:<id>, product_marketing:<id>).

SSE_Suppressions is the backing store: a generic (email, scope, reason, source) table with add/remove/is_suppressed. Because scope is an arbitrary string, new opt-out dimensions need no schema change — product_support was added as just a new scope value + one gate branch.

Provider abstraction

SSE_Provider is the contract: identity (slug, label), header injection (inject_headers), and webhook handling (has_webhook, webhook_route, verify_webhook, parse_webhook_events). Concrete providers live in providers/ (none/wp_mail + Mailgun are tested; others are stubs).

SSE_Providers is the registry. Note two distinct sets: all() (every registered provider, used by the sender to find the active one) and webhook_providers() (the tested set plus the currently-selected provider — the only ones whose public webhook route is registered). This split is a security control: an unauthenticated webhook for a provider with stub verification would let anyone forge suppression events, so untested providers expose no endpoint.

SSE_Webhook_Dispatcher is the uniform persistence half. Every provider’s route points at it; it calls verify_webhook() (reject with 401 if it fails), then for each parsed event records it to sse_email_events, progresses the sse_email_log row, and auto-suppresses on complaint (→ marketing), hard bounce (→ global), or unsubscribe (→ the event’s scope). Adding a provider means writing only the parse half.

Segment engine

SSE_Segment::resolve($query, $category) is the audience resolver. The predicate vocabulary (documented in the file header) covers list membership, newsletter category preference, WooCommerce ownership (with bundle expansion and direct-only options), purchase recency/windows, product/subscriber/order meta-date ranges, attributes, and suppression membership — combined with all_of/any_of/not. Each leaf returns an array of subscriber IDs; combinators perform set intersection/union/difference. resolve() then applies the category-appropriate suppression mask; resolve_raw() skips it for diagnostics. This is what campaigns and the support-email engine use to answer “who?”.

Newsletter

SSE_Newsletter hooks transition_post_status for instant alerts (a post entering publish emails subscribers on the instant frequency whose category prefs match) and runs a digest on an hourly tick that fires only when the GMT day/hour matches a configured slot, gathering posts published since the last run. Per-subscriber state lives in two places: the digest_frequency column (instant|digest) and the sse_newsletter_prefs table (per category, default-subscribed when no explicit row). Bodies render through the newsletter_instant / newsletter_digest / newsletter_digest_item templates.

Automations

SSE_Automation_Scheduler implements triggered drips on the same enroll→tick→send shape:

  • Enroll — a trigger (currently wc_product_purchased, extensible via the sse_automation_triggers filter) creates an sse_automation_enrollments row, deduplicated by a context hash (e.g. order ID) so the same order can’t enrol twice.
  • Tick — the 1-minute sse_automation_tick finds enrollments whose next step is due (step offset_days/offset_hours computed from the enrol time), sends that step via sse_send(), and advances to the next step (or completes).
  • Cancelwoocommerce_order_refunded cancels enrollments tied to that order.

Steps live in sse_automation_steps (ordered, each with its own offset and subject/body).

Support emails

Covered in depth in the developer guide. Architecturally: a Support Email is a record in sse_support_rules (name + product_ids checklist + recency rules + subject/body). SSE_Support_Emails is the generic engine — plan_support_email() / run_support_email() fan out over the record’s products, and for each product resolve owners via SSE_Product_Owners, apply the per-day guard (sse_email_log.source_reference = support:<record_id>:<product_id>, “created today”), the suppression gate and the recency rules, then queue. It exposes four filters — sse_should_send_support_email, sse_support_email_subject, sse_support_email_content, sse_support_email_should_send_to_user — each receiving an SSE_Support_Context, plus the sse_support_email_sent action. The core ships no changelog code; the changelog behaviour lives in a separate companion plugin (ssp-changelog-emails) that hooks those filters, reads readme.txt + a metadata JSON from the local filesystem (never HTTP), and maps product IDs to plugin slugs via sse_changelog_product_map. The per-product AJAX Preview/Send now buttons and the optional daily cron drive the same run_support_email().

Templates

SSE_Templates is a small registry (filterable via sse_templates_registry) of plain-text templates, each with a default subject/body and a token list. Storage is per-template options (sse_tpl_<key>_subject/_body); empty falls back to the default. Rendering is two-pass: render_string() replaces only the tokens it’s given and leaves the rest intact, so one party (e.g. the changelog class) can fill product tokens and the send engine later fills per-recipient tokens like {{first_name}}.

Forms, capture & the Ultra AJAX fast path

Forms are configured in SSE_Forms (fields, style preset, lists) and rendered inline (shortcode/Gutenberg block) or as a popup (SSE_Popup, triggered by delay/scroll). Submission posts to the REST routes in class-sse-rest.php, but the handlers themselves live in SSE_Ajax as plain static methods so they can be invoked from three places: the REST callback, admin-ajax, and the fast-ajax mu-plugin.

mu-plugins/sse-fast-ajax.php (installed by SSE_MU_Installer on activation) loads as a must-use plugin — before the normal plugin/theme stack. It inspects the request path, and for the two visitor-facing routes (subscribe, update-subscriptions) it short-circuits into a minimal WP load and calls SSE_Ajax directly, skipping the overhead of the full REST framework and the rest of the plugin/theme boot. Admin and other routes fall through to normal WordPress. This is the “near-instant signup” claim; it’s toggleable via the sse_options['sse_mu_enabled'] flag.

Spam controls: a honeypot field, the per-IP SSE_Rate_Limit, nonce verification, and email validation. Double opt-in is handled by SSE_Confirmations (256-bit single-use tokens in sse_confirmation_tokens, TTL filterable).

Product ownership

SSE_Product_Owners listens for WooCommerce orders reaching a paid status and upserts sse_product_owners rows: a direct=1 row for each purchased product, plus direct=0 rows for every component of a bundle (expanded via SSE_Bundles). subscriber_ids_owning($product_id, $include_bundles) is the lookup the support-email and segment engines use. Variations collapse to the parent product (WooCommerce reports the parent ID for line items), so a plugin sold in several licence tiers is one product for ownership purposes. A nightly rebuild keeps the cache honest.

Preference centre & unsubscribe

Three surfaces share templates/manage-subscriptions.php: the

shortcode, the WooCommerce “Email Preferences” My Account tab (SSE_WooCommerce, gated on a setting), and — separately — the /sse/u/ tokenised unsubscribe handler (SSE_Unsubscribe). The first two require a possessed identity (session or own cookie) and create a subscriber record on first save for a logged-in non-subscriber; the third needs only the 128-bit token and is the logged-out path. The handler honours RFC 8058 one-click POST and maps the link’s s=<scope> to a suppression scope.

Migration

SSE_Migrate is shared infrastructure (apply_subscriber() upserts by email, maps source status → SSE action, applies suppressions, attaches list memberships). SSE_Migrate_MailPoet and SSE_Migrate_MailerLite are source-specific importers driven by wp sse migrate <source> (dry-run by default, --commit to write). Idempotent — re-running matches by email and produces no duplicates.

Extension points (filters & actions)

The public hooks, all documented in the Developer Reference: Filters & Actions: sse_send() (function); sse_should_send_support_email, sse_support_email_subject, sse_support_email_content, sse_support_email_should_send_to_user, sse_support_email_sent; sse_changelog_product_map, sse_support_assets_dir (both companion-plugin); sse_templates_registry; sse_confirmation_ttl_days; sse_automation_triggers; sse_rate_limit_enabled.

Related

Leave a Reply

Your email address will not be published. Required fields are marked *

×
1/1