Notification history and opt-out

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 usermeta push_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 DELETE risks corrupting indexes used by the admin grid queries.
  • Two distinct tables. wp_mam_notifications_history is the admin broadcast send-log; the per-user in-app center reads wp_mam_notifications_manager (filtered on pn_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 $wpdb reads against the legacy wp_tsl_local_app_notifications_history table name no longer work. Use the repository.

  • 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
Was this article helpful?
Contents

    Need Support?

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