Register a custom notification type with email, SMS, and push templates

MAM Suite ships a single notification pipeline that fans one event out to three channels — email, SMS, and push — and records each send. Your plugin does not talk to wp_mail(), an SMS provider, or APNs/FCM directly. Instead you register a notification type so admins can template and toggle it, then fire that type and let the dispatcher route it.

This guide takes you from “I have an event” to “the admin can edit the templates and the message goes out on every enabled channel.”

Goal

Register a custom notification type that:

  • Appears in Mobile App Mgr -> Notifications Manager so an admin can edit its email, SMS, and push templates and toggle each channel
  • Carries your own replacement tokens (for example [order_id], [tracking_number])
  • Fires from your sibling plugin in one call, with the dispatcher deciding which channels actually send

Prerequisites

  • mam-main is active. The notification dispatcher and the mam_notification_list registry live there; without it the hooks below do nothing.
  • A sibling plugin with its own slug (for example mam-my-plugin) where this code will live.
  • An event you want to notify on — a form submission, an order status change, a scheduled job, etc.
  • A WordPress user ID for the recipient. Notifications are addressed to a WP user; email defaults to that user’s user_email, SMS to their billing_phone user meta, and push to that user’s registered device tokens.

Steps

1. Choose a unique slug

The slug is the type’s identity. It becomes the message_type you pass when firing, and it is the suffix on every option key the admin UI reads and writes.

Use the convention <plugin-slug>-<event_name>, for example mam-my-plugin-order_shipped. The plugin prefix matters: slug collisions are silent and destructive. If two plugins register the same slug, whichever wins the filter ordering takes over, and the loser’s templates are ignored.

2. Register the type on mam_notification_list

Hook the registry filter and append one array describing your type. This is all it takes for the type to show up in the admin UI.

add_filter( 'mam_notification_list', function ( array $data ): array {
    $data[] = array(
        'source'          => 'mam-my-plugin',
        'title'           => 'Order Shipped',
        'slug'            => 'mam-my-plugin-order_shipped',
        'replacements'    => array( 'order_id', 'tracking_number', 'user_login' ),
        'default_content' => 'Your order [order_id] has shipped. Track: [tracking_number]',
        'default_on'      => array( 'email', 'sms', 'push' ),
        'description'     => 'Sent when an order is fulfilled and a shipping label is generated.',
    );
    return $data;
} );

Field reference:

Field Type Purpose
source string Your plugin slug. Used to group the type in the admin UI.
title string Human-readable name shown to admins.
slug string The unique identifier. Becomes the message_type value when firing.
replacements array Token names available in templates, declared without brackets.
default_content string (optional) Default body text the type seeds with.
default_on array (optional) Channels enabled by default — any of email, sms, push.
description string Admin-facing explanation of when the type fires.

Two things to get right here:

  • Declare your tokens. List every custom token in replacements (without brackets). Substitution still works if you skip this, but the admin’s template editor won’t list the token as available, so admins won’t know they can use it.
  • Set default_on. It is a default, not a force. If you omit it, the type registers but every channel starts off, and an admin has to enable each one by hand before anything sends.

3. Let the admin set the templates (or seed them programmatically)

Once registered, the type appears under Mobile App Mgr -> Notifications Manager, grouped by your source. The admin can edit a distinct template per channel:

  • Subject — used as the email subject and the push title (the SMS channel does not use it)
  • Email body
  • SMS body
  • Push body

The admin UI is the canonical place to author templates. If your plugin needs sensible defaults out of the box, seed the underlying option keys on activation — but only if they are not already set, so you never stomp an admin’s edits:

register_activation_hook( __FILE__, function () {
    $slug = 'mam-my-plugin-order_shipped';

    if ( ! get_option( 'tsl_notification_subject_' . $slug ) ) {
        update_option( 'tsl_notification_subject_' . $slug, 'Your order has shipped' );
    }
    if ( ! get_option( 'tsl_notification_email_text_' . $slug ) ) {
        update_option(
            'tsl_notification_email_text_' . $slug,
            "Hi [user_login],nnYour order [order_id] has shipped.nnTrack: [tracking_number]"
        );
    }
    if ( ! get_option( 'tsl_notification_pn_text_' . $slug ) ) {
        update_option( 'tsl_notification_pn_text_' . $slug, 'Order [order_id] shipped' );
    }
    if ( ! get_option( 'tsl_notification_text_text_' . $slug ) ) {
        update_option( 'tsl_notification_text_text_' . $slug, 'Order [order_id] has shipped. Track: [tracking_number]' );
    }
} );

The per-channel option keys, for a given <slug>:

Option key Channel Holds
tsl_notification_subject_<slug> email subject + push title subject / title line
tsl_notification_email_text_<slug> email email body
tsl_notification_text_text_<slug> SMS SMS body
tsl_notification_pn_text_<slug> push push body

And the per-channel enable flags, which the admin’s toggles write:

