Summary
Admin broadcast sends are logged to wp_mam_notifications_history (a send-log). The in-app notification center (Open Notifications content class) reads per-user notification rows from a separate table, wp_mam_notifications_manager. Per-user opt-out is enforced before send.
wp_mam_notifications_history
Owned by MAM_Notification_History_Repository (constant TABLE = 'mam_notifications_history', PK this_index). Renamed from wp_tsl_local_app_notifications_history in PR #31. Frozen — do not rename in place.
This is the admin broadcast send-log, not a per-user read/unread store. Per-row schema:
| Column | Purpose |
|---|---|
this_index |
Primary key (auto-increment) |
message |
Message body (VARCHAR 300) |
trans_date |
Insert timestamp (CURRENT_TIMESTAMP) |
send_date |
Scheduled/actual send date |
target_type |
Recipient targeting mode |
target_source |
Targeting source id |
ios_phones |
iOS device count |
android_phones |
Android device count |
sent_by_name |
Admin who sent the broadcast |
The repository is a thin MAM_Base_Repository subclass (constants only) — grid classes read it directly via $wpdb.
In-app notification center
The Open Notifications content class reads via mam_notification_manager::get_notifications_for_user() from the wp_mam_notifications_manager table (not the history send-log). The query is:
SELECT * FROM wp_mam_notifications_manager
WHERE title != 'New chat message'
AND pn_status < 3
AND user_id = ?
ORDER BY this_index DESC
Read state is tracked by the pn_status column (chat messages, title = 'New chat message', are filtered out). mam_notification_manager::mark_notification_read() updates pn_status; remove_notification() deletes a row. Per-user rows carry notification_date (converted to the user’s local time on read), data_json, and is_silent.
Opt-out: email
Per-user opt-out is stored in wp_mam_email_opted_out (or per-user meta — varies by site). Before sending, MAM_Email_Sender checks:
if ( $this->is_opted_out( $user_id, $email ) ) {
// skip; record as opted_out in history
return;
}
Opt-outs are populated from:
- The unsubscribe link in outbound emails
- An admin override in Mobile App Manager → Notifications → Opt-Outs
- A per-user toggle in the user’s profile
Opt-out: SMS
SMS opt-out is handled at two layers. Twilio responds to “STOP” replies by suppressing future messages from that number (carrier-level compliance). In addition, mam-main mirrors opt-out state locally: the inbound webhook (admin-ajax.php?action=mam_sms_webhook, signature-validated with X-Twilio-Signature) catches STOP/STOPALL/UNSUBSCRIBE/CANCEL/END/QUIT and START/YES/UNSTOP keywords and writes to wp_mam_email_opted_out (phone in the email column, E.164 form).
MAM_Sms_Client checks this table before every non-transactional send, so opted-out numbers are skipped client-side and surface in the Opt Out List admin page alongside email opt-outs. Transactional sends (phone-verification codes) bypass the list. mam-main appends the STOP-to-opt-out copy to marketing messages automatically. Point the Twilio number / messaging service “A message comes in” webhook at the URL shown on the Notifications Manager → SMS Setup tab.
Opt-out: push
Push has no opt-out table. The user opts out by:
- Revoking push permission in iOS / Android Settings (the OS stops delivering)
- Uninstalling the app (token becomes invalid)
- Explicitly disabling in-app push (sets
usermetapush_enabled = 0)
Send attempts to a revoked-permission token return BadDeviceToken from APNs / Unregistered from FCM. The token repository should drop the stored token on these errors (tracked as a hardening item).
Outbox viewer
Mobile App Manager → Notifications → Outbox uses mam_notifications_outbox_viewer (a WP_List_Table subclass) to surface recent dispatches across all channels with their statuses. Useful for diagnosing delivery failures.
Gotchas
- History rows are not deleted automatically. A high-traffic site accumulates rows indefinitely. If you implement pruning, do it via the repository — direct
DELETErisks corrupting indexes used by the admin grid queries. - Two distinct tables.
wp_mam_notifications_historyis the admin broadcast send-log; the per-user in-app center readswp_mam_notifications_manager(filtered onpn_status). Don’t conflate them. - Opt-out is checked at send time, not at queue time. A user who opts out between queue and send (cron tick later) has the opt-out honored.
- PR #31 rename: backward-compat aliases mean direct
$wpdbreads against the legacywp_tsl_local_app_notifications_historytable name no longer work. Use the repository.
Related articles
- Notifications overview
- Notification channels: email, SMS, push
- Notification queue and cron
- Content class: Open Notifications
- Frozen public contracts reference
Metadata
| Field | Value |
|---|---|
| Article type | Plugin Overview |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Category | Plugin Reference |
| Audience | PHP developer |
| Last verified | 2026-05-02 |
