Purpose
The single entry point for sending notifications across email, SMS, and push channels. Fired by sibling plugins, form handlers, and core mam-main flows (login, registration, lost-password). Routed by MAM_Notification_Dispatcher to per-channel senders based on the mam_notification_list type registry plus per-channel opt-in option keys.
⚠️ Frozen contract. Renaming silently breaks every fire site and every legacy apply_filters caller.
Signature
do_action( 'mam_notification_send_message', array $message );
Or for legacy callers:
$result = apply_filters( 'mam_notification_send_message', array $message );
// $result['status'] === true on success
The action and filter are dual-registered — see Notifications overview for the SC #175 history.
| Parameter | Type | Description |
|---|---|---|
$message |
array | The message shape — see below |
Message shape
array(
'message_type' => 'mam-my-plugin-event_name', // required — must match a slug from mam_notification_list
'recipient_id' => 123, // WP user id
'email' => 'override@example.com', // optional — defaults to user's email
'phone' => '+15551234567', // optional — defaults to billing_phone usermeta
'subject' => 'Optional override', // rarely used — usually templated
'message' => 'Inline body', // rarely used — usually templated
'replacements' => array( // token substitution values
'order_id' => '42',
'tracking_number' => 'ABC123',
),
'send_now' => true, // optional — bypass cron threshold
'data' => array( ... ), // optional push-payload extras (deeplink, action buttons)
)
A batch can be sent by passing an array of message arrays:
do_action( 'mam_notification_send_message', array(
array( 'message_type' => '...', 'recipient_id' => 1, ... ),
array( 'message_type' => '...', 'recipient_id' => 2, ... ),
// ... up to N messages
) );
The dispatcher detects single-message vs batch by the presence of message_type at the top level.
Routing
For each message:
- Look up
$message['message_type']inmam_notification_list - For each channel (email, SMS, push), check the per-channel opt-in:
- Email:
tsl_notification_email_status_<slug>enabled OR type’sdefault_onincludesemail - SMS:
tsl_notification_text_status_<slug>ORdefault_onincludessms(also requires phone) - Push:
tsl_notification_pn_status_<slug>ORdefault_onincludespush(also requiresrecipient_id)
- Email:
- Dispatch to the enabled channel sender(s)
See Notification channels: email, SMS, push.
Cron decision
$use_cron = ( count( $messages ) > 10 ) AND ( get_option( 'mam_notification_use_cron' ) == 1 );
A batch of >10 messages on a cron-enabled site queues; everything else sends immediately.
Special case: system_chat
do_action( 'mam_notification_send_message', array(
'message_type' => 'system_chat',
'recipient_id' => $user_id,
'message' => 'New chat message',
'target' => $thread_post_id, // permalink target
'data' => array( ... ),
) );
system_chat bypasses notification-list resolution and fires mam_notification_send_pn directly. Used by the chat manager for instant push.
Example: simple fire from a sibling plugin
do_action( 'mam_notification_send_message', array(
'message_type' => 'mam-my-plugin-order_shipped',
'recipient_id' => $user_id,
'replacements' => array(
'order_id' => $order_id,
'tracking_number' => $tracking,
),
) );
The dispatcher resolves the type, applies templates, fires per enabled channel, and writes a row to wp_mam_notification_history for each.
Example: from a form-submission handler
Don’t fire mam_notification_send_message directly from form handlers. Go through mam_form_manager_send_notifications:
add_action( 'mam_form_manager_send_notifications', function ( $entry, $form, $result ) {
if ( $form['id'] !== 42 ) return;
do_action( 'mam_notification_send_message', array(
'message_type' => 'mam-my-plugin-form42_submitted',
'recipient_id' => $entry['created_by'],
'replacements' => array( /* ... */ ),
) );
}, 10, 3 );
This lets admin overrides intercept before notification dispatch.
Gotchas
message_typemust match a registered slug. Unregistered slugs are silently dropped.- Per-message
send_text/send_email/send_pnflags were inert and were removed in PR #34. Channel routing comes from the type registry, not per-message flags. - Legacy
add_filterregistration retained — olderapply_filterscallers in mam-account-manager and customer-site copies of older plugins still work. SC #175 tracks removal. - Return value (filter path) is
$message_array + ['status' => true]— used only by the legacy filter path. Action callers discard. - Empty messages produce a
mamdebugadmin alert but otherwise no-op. - Don’t fire
mam_notification_send_pndirectly unless you’re in thesystem_chatspecial path. Usemam_notification_send_messageso the dispatcher writes the history row. - Cron-vs-immediate is a heuristic. A 1000-message batch on a cron-disabled site sends synchronously and likely overruns the request timeout. Turn cron on for any site with bulk sends.
Related articles
- Notifications overview
- Notification types registry
- Notification channels: email, SMS, push
- Notification queue and cron
- Hook: mam_notification_send_pn
- Hook: mam_notification_list
- Hook: mam_form_manager_send_notifications
- Recipe: Register a notification type
- Frozen public contracts reference
Metadata
| Field | Value |
|---|---|
| Article type | Hook Reference |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Hook type | action (with legacy filter registration) |
| Audience | PHP developer |
| Frozen contract | yes — ~30 fire sites across mam-suite + mam-suite-hold |
| Last verified | 2026-05-02 |
