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-mainis active. The notification dispatcher and themam_notification_listregistry 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 theirbilling_phoneuser 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 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 broadertsl_*-to-mam_*rename elsewhere on the platform, these particular keys were never renamed and have nomam_*alias — the dispatcher and the admin UI both read and write thetsl_*names, so seed and readtsl_*.
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
replacementsarray 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 inreplacementsyourself, right alongside custom tokens likeorder_id. - No token name is reserved. Because nothing is auto-hydrated,
[user_login]is substituted only if you putuser_logininreplacements— it is just another key you supply. Naming a custom tokenuser_loginis fine; give it a distinctive name likeorder_user_loginonly 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(orwp_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 throughmam_notification_send_messageand let the dispatcher route.
6. Verify
- Registry — confirm your type is present:
print_r( apply_filters( 'mam_notification_list', array() ) ); - Admin UI — open Mobile App Mgr -> Notifications Manager, find your type under its
sourcegroup, confirm the per-channel toggles persist and the templates save. - Test send — open your type’s settings, pick a recipient from the user dropdown, and click Send Test Message.
- Outbox — check Mobile App Mgr -> Sent Messages for a row per enabled channel, with tokens substituted correctly.
- 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_messageinstead.
Related
- Notifications overview — the pipeline end to end
- Notification types registry — the full
mam_notification_listshape 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.
