Developers

Developer Reference

Last updated July 7, 2026

Technical reference for developers extending or integrating with Super Speedy Emails.

Last updated for DB version 2.5.0 / plugin version 1.05. If get_option( 'sse_db_version' ) reports a higher number, this doc may be behind.


Database Tables

All tables use the WordPress prefix (e.g. wp_sse_lists). Schema changes go through SSE_Activator::create_tables() via dbDelta.

Phase 1 tables (signup / forms / subscription management)

sse_lists

ColumnTypeDescription
idbigint(20) unsignedPrimary key
namevarchar(255)List name
slugvarchar(255)URL-friendly slug (unique)
descriptiontextOptional description
statusvarchar(20)active or archived
settingslongtextJSON for misc settings
requires_confirmationtinyint(1)Double opt-in flag (default 1) — Phase 2B
is_newslettertinyint(1)1 = this list is the article-newsletter audience (single-select)
categoryvarchar(40)Email category this list maps to (list_broadcast default) — Phase 2C
created_at / updated_atdatetime

sse_subscribers

ColumnTypeDescription
idbigint(20) unsignedPrimary key
emailvarchar(255)Email address (unique)
first_name / last_namevarchar(255)
wp_user_idbigint(20) unsignedLinked WordPress user ID (nullable)
cookie_idvarchar(64)Guest identification cookie value
ip_addressvarchar(45)IP at time of subscription
statusvarchar(20)pending / active / bounced / complained / suppressed — Phase 2A
confirmed_atdatetimeDouble opt-in confirmation timestamp — Phase 2B
marketing_opted_out_atdatetimeWhen marketing-opt-out happened
marketing_opt_out_reasonvarchar(64)user_request / complaint / bounce / migrated_unsubscribed
digest_frequencyvarchar(20)instant / digest — Phase 2C
unsubscribe_tokenvarchar(64)Long random token for one-click unsubscribe URLs
sourcevarchar(64)form / migration_mailpoet / migration_mailerlite / wc_purchase / admin
created_at / updated_atdatetime

sse_subscriber_lists

ColumnTypeDescription
idbigint(20) unsignedPrimary key
subscriber_idbigint(20) unsignedFK to sse_subscribers
list_idbigint(20) unsignedFK to sse_lists
statusvarchar(20)subscribed, unsubscribed, or pending
subscribed_atdatetime
unsubscribed_atdatetimenullable

Unique on (subscriber_id, list_id).

sse_forms

ColumnTypeDescription
idbigint(20) unsignedPrimary key
namevarchar(255)Internal form name
list_idslongtextJSON array of list IDs
fieldslongtextJSON: {"first_name": true, "last_name": false}
stylelongtextJSON: preset, colors, border radius, button text
popup_settingslongtextJSON popup config (null if not a popup)
success_messagetext
statusvarchar(20)active or inactive
created_at / updated_atdatetime

Phase 2 tables (sending pipeline)

sse_suppressions

The authoritative do-not-send store. Scopes: global, marketing, category:<term_id>, list:<list_id>, product_marketing:<product_id>.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
emailvarchar(255)Normalized lowercase
scopevarchar(80)See above
reasonvarchar(64)user_unsubscribe / complaint / bounce_hard / bounce_soft_repeated / manual / migrated
sourcevarchar(64)Where the suppression came from
created_atdatetime

Unique on (email, scope).

sse_newsletter_prefs

Per-subscriber, per-category preferences. Missing rows mean defaulted-in.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
subscriber_idbigint(20) unsignedFK to sse_subscribers
term_idbigint(20) unsignedWP term_id of a top-level post category
subscribedtinyint(1)1 = wants this category
updated_atdatetime

Unique on (subscriber_id, term_id).

sse_campaigns

