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_groupfires when someone gets a tag. - “Contact” means subscriber. The admin says subscriber. The code says contact.
- Load order. Register your hooks on
plugins_loadedor later, or wait formailpuff_loaded, so Mailpuff’s classes exist when your code runs. - Look up a contact with
Mailpuff_Contact::find( $id )orMailpuff_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.