Option key Controls
tsl_notification_email_status_<slug> email on/off
tsl_notification_text_status_<slug> SMS on/off
tsl_notification_pn_status_<slug> push on/off

Note: these option keys use the tsl_notification_ prefix. Despite the broader tsl_*-to-mam_* rename elsewhere on the platform, these particular keys were never renamed and have no mam_* alias — the dispatcher and the admin UI both read and write the tsl_* names, so seed and read tsl_*.

4. Write your templates with bracketed tokens

In templates, tokens appear with brackets, matching the names you declared in replacements:

Your order [order_id] has shipped!
Track at: https://example.com/track/[tracking_number]
Thanks, [user_login].

Token rules worth knowing before you ship templates:

  • Tokens are case-sensitive. [user_login] and [User_Login] are different.
  • Undefined tokens are left in place. A token with no supplied value stays as literal [token_name] in the output — handy for spotting template bugs, ugly if it reaches a customer. Re-audit templates after any registry change.
  • Every token value comes from the replacements array you pass at fire time. The dispatcher does not auto-fill WP-user fields. If a template uses [user_login], [display_name], or any other user field, you must load the recipient and include those values in replacements yourself, right alongside custom tokens like order_id.
  • No token name is reserved. Because nothing is auto-hydrated, [user_login] is substituted only if you put user_login in replacements — it is just another key you supply. Naming a custom token user_login is fine; give it a distinctive name like order_user_login only if you want to avoid confusion in the template.

5. Fire the notification

Fire your type with the mam_notification_send_message action. The dispatcher resolves the message_type against the registry, decides per channel whether to send (using default_on plus the admin toggles), substitutes tokens into each channel’s template, queues or sends, and writes a history row.

$user = get_userdata( $user_id );

do_action( 'mam_notification_send_message', array(
    'message_type' => 'mam-my-plugin-order_shipped',
    'recipient_id' => $user_id,
    'replacements' => array(
        'user_login'      => $user->user_login,
        'order_id'        => $order_id,
        'tracking_number' => $tracking,
    ),
) );

A single fire produces email, SMS, and push together — all three channels share the same replacements and differ only in their template. Supply a value for every token your templates use, including user fields like [user_login] — the dispatcher does not fill them in from $user_id for you.

The message array accepts a few optional keys when you need to override the defaults:

Key Purpose
recipient_id WP user ID of the recipient (required in practice).
replacements Your custom token values, keyed by token name.
email Override the recipient email; defaults to the user’s user_email.
phone Override the SMS number; defaults to billing_phone user meta.
send_now Marks the push for immediate delivery. Note the dispatcher only queues at all when a single batch exceeds 10 messages and the global cron option is enabled, so a single-recipient fire always sends immediately.

Do not call mam_notification_send_pn (or wp_mail(), or an SMS provider) directly to “just send a push.” That skips per-channel opt-in, template resolution, and the history row. Always go through mam_notification_send_message and let the dispatcher route.

6. Verify

  1. Registry — confirm your type is present:
    print_r( apply_filters( 'mam_notification_list', array() ) );
  2. Admin UI — open Mobile App Mgr -> Notifications Manager, find your type under its source group, confirm the per-channel toggles persist and the templates save.
  3. Test send — open your type’s settings, pick a recipient from the user dropdown, and click Send Test Message.
  4. Outbox — check Mobile App Mgr -> Sent Messages for a row per enabled channel, with tokens substituted correctly.
  5. In-app — confirm the in-app notification center on the device shows the entry, if your type surfaces there.

Common pitfalls

  • Slug collision. Reusing a slug another plugin (or an older version of yours) registered silently overrides one definition. Always prefix with your plugin slug, and check the registry if templates seem to vanish.
  • Forgot default_on. The type registers but every channel is off until an admin enables it. Easy to mistake for “notifications are broken.”
  • Custom token not declared in replacements. Substitution still works, but the token won’t appear in the admin’s available-token hint list, so admins won’t use it.
  • *Wrote `mam_option keys.** The dispatcher readstsl_*`. If your seeded templates never appear, check the prefix.
  • Fired a channel hook directly. Bypassing the dispatcher skips opt-in and history. Fire mam_notification_send_message instead.
  • Notifications overview — the pipeline end to end
  • Notification types registry — the full mam_notification_list shape and the bundled core types
  • Notification channels: email, SMS, push — how each channel is actually transported, queued, and diagnosed
  • Notification template replacements — the token system in depth
  • Hook: mam_notification_send_message
  • Hook: mam_notification_list

What’s next

  • If your event needs to reach more than one recipient, fire once per recipient ID rather than batching — the dispatcher addresses a single user per call.
  • If sends are slow or bursty, read Notification queue and cron to understand when the dispatcher queues versus sends immediately — it only queues once a batch exceeds 10 messages and the global cron option is enabled.
  • If you want admins to opt individual users out, see Notification history and opt-out.
Was this article helpful?
Contents

    Need Support?

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