ColumnTypeDescription
idbigint(20) unsignedPrimary key
namevarchar(255)Internal name
categoryvarchar(40)See Email Categories
typevarchar(20)broadcast / automation_step / digest
subjectvarchar(255)
from_name / from_email / reply_tovarchar(255)Optional per-campaign overrides
body_textlongtextPlain-text body (with merge tags)
body_htmllongtextHTML body (optional; auto-generated when omitted)
audience_querylongtextJSON segment query — see Segments
audience_snapshotlongtextFrozen recipient list at sending transition
automation_idbigint(20) unsignedFK to sse_automations (null for broadcasts)
schedule_atdatetimenullable
statusvarchar(20)draft / scheduled / sending / sent / cancelled
sent_atdatetime
recipient_countint unsignedFrozen count from snapshot
created_at / updated_atdatetime

sse_automations

ColumnTypeDescription
idbigint(20) unsignedPrimary key
namevarchar(255)
trigger_typevarchar(40)wc_product_purchased / manual / custom (via filter)
trigger_configlongtextJSON, e.g. { "product_ids": [...], "match": "any" }
categoryvarchar(40)Email category for all steps
statusvarchar(20)active / paused
created_at / updated_atdatetime

sse_automation_steps

Ordered steps for an automation. Cumulative offsets from enrolment timestamp.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
automation_idbigint(20) unsignedFK
positionint unsignedSort order (0-indexed)
offset_days / offset_hoursintCumulative offset from enrollment_created_at
subjectvarchar(255)
body_textlongtext
body_htmllongtext
created_at / updated_atdatetime

Index on (automation_id, position).

sse_automation_enrollments

ColumnTypeDescription
idbigint(20) unsignedPrimary key
automation_idbigint(20) unsignedFK
subscriber_idbigint(20) unsignedFK
contextlongtextJSON, e.g. { "order_id": 123, "product_id": 456 }
context_hashvarchar(32)md5 of dedup-relevant context (e.g. order_id)
current_stepint unsignedPosition of the next step to send
next_send_atdatetimeWhen the current step is due
statusvarchar(20)active / complete / cancelled
created_at / updated_atdatetime

Unique on (automation_id, subscriber_id, context_hash). Index on (next_send_at, status).

sse_email_log

One row per attempted send. Drives reporting + compliance audit trail.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
subscriber_idbigint(20) unsignednullable for fully ad-hoc
emailvarchar(255)Captured at send-time
campaign_id / automation_idbigint(20) unsignednullable
categoryvarchar(40)Denormalised for fast filtering
legal_basisvarchar(40)consent / legitimate_interest / service
subjectvarchar(255)
mailgun_message_idvarchar(255)
statusvarchar(20)queued / sent / delivered / bounced / complained / failed
opened_at / clicked_at / bounced_at / complained_at / unsubscribed_at / delivered_atdatetimeFirst-event timestamps
open_count / click_countint unsignedCumulative counters
errortextProvider error text
contextlongtextJSON merge-data snapshot
source_plugin / source_referencevarcharFor 3rd-party integrations (e.g. ssp-product-renewals / renewal:order:123)
test_modevarchar(20)live / log_only / redirect
created_at / updated_atdatetime

Indexes: (subscriber_id, created_at), (campaign_id, status), mailgun_message_id, category, test_mode.

sse_email_events

Raw event stream from MailGun webhooks. Append-only.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
email_log_idbigint(20) unsignedFK (nullable — events can arrive before match)
mailgun_message_idvarchar(255)
eventvarchar(40)delivered / opened / clicked / unsubscribed / complained / permanent_fail / temporary_fail
payloadlongtextFull webhook payload (JSON)
occurred_atdatetimeFrom the webhook payload
received_atdatetimeWhen we received it

sse_email_queue

Cron-drained send queue. Small (rows live only while pending).

ColumnTypeDescription
idbigint(20) unsignedPrimary key
email_log_idbigint(20) unsignedFK — log row is created up-front
priorityintLower = sooner (transactional 1, marketing 10)
attemptsint unsigned
next_attempt_atdatetimeExponential backoff (1m → 4m → 16m → 64m → 256m)
locked_byvarchar(64)Worker ID
locked_untildatetime
last_errortext
created_atdatetime

sse_product_owners

Derived cache populated from completed/processing WC orders. Bundle expansion applied at write time.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
subscriber_idbigint(20) unsignedFK
product_idbigint(20) unsignedThe resolved product (direct line item OR bundle component)
directtinyint(1)1 = bought it directly, 0 = via a bundle
first_purchased_at / last_purchased_atdatetime

Unique on (subscriber_id, product_id, direct).

sse_confirmation_tokens

Double opt-in tokens. 7-day TTL.

ColumnTypeDescription
idbigint(20) unsignedPrimary key
tokenvarchar(64)Unique
subscriber_idbigint(20) unsignedFK
list_idslongtextJSON array of list IDs being confirmed
form_idbigint(20) unsignednullable
expires_atdatetime
consumed_atdatetimenullable
created_atdatetime

PHP Classes

Phase 1

SSE_Lists

SSE_Lists::create( array $data ): int|WP_Error
SSE_Lists::get( int $id ): object|null
SSE_Lists::get_all( array $args ): array
SSE_Lists::count( array $args ): int
SSE_Lists::update( int $id, array $data ): bool|WP_Error
SSE_Lists::delete( int $id ): bool
SSE_Lists::get_subscriber_count( int $id ): int

SSE_Subscribers

SSE_Subscribers::create( array $data ): int|WP_Error
SSE_Subscribers::get( int $id ): object|null
SSE_Subscribers::get_by_email( string $email ): object|null
SSE_Subscribers::get_by_cookie( string $cookie_id ): object|null
SSE_Subscribers::get_by_user_id( int $user_id ): object|null
SSE_Subscribers::update( int $id, array $data ): bool
SSE_Subscribers::delete( int $id ): bool
SSE_Subscribers::get_all( array $args ): array
SSE_Subscribers::count( array $args ): int
SSE_Subscribers::subscribe_to_list( int $subscriber_id, int $list_id, string $status = 'subscribed' ): bool
SSE_Subscribers::unsubscribe_from_list( int $subscriber_id, int $list_id ): bool
SSE_Subscribers::get_lists( int $subscriber_id ): array
SSE_Subscribers::export_csv( array $args ): void  // Streams CSV
SSE_Subscribers::total_count(): int

SSE_Forms

SSE_Forms::create( array $data ): int|WP_Error
SSE_Forms::get( int $id ): object|null  // JSON fields auto-decoded
SSE_Forms::get_all( array $args ): array
SSE_Forms::count( array $args ): int
SSE_Forms::update( int $id, array $data ): bool
SSE_Forms::delete( int $id ): bool
SSE_Forms::render( int $id, string $context = 'inline' ): string
SSE_Forms::get_popups_for_page( int $page_id, string $url ): array

Phase 2 — sending pipeline

sse_send() (global function)

The public send API. Used by ssp-product-renewals and any other plugin that wants SSE to deliver one message.

sse_send( array $args ): int|WP_Error

$args keys:

KeyTypeDescription
email (or subscriber_email)stringRecipient email (required)
categorystringOne of: newsletter / list_broadcast / product_marketing / product_support / onboarding / transactional. Default: transactional.
subjectstringSubject line (required)
body_textstringPlain-text body (required)
body_htmlstringOptional HTML body
from_name / from_email / reply_tostringOptional per-message overrides
subscriber_idintOptional; resolves automatically from email if omitted
campaign_id / automation_idintOptional FKs for log row
gate_contextarrayPer-call hints for the suppression gate (e.g. { category_term_id: 12 } for newsletter sends)
contextarraySnapshot data persisted to sse_email_log.context
source_plugin / source_referencestringFor third-party integrations
priorityintQueue priority (lower = sooner). Default 10 for marketing, 1 for transactional.

Returns: the new sse_email_log row ID, or WP_Error (invalid email, blocked by suppression gate, queue insert failure).

A return of WP_Error( 'sse_suppressed', ... ) is normal — it means the recipient is suppressed for that category and the send was correctly skipped.

SSE_Suppressions

SSE_Suppressions::add( string $email, string $scope = 'global', string $reason = '', string $source = '' ): int|false
SSE_Suppressions::remove( string $email, string $scope = 'global' ): bool
SSE_Suppressions::is_suppressed( string $email, string $scope ): bool
SSE_Suppressions::is_globally_suppressed( string $email ): bool
SSE_Suppressions::get_scopes_for_email( string $email ): string[]
SSE_Suppressions::get_all( array $args ): array
SSE_Suppressions::count( array $args ): int

Constants: REASON_USER_UNSUBSCRIBE, REASON_COMPLAINT, REASON_BOUNCE_HARD, REASON_BOUNCE_SOFT_REPEAT, REASON_MANUAL, REASON_MIGRATED.

SSE_Suppression_Gate

SSE_Suppression_Gate::check( string $email, string $category, array $context = array() ): array
// Returns: [ 'allowed' => bool, 'reason' => string|null ]

Encapsulates the category × suppression matrix. Every send goes through this.

SSE_Email_Log

SSE_Email_Log::create( array $data ): int  // Always reads test_mode from option
SSE_Email_Log::get( int $id ): object|null
SSE_Email_Log::record_event( int $log_id, string $event, string|null $occurred_at = null ): bool
SSE_Email_Log::find_by_mailgun_id( string $message_id ): object|null
SSE_Email_Log::for_subscriber( int $subscriber_id, array $args = array() ): array
SSE_Email_Log::campaign_stats( int $campaign_id ): array

SSE_Email_Queue

SSE_Email_Queue::init(): void  // Registers cron schedule + drain hook
SSE_Email_Queue::enqueue( int $log_id, int $priority = 10 ): int|false
SSE_Email_Queue::drain( int|null $batch_size = null ): int  // Returns messages sent
SSE_Email_Queue::pending_count(): int

SSE_Sender

SSE_Sender::send( array $args ): bool|WP_Error
// Internal — wraps wp_mail with category-aware From + Mailgun header injection.
// You don't call this directly; sse_send() / the drainer do.

SSE_Campaigns

SSE_Campaigns::create( array $data ): int|WP_Error
SSE_Campaigns::get( int $id ): object|null
SSE_Campaigns::get_all( array $args ): array
SSE_Campaigns::update( int $id, array $data ): bool|WP_Error
SSE_Campaigns::delete( int $id ): bool
SSE_Campaigns::send( int $id ): array|WP_Error  // Refuses unless status in draft/scheduled
SSE_Campaigns::send_test( int $id, string $to_email ): int|WP_Error
SSE_Campaigns::reset_to_draft( int $id ): bool
SSE_Campaigns::resolve_audience( object $campaign ): int[]  // Subscriber IDs

Status constants: STATUS_DRAFT, STATUS_SCHEDULED, STATUS_SENDING, STATUS_SENT, STATUS_CANCELLED.

SSE_Confirmations

SSE_Confirmations::issue( int $subscriber_id, int[] $list_ids, int|null $form_id = null ): string  // Token
SSE_Confirmations::consume( string $token ): object|WP_Error  // Returns the subscriber row
SSE_Confirmations::send_confirmation_email( int $subscriber_id, string $token ): int|WP_Error

SSE_Newsletter

SSE_Newsletter::init(): void  // Registers transition_post_status + hourly tick
SSE_Newsletter::enabled_terms(): int[]  // Allowed top-level term_ids
SSE_Newsletter::dispatch_instant( int $post_id ): int  // Recipients queued
SSE_Newsletter::run_digest( int|null $now_gmt = null ): int  // Recipients queued
SSE_Newsletter::resolve_top_level_terms( int $post_id ): int[]
SSE_Newsletter::is_schedule_match( int $now_gmt = null ): bool

SSE_Bundles

SSE_Bundles::components_of( int $product_id ): int[]  // [] if not a bundle
SSE_Bundles::is_available(): bool  // True if WC Product Bundles tables present

SSE_Product_Owners

SSE_Product_Owners::init(): void  // Registers WC hooks
SSE_Product_Owners::on_status_changed( int $order_id, string $old, string $new, $order = null ): void
SSE_Product_Owners::rebuild_for_order( int $order_id ): void
SSE_Product_Owners::rebuild_all( int $days = 0 ): int  // Backfill (used by migration)
SSE_Product_Owners::subscriber_ids_owning( int $product_id, bool $include_bundles = true ): int[]

Constant: PAID_STATUSES = [ 'completed', 'processing' ].

SSE_Segment

SSE_Segment::resolve( array $query, string $category = 'list_broadcast' ): int[]  // Wraps with suppression mask
SSE_Segment::resolve_raw( array $query ): int[]  // No mask — for testing

See Product Audiences & Segments for the predicate language.

SSE_Automations

SSE_Automations::create( array $data ): int|WP_Error
SSE_Automations::get( int $id ): object|null  // trigger_config auto-decoded
SSE_Automations::get_all( array $args = array() ): array
SSE_Automations::update( int $id, array $data ): bool
SSE_Automations::delete( int $id ): bool
SSE_Automations::add_step( int $automation_id, array $data ): int
SSE_Automations::update_step( int $step_id, array $data ): bool
SSE_Automations::delete_step( int $step_id ): bool
SSE_Automations::get_steps( int $automation_id ): array
SSE_Automations::count_steps( int $automation_id ): int

SSE_Automation_Scheduler

SSE_Automation_Scheduler::init(): void  // Registers WC hooks + tick
SSE_Automation_Scheduler::enroll( int $automation_id, int $subscriber_id, array $context = [] ): int|WP_Error  // Idempotent on (auto, sub, context_hash)
SSE_Automation_Scheduler::tick(): int  // Number of steps fired
SSE_Automation_Scheduler::on_wc_order_status_changed( int $order_id, string $old, string $new, $order = null ): void
SSE_Automation_Scheduler::on_wc_order_refunded( int $order_id ): void
SSE_Automation_Scheduler::match_wc_product_purchased( object $automation, array $purchase_data ): bool
SSE_Automation_Scheduler::registered_triggers(): array  // Filterable via 'sse_automation_triggers'

SSE_Mailgun_Webhook

SSE_Mailgun_Webhook::handle( WP_REST_Request $request ): WP_REST_Response
// Registered at /wp-json/sse/v1/mailgun-webhook. HMAC-verified.

SSE_Migrate (Phase 2F)

SSE_Migrate::apply_subscriber( array $rec, bool $commit = false ): array
SSE_Migrate::import_wc_customers( int $days = 0, bool $commit = false ): array

Action constants: ACTION_ACTIVE, ACTION_PENDING, ACTION_UNSUBSCRIBED, ACTION_BOUNCED, ACTION_COMPLAINED.

SSE_Migrate_MailPoet::is_installed(): bool
SSE_Migrate_MailPoet::build_list_map( bool $commit = false, array $existing_map = [] ): array
SSE_Migrate_MailPoet::run( bool $commit = false ): array  // Report

SSE_Migrate_MailerLite::parse_csv( string $path ): array
SSE_Migrate_MailerLite::run( string $path, bool $commit = false ): array  // Report

Filters & Actions

apply_filters( 'sse_automation_triggers', array $triggers ): array

Register custom triggers for SSE automations. Used by ssp-product-renewals (and anyone else).

add_filter( 'sse_automation_triggers', function( $triggers ) {
    $triggers['renewal_due_t30'] = array(
        'label'   => 'Renewal due in 30 days',
        'matcher' => 'my_plugin_match_renewal_t30',  // callable
    );
    return $triggers;
} );

