PlugPress

Mailpuff

Mailpuff Hooks Reference: Actions and Filters for Developers

Every WordPress action and filter Mailpuff offers, with arguments and examples: sync new subscribers to another system, change messages before they send, report bounces, and extend the admin.

Mailpuff fires WordPress actions when subscribers, campaigns and emails change, and offers filters that adjust messages, audiences and sending limits. Use them to sync subscribers to another system, log activity, or build on Mailpuff from your own plugin. Every hook starts with mailpuff_, works in the free plugin, and needs no other plugin to be active.

What should I know before using Mailpuff hooks?

  • “Group” means tag. The admin and the AI tools say “tag”. Hooks, tables and REST routes keep the older internal word “group”, so mailpuff_contact_added_to_group fires when someone gets a tag.
  • “Contact” means subscriber. The admin says subscriber. The code says contact.
  • Load order. Register your hooks on plugins_loaded or later, or wait for mailpuff_loaded, so Mailpuff’s classes exist when your code runs.
  • Look up a contact with Mailpuff_Contact::find( $id ) or Mailpuff_Contact::find_by_email( $email ). Both return the row as an object, or null.

Which actions fire for subscribers?

Action Arguments Fires when
mailpuff_contact_created $contact_id, $source A subscriber is added. $source is form, import, subscribe (the API), admin or ability (an AI agent)
mailpuff_contact_status_changed $contact_id, $from, $to, $source A status changes, for example pending to subscribed
mailpuff_contact_confirmed $contact_id Someone clicks the double opt-in link
mailpuff_confirmation_sent $contact_id, $sent A confirmation email was attempted. $sent is true when it went out
mailpuff_contact_updated $contact_id, $changes A subscriber is edited in the admin, changed by a bulk action, or unsubscribed by an inactive cleanup
mailpuff_contact_deleted $contact_id, $email A subscriber is deleted in the admin, one at a time or in bulk
mailpuff_contact_added_to_group $contact_id, $group_id A subscriber gets a tag
mailpuff_contact_removed_from_group $contact_id, $group_id A tag is taken off a subscriber
mailpuff_preferences_saved $contact_id A subscriber saves their preferences page
mailpuff_inactive_cleanup $action, $ids, $context Inactive subscribers were tagged (group) or unsubscribed (unsubscribe)

Which actions fire for campaigns and emails?

Action Arguments Fires when
mailpuff_batch_queued $batch_id, $campaign_id, $total A send is queued
mailpuff_email_sent $email, $ok, $campaign_id, $contact_id, $batch_id Each queued email is handed to the mailer. $ok says whether it was accepted
mailpuff_batch_completed $batch_id, $campaign_id, $totals A send finishes or is stopped
mailpuff_campaign_sent $campaign_id, $totals A campaign finishes sending, or is stopped and marked sent
mailpuff_campaign_stopped $campaign_id Someone stops a send in progress
mailpuff_email_opened $batch_id, $contact_id, $first An open is tracked. $first is true on that reader’s first open
mailpuff_link_clicked $batch_id, $contact_id, $url A tracked link is clicked

Open and click actions only fire while tracking is on in Settings → Privacy.

Which other actions does Mailpuff offer?

Action Arguments What it’s for
mailpuff_loaded $plugin Mailpuff has booted. A safe point to register your own hooks
mailpuff_bounce $email, $type, $reason, $source Fire it to report a bounce. $type is hard, soft or complaint
mailpuff_erase_all None The owner ran Delete all data. Clean up anything your plugin stored for Mailpuff
mailpuff_admin_enqueue_scripts $handle Mailpuff’s admin app is loading. Enqueue scripts that depend on it here

Which filters can I use?

