Goal
A notification your app should be sending — a welcome email, a password-reset code, an order-shipped text — never arrives. There are two distinct causes, and this guide shows you how to tell them apart:
- The notification fired, but it’s stuck. It was queued and the queue isn’t draining (a cron problem), or the channel transport itself is misconfigured (
wp_mail/ Twilio), or the recipient is opted out. - The notification never fired at all. The notification type isn’t enabled for that channel, the recipient has no email or phone on file, or the triggering event never reached the dispatcher.
Work through the steps in order — by the end of Step 2, you’ll usually already know which category you’re in.
Prerequisites
- A WordPress admin account (the
manage_optionscapability) on your MAM Suite site. - MAM Suite with
mam-mainactive. The notification system (notifications-manager) and the queue processor (mail-cron-manager) both live inmam-main. - For SMS: an SMS provider configured (Twilio). MAM Suite sends SMS through its in-house Twilio client (
MAM_Sms_Client) — if no provider is wired up, the client reports the send asskippedrather than delivering it. - For email: a working outbound mail path on the site. MAM Suite sends email through WordPress’s standard
wp_mail(), so an SMTP plugin or a deliverability service (SendGrid, Postmark, etc.) is strongly recommended. - Helpful, not required: access to your Twilio (or other provider) dashboard, and access to your hosting/SMTP logs.
Steps
1. Check the Outbox first — did it fire at all?
Start here: open Mobile App Manager → Sent Messages — the Outbox grid. It lists recent dispatches across all three channels (email, SMS, push) with a per-row status, and it’s the fastest way to split the two failure categories.
- You see a row for the message. Good — it fired. Look at the status column:
queued— it’s waiting on cron. Go to Step 2.failed— the transport rejected it. Go to Step 4 (email) or Step 5 (SMS).sent— MAM Suite handed it off successfully; the problem is downstream (spam folder, carrier filtering, provider suppression). Go to Step 5 for SMS provider checks, or check the recipient’s spam folder and your sending-domain reputation for email.opted_out(or a skipped/suppressed marker) — the recipient is opted out. Go to Step 6.
- You see no row at all. That means the notification likely never fired — go to Step 7.
The Outbox reads from the notification history log (
wp_mam_notification_history). One event can target three channels, so it produces three separate rows — seeing the email row assentand the SMS row asfailedfor the same event is normal, not a bug.
2. Confirm whether the message was queued or sent immediately
MAM Suite doesn’t queue every notification — it decides per batch whether to send right away or hand things off to cron. Both of these have to be true before a batch gets queued:
- The batch contains more than 10 messages, and
- The global “use cron” option (
mam_notification_use_cron) is turned on.
Here’s what that means in practice:
- A small batch (10 or fewer recipients) always sends immediately, regardless of the cron setting. If a single welcome email or one password-reset code isn’t arriving, queueing isn’t your problem — skip to Step 4/5/6.
- A large batch — a bulk custom notification to your whole user base — only queues if the cron option is on. Turn it off, and MAM Suite tries to send every message synchronously in one request, which can blow past PHP’s execution-time limit and leave a chunk of the batch unsent. If your site does regular bulk sends, keep the cron option on.
If your stuck messages are queued in the Outbox, they were batched and handed to cron — continue to Step 3. If they’re a small immediate send, go to Step 4/5.
3. Verify cron is actually running (the usual culprit for a stuck queue)
Queued notifications drain on a schedule, so if that schedule isn’t ticking, the queue just keeps growing and nothing goes out. MAM Suite doesn’t rely on WordPress’s built-in wp-cron.php for this (which only fires when someone visits the site) — instead it uses FastCron, an external service that pings a known endpoint on a fixed schedule so the queue drains even on a low-traffic site.
The queue-draining job is registered under the cron id mam_mail_handler. On each tick it:
- Pulls a batch of email from the email queue (
wp_mam_mail_queue) and sends each viawp_mail(). - Pulls a batch of SMS/push from the unified queue (
wp_mam_sms_queue) and dispatches each.
Work through these, in order:
- Is FastCron configured? Open the SetCron / FastCron admin settings page (under Mobile App Manager, in the cron/integration settings). There’s an API-token field.
- A real token is a non-numeric string. If it’s missing — or set to the literal value
1— cron is disabled. That1is a “disabled” sentinel, and it turns cron off silently, with no error to tip you off. It’s a common gotcha, so set a real token to re-enable it.
- A real token is a non-numeric string. If it’s missing — or set to the literal value
- Is the
mam_mail_handlercron enabled in the list of registered crons? If it’s toggled off, the queue won’t drain even with a valid token. - Can FastCron reach your site? FastCron pings your public site URL as an unauthenticated request. If your site sits behind HTTP basic auth, an IP allow-list, a staging password, or a “coming soon” gate, FastCron’s pings are blocked and nothing drains. Allow-list FastCron, or remove the gate.
- Is the queue actually moving? Re-open the Outbox after a few minutes. If
queuedrows are flipping tosent, cron is working and the backlog is just catching up. If they’re frozen, cron isn’t ticking — revisit items 1–3.
Email retries up to 3 times before a row is finally marked
failedfor the audit trail. SMS and push do not retry — once a dispatch fails, it stays failed, and getting it out means firing the notification again. AfailedSMS row in the Outbox won’t resolve itself if you just wait.
4. Email won’t send: check the wp_mail path
If the email row shows failed (or sent but never received), the queue and cron aren’t your problem — the mail transport is. MAM Suite sends through WordPress’s standard wp_mail(), so anything that breaks wp_mail() breaks MAM Suite email too.
- Confirm the site can send any email at all. Trigger a generic WordPress email (e.g., a password reset from
wp-login.php) and see whether it arrives. If even that fails, it’s a site-wide mail problem, not a MAM Suite one. - Install/verify an SMTP or deliverability plugin. Out of the box, many hosts have unreliable or disabled
mail(). Routingwp_mail()through authenticated SMTP (or SendGrid/Postmark/Mailgun) usually improves delivery a lot. Confirm its credentials are valid and its connection test passes. - Check the From address and domain reputation. Email “sent” by MAM Suite that lands in spam (or is silently dropped) is usually a sending-domain reputation or SPF/DKIM/DMARC problem on the From address, not a MAM Suite bug. Make sure the From address is on a domain you control and have authenticated.
- Look for
wp_mail_failederrors in your site/SMTP logs. The transport (SMTP server) often reports the real reason — rejected sender, rate limit, bad recipient — there.
5. SMS won’t send: check the provider (Twilio)
SMS goes out through mam-main’s in-house Twilio client (MAM_Sms_Client), whose send() method talks to Twilio directly. Here’s what most commonly goes wrong:
- No SMS provider is configured. If Twilio credentials aren’t entered,
MAM_Sms_Client::is_configured()returns false and the send comes back asskipped(twilio_not_configured) — nothing sends. Confirm your Twilio credentials are entered and active on the SMS Setup tab of the Notifications Manager page. - The recipient has no phone number. SMS uses the recipient’s
billing_phoneuser meta (unless a phone is passed explicitly with the message). A user with nobilling_phoneon file gets no text. Confirm the recipient actually has a phone number stored. - The number is invalid or unformatted. Provider rejections for badly formatted numbers (missing country code, etc.) surface in your Twilio/provider dashboard, not in WordPress. Check the provider’s message logs for the specific error.
- Provider quota / account issue. A suspended Twilio account, an exhausted balance, or a messaging-service misconfiguration will reject sends. Again, the provider dashboard is the source of truth here.
- Carrier filtering / A2P registration. US carriers filter unregistered application-to-person traffic. If sends show “delivered” to Twilio but never reach phones, check your A2P 10DLC / sender registration status with the provider.
MAM Suite automatically appends a “reply STOP to opt out” line to outbound SMS. That’s expected, not a bug.
6. Rule out an opt-out
Opt-outs are honored at send time — so if a recipient opted out, even after their message was queued, it won’t go through. Each channel handles this a little differently:
- Email. MAM Suite checks a per-user opt-out before sending and records the skip in history. Opt-outs come from the unsubscribe link in outbound emails, a per-user profile toggle, or an admin override on the Opt Out List page (Mobile App Manager → Opt Out List). Check that the recipient isn’t on the opt-out list.
- SMS. SMS opt-out is enforced at two layers. Twilio suppresses future messages to a number that replied “STOP,” and mam-main also mirrors that state locally: an inbound STOP webhook writes the number to the shared opt-out table (
wp_mam_email_opted_out, phone stored in theemailcolumn), andMAM_Sms_Clientchecks it before every non-transactional send. Opted-out numbers surface on the Opt Out List admin page alongside email opt-outs (transactional sends like phone-verification codes bypass the list). Check both the Opt Out List and the recipient’s number in your Twilio dashboard. - Push (for completeness). Push has no opt-out table; the user opts out by revoking OS-level permission, uninstalling, or disabling push in-app. Revoked tokens come back as
BadDeviceToken(APNs) /Unregistered(FCM).
If the recipient is opted out and shouldn’t be, clear the opt-out at its source (un-STOP via the provider for SMS, remove the email opt-out entry, etc.) and re-send.
7. The notification never fired: check the type and the trigger
If the Outbox shows no row at all for the event, the message never reached the dispatcher in the first place — so there’s nothing for cron or the transport to deliver. Work through these:
-
Is the channel enabled for that notification type? Every notification type declares which channels it supports, and admins toggle channels per type on the Mobile App Manager → Notifications Manager page. A type whose email (or SMS) channel is switched off — and isn’t on by default — is simply skipped for that channel. This is the single most common reason a “missing” notification turns out to have never fired. Confirm the channel is enabled for the specific type.
Several bundled types ship off by default (for example, the welcome email) and require an admin to enable them. If your welcome email never sends, this is very likely why.
-
Does the recipient have the contact detail the channel needs? No email on the user account → no email sent. No
billing_phone→ no SMS sent. The dispatcher silently skips a channel when the recipient has no address for it. -
Did the triggering event actually happen? The dispatcher only acts when something fires the send. If the upstream event (a form submission, an order status change, a registration) didn’t complete, no notification is generated. Re-trigger the event and watch the Outbox in real time.
-
For a custom/developer-built notification: confirm the firing code is actually running and is using the correct
message_typeslug. A typo’d slug, or a slug that no plugin has registered, resolves to nothing and silently sends nothing. Notification firing should go through the standard send-message action and reference a slug that’s registered in the type registry — not call a channel directly.
Quick reference: stuck vs. never-fired
| Symptom in Outbox | Most likely cause | Go to |
|---|---|---|
Row shows queued, not moving |
Cron/FastCron not running | Step 3 |
Row shows failed (email) |
wp_mail / SMTP transport |
Step 4 |
Row shows failed (SMS) |
Provider/Twilio config, bad/invalid number | Step 5 |
Row shows sent, not received |
Spam folder, domain reputation, carrier/provider suppression | Steps 4–5 |
Row shows opted_out/skipped |
Recipient opted out | Step 6 |
| No row at all | Channel disabled for the type, no recipient contact info, or event never fired | Step 7 |
Related
- Notification queue and cron — the queue tables, the >10-message batch threshold, and the per-tick batch sizes.
- Integration: FastCron — how cron is scheduled and the API-token “
1= disabled” gotcha. - Notification channels: email, SMS, push — the per-channel senders and their specific failure modes.
- Notification history and opt-out — the Outbox, the history log, and per-channel opt-out behavior.
- Notification types registry — how types declare channels and how per-type channel opt-in works.
What’s next
Once delivery’s healthy, turn the global “use cron” option on for any site that sends bulk notifications, and confirm FastCron has a valid (non-1) API token. Then make the Outbox your first stop whenever a notification goes missing — its per-row status will almost always tell you, in one screen, whether you’re chasing a stuck queue or a notification that never fired in the first place.
