Hook: mam_notification_send_message

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:

  1. Look up $message['message_type'] in mam_notification_list
  2. For each channel (email, SMS, push), check the per-channel opt-in:
    • Email: tsl_notification_email_status_<slug> enabled OR type’s default_on includes email
    • SMS: tsl_notification_text_status_<slug> OR default_on includes sms (also requires phone)
    • Push: tsl_notification_pn_status_<slug> OR default_on includes push (also requires recipient_id)
  3. 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_type must match a registered slug. Unregistered slugs are silently dropped.
  • Per-message send_text/send_email/send_pn flags were inert and were removed in PR #34. Channel routing comes from the type registry, not per-message flags.
  • Legacy add_filter registration retained — older apply_filters callers 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 mamdebug admin alert but otherwise no-op.
  • Don’t fire mam_notification_send_pn directly unless you’re in the system_chat special path. Use mam_notification_send_message so 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.

  • 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
Contents

    Need Support?

    Can’t find the answer you’re looking for? Don’t worry we’re here to help!