Filter Arguments What it changes
mailpuff_message $message, $context One message before it’s sent: to, subject, html and headers, after merge tags. $context has email_id, type, contact_id and batch_id
mailpuff_email_types $types The rules per email type: allowed statuses, unsubscribe, footer and tracking
mailpuff_audience_types $types Who an email can go to
mailpuff_contact_engagement_rules $rules The engagement rules segments can filter on
mailpuff_inactive_emails $count How many unopened campaigns make someone inactive. Default 5
mailpuff_engagement_excluded_types $types Email types left out of engagement counts
mailpuff_warmup_cap $cap, $day, $context The warm-up limit for a day. Your plan’s daily limit still applies on top
mailpuff_delivery_ready $ready, $route Whether the current sending route counts as ready
mailpuff_send_time $result The best send time Mailpuff suggests
mailpuff_send_time_min_opens $min Opens needed before the suggestion uses real data. Default 30
mailpuff_send_time_default $slot The fallback slot, weekday 0 to 6 and hour 0 to 23. Default Tuesday at 10
mailpuff_signup_min_seconds $seconds How fast a signup form may be sent before it counts as a bot. Default 2
mailpuff_subscribe_internal_groups $allow, $request Whether a Subscribe API request may give internal tags
mailpuff_smartcode_groups $groups The merge tag groups the editor offers
mailpuff_dashboard_summary $summary The numbers on the Home page
mailpuff_ability_mcp_public $public, $name Whether the MCP Adapter’s default server offers a Mailpuff tool
mailpuff_js_data $data The data passed to the admin app

Example: send new subscribers to another system

A person becomes a subscriber in one of two ways: added as subscribed right away, or confirmed later from pending. Hook both.

function myplugin_push_subscriber( $contact_id ) {
    $contact = Mailpuff_Contact::find( $contact_id );
    if ( ! $contact || 'subscribed' !== $contact->status ) {
        return;
    }
    wp_remote_post(
        'https://crm.example.com/hooks/new-subscriber',
        array(
            'headers'  => array( 'Content-Type' => 'application/json' ),
            'body'     => wp_json_encode(
                array(
                    'email'      => $contact->email,
                    'first_name' => $contact->first_name,
                )
            ),
            'blocking' => false,
        )
    );
}

add_action( 'mailpuff_contact_created', 'myplugin_push_subscriber' );

add_action(
    'mailpuff_contact_status_changed',
    function ( $contact_id, $from, $to ) {
        if ( 'subscribed' === $to ) {
            myplugin_push_subscriber( $contact_id );
        }
    },
    10,
    3
);

Example: add a header to every campaign email

add_filter(
    'mailpuff_message',
    function ( $message, $context ) {
        if ( 'broadcast' === $context['type'] ) {
            $message['headers'][] = 'X-Campaign-Id: ' . $context['email_id'];
        }
        return $message;
    },
    10,
    2
);

Mailpuff adds the List-Unsubscribe headers back after this filter runs, so a filter can’t remove them by accident.

Example: report a bounce from your own mail code

do_action( 'mailpuff_bounce', '[email protected]', 'hard', '550 5.1.1 User unknown', 'my-plugin' );

A hard bounce or a complaint stops all mail to the address at once. Three soft bounces within a month do the same. The sending services guide explains how PostShiba and Mailyard report bounces on their own.

Can I extend the Mailpuff admin screens?

Yes. The admin app is built with React and reads these filters from @wordpress/hooks. Add yours in a script enqueued on mailpuff_admin_enqueue_scripts.

Filter What it changes
mailpuff.sections The sections in Mailpuff’s admin menu
mailpuff.screens The screen shown for each section and page
mailpuff.home.cards Extra cards on the Home page
mailpuff.commands Extra commands in the WordPress command palette (⌘K)
mailpuff.createItems Items in the header’s Create menu
mailpuff.helpItems Items in the header’s help menu

Can I call Mailpuff’s features from PHP?

Yes. Every AI tool is a WordPress ability you can run from your own code, with the same checks:

$overview = wp_get_ability( 'mailpuff/get-overview' )->execute( array() );

The tools reference lists all 40 with what they do.

FAQ

Does Mailpuff have a hook for “a subscriber unsubscribed”?

Use mailpuff_contact_status_changed and check for $to === 'unsubscribed'. The same hook covers bounced and complained.

Are there old hook names I should also support?

No. Every hook is named mailpuff_*, with no older aliases.

Can another plugin show Mailpuff data on its own contact screen?

Yes. Mailpuff answers the shared plugpress_contact_cards filter with a Newsletter card for an email address: their status and tags, with a link to their record. PlugPress plugins such as Bridle share this filter, so each can show what the others know about a person without a shared table.