Your matcher is called by the scheduler when external code invokes SSE_Automation_Scheduler::enroll() for that trigger type. See class-sse-automation-scheduler.php for the calling convention.


AJAX Endpoints

sse_subscribe

Form submissions. Registered for both logged-in and anonymous users.

POST: sse_form_id, sse_email, sse_first_name, sse_last_name, sse_nonce, sse_hp (honeypot)

Response: { success, data }

sse_update_subscriptions

Subscription preference updates from the management page. Also handles newsletter category prefs + frequency.

POST: nonce, list_ids[], newsletter_terms[], digest_frequency

Response: { success, data: { message } }


REST API

GET /wp-json/sse/v1/forms

Returns active forms. Used by the Gutenberg block editor.

Permission: edit_posts Response: [ { id, name }, ... ]

GET /wp-json/sse/v1/confirm?token=...

Double opt-in confirmation landing. Consumes the token, flips subscriber to active.

Permission: public

POST /wp-json/sse/v1/mailgun-webhook

MailGun event ingestion endpoint. HMAC-verified using sse_mailgun_webhook_key.

Permission: public (signature is the security)


WP-CLI Commands

wp sse migrate mailpoet     [--dry-run|--commit]
wp sse migrate mailerlite   --file=<path>  [--dry-run|--commit]
wp sse migrate wc-customers [--days=<n>]   [--dry-run|--commit]
wp sse migrate report

Default mode is --dry-run. --commit writes. Re-runs are idempotent.

See Email Migration Guide for usage.


Cron Schedules

Registered by SSE_Activator::schedule_cron() on activation:

HookIntervalClass
sse_drain_email_queueevery minute (sse_every_minute)SSE_Email_Queue::drain()
sse_newsletter_digest_tickhourlySSE_Newsletter::run_digest_if_due()
sse_automation_tickevery minuteSSE_Automation_Scheduler::tick()

Custom interval sse_every_minute is added via the cron_schedules filter in SSE_Email_Queue::init().


Constants

Defined in super-speedy-emails.php:

ConstantValue
SSE_VERSION1.04 (read from plugin header)
SSE_PLUGIN_DIRAbsolute path to plugin directory
SSE_PLUGIN_URLURL to plugin directory
SSE_PLUGIN_FILEPath to main plugin file
SSE_PLUGIN_BASENAMEPlugin basename for hooks

Options

Phase 1

OptionDefaultDescription
sse_db_version2.5.0Database schema version
sse_from_nameSite nameGlobal sender name
sse_from_emailAdmin emailGlobal sender email
sse_manage_page_id0ID of the subscription management page
sse_wc_myaccount_enabledfalseWooCommerce My Account tab toggle
sse_wc_endpoint_slugemail-preferencesWooCommerce endpoint slug
sse_cookie_lifetime365Cookie lifetime (days)
sse_delete_data_on_uninstallfalseDelete all data on uninstall

Phase 2 — sending

OptionDefaultDescription
sse_test_modelivelive / log_only / redirect
sse_test_redirect_emailemptyDestination for redirect mode
sse_mailgun_webhook_keyemptyMailGun webhook signing key
sse_from_name__<category>emptyPer-category sender name override
sse_from_email__<category>emptyPer-category sender email override
sse_reply_to__<category>emptyPer-category Reply-To override
sse_newsletter_enabled_terms[]JSON array of term_ids enabled for the newsletter
sse_newsletter_schedule[{day:tue,hour:12},{day:sat,hour:12}]Digest schedule (GMT)
sse_newsletter_digest_introemptyOptional intro text above the post list
sse_newsletter_last_run_at0Unix timestamp of last digest run
sse_migrate_mailpoet_prefixemptyOverride MailPoet table prefix (testing)
sse_support_email_cron_enabled0Opt-in: run every active Support Email on the daily sse_support_email_check cron

Support Emails (product-update / changelog engine)

Added in DB 2.3.0. A Support Email is a record in sse_support_rules; the engine fans out over its product checklist (one email per product to that product’s owners), applying a per-day guard, the suppression gate and recency rules. Full guide: Automated Support Emails — Developer Guide.

Table sse_support_rules: id, name, status (draft/active/paused), product_ids (JSON — the fan-out checklist), match_mode (all/any), conditions (JSON recency rules), subject, body, last_run_at, created_at, updated_at. (reference_mode/freq_cap_days columns are legacy/unused.)

Classes:

  • SSE_Support_Rules — CRUD for the records + passes_audience($record,$context) (recency rules) + describe().
  • SSE_Support_Emails — the engine: plan_support_email($record,$is_preview,$only_product), run_support_email($record,$only_product), daily_check() (cron, opt-in), AJAX ajax_preview() / ajax_send().
  • SSE_Support_Context — the object passed to every support-email hook (support_email_id, product_id, subscriber, matching_product, matching_products[], all_products[], is_preview, name; helpers is_product_mode(), owns(), bought_within(), owned_row(), wc_customer()).

Hooks (each callback gets ($value, SSE_Support_Context $context)):

  • sse_should_send_support_email (filter, per product, default true)
  • sse_support_email_subject (filter, optional subject override)
  • sse_support_email_content (filter, returns { body, name })
  • sse_support_email_should_send_to_user (filter, per owner, default true)
  • sse_support_email_sent (action, per queued email, real sends only)

There is no sse_support_email_product_ids filter and no version/reference dedup — repeat behaviour is the per-day guard (sse_email_log.source_reference = support:<record_id>:<product_id>). Changelog behaviour lives in the separate ssp-changelog-emails companion plugin.

AJAX actions: sse_support_email_preview (per-product preview from the unsaved editor form), sse_support_email_send (send one product of a saved record).


CSS Classes

Frontend form classes for custom styling — unchanged from Phase 1.

ClassElement
.sse-formForm wrapper
.sse-form--minimal / --clean / --brandedPreset modifier
.sse-form--loadingApplied during AJAX submission
.sse-form--successApplied after successful submission
.sse-form__innerThe <form> element
.sse-form__fieldsFields container (flexbox)
.sse-form__fieldIndividual field wrapper
.sse-form__inputInput elements
.sse-form__input.sse-has-errorInput with validation error
.sse-form__submitSubmit button
.sse-form__messageError/info message area
.sse-form__successSuccess message (hidden until success)
[data-form-id="X"]Target a specific form by ID

Enqueued Assets

HandleTypeWhen loaded
sse-formsCSS + JSWhen a form is rendered
sse-popupCSS + JSWhen a popup form matches the current page
sse-adminCSS + JSAdmin pages with sse- in the hook

Extending: a worked example

A third-party plugin (e.g. ssp-product-renewals) that wants to send a renewal email through SSE:

// In your plugin:
$result = sse_send( array(
    'email'            => $customer_email,
    'category'         => 'product_marketing',
    'subject'          => 'Your Scalability Pro renewal is due',
    'body_text'        => $rendered_body,
    'subscriber_id'    => $sub_id,
    'context'          => array(
        'order_id'       => $order_id,
        'product_id'     => $product_id,
        'renewal_due_at' => $due_at,
    ),
    'source_plugin'    => 'ssp-product-renewals',
    'source_reference' => "renewal:order:{$order_id}:t-{$days_until}",
) );

if ( is_wp_error( $result ) ) {
    if ( $result->get_error_code() === 'sse_suppressed' ) {
        // Recipient is suppressed for this category — that's a normal outcome.
        // Log it for your own audit if needed, but don't treat as a failure.
    } else {
        // Genuine error — invalid email, DB failure, etc.
        error_log( 'SSE send failed: ' . $result->get_error_message() );
    }
}
// $result is the sse_email_log row ID on success.

SSE handles the gate check, queue insert, From: address per category, audit logging, and delivery via the cron drainer. The other plugin doesn’t need to know about MailGun, suppressions, or queues — just compose and call.

Leave a Reply

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

×
1/1