Quick check
Run through these four before going deeper.
- The send shows up in Settings → SMS log.
- That row’s
statusisdelivered— notqueued,sent,failed, oropted_out. - The recipient’s number is in E.164 format (
+15551234567) on their client record. - The recipient hasn’t replied STOP previously (look for an “Opted out” chip on the client detail page).
Recipient opted out
The symptom
SMS log row shows status = opted_out. No message was actually sent. The client detail page shows an Opted out chip next to the phone number.
Why it happens
The recipient replied STOP (or one of the carrier-recognized variants: UNSUBSCRIBE, CANCEL, END, QUIT) at some point in the past. Carrier regulations are strict about this — once a number opts out, we’re legally required to stop sending until they explicitly opt back in by replying START or UNSTOP.
The opt-out is global per number, across every template, every campaign, and every store. It is not per-conversation.
How to fix it
- Confirm the opt-out is real — click the chip on the client detail to see the timestamp and the inbound message that triggered it.
- If the recipient asks you in person to resume texts, instruct them to reply START from their phone. We can’t flip the opt-out flag from the admin side — the recipient has to do it.
- Meanwhile, use email or a phone call instead. The client’s contact preferences card shows what other channels are available.
The carrier compliance gate on core.fn_sms_check_opt_out is intentionally not overridable from the operator UI. Forcing a send to an opted-out number creates regulatory exposure and gets your sender number flagged for revocation.
Invalid phone number format
The symptom
Log row shows status = failed, error_code = invalid_phone (or Twilio code 21211).
Why it happens
The number on the client record isn’t in E.164 format. We require:
- Leading
+ - Country code (e.g.
1for US/Canada) - National number with no spaces, dashes, parens, or dots
So +15551234567 — not (555) 123-4567 or 555-1234567 or 1 555 123 4567.
How to fix it
- Open the client detail page. Edit the phone field.
- Reformat to E.164: prepend
+and the country code, strip all separators. For US numbers that’s+1+ the 10-digit number, e.g.+15551234567. - Save. The recipient’s normalized number is what subsequent sends use.
- Retry the action that triggers the SMS. Next log row should be
delivered.
Number is a landline
The symptom
Log row shows status = failed, error_code = 30006 (Twilio) or landline_unsupported (SendBlue).
Why it happens
The carrier reports the number is a landline or VoIP line that doesn’t accept SMS. There’s no workaround on our side — if the carrier won’t terminate the message, nothing we send will land.
How to fix it
- Confirm with the recipient that the number is a mobile (text-capable). If they have a different mobile number, update the client record to use that one.
- If the recipient genuinely only has a landline, set their contact preferences to email-only and use email instead.
- If you have many landline-classified numbers that you’re sure are actually mobile, write to {{CONTACT_EMAIL}} with samples — carrier line-type lookups occasionally lag for ported numbers.
Sender number not provisioned
The symptom
Log row shows status = failed, error_code = sender_not_provisioned or Twilio 21606 / 21659.
Why it happens
The from-number on the SMS template (or your org’s default sender) isn’t actually provisioned in the SendBlue or Twilio account that’s sending. Common causes:
- You set up a new org with a sender number that was never actually bought through the provider.
- The provider reclaimed the number for non-payment or A2P registration lapse.
- The number is registered to one Twilio sub-account but you’re sending from a different one.
How to fix it
- Open Settings → SMS → Sender numbers. Confirm the default sender for the org has a green Active badge.
- If it’s not active, open the provider dashboard (SendBlue or Twilio). Confirm you actually own the number and that your A2P 10DLC registration is current.
- If the number was reclaimed, you’ll need to buy a new one and update the org default sender.
- Retry the SMS. Next log row should land at
delivered.
Held because of quiet hours
The symptom
Log row shows status = scheduled, with a future send_at timestamp. The recipient gets it later — not at the moment you triggered the send.
Why it happens
Per-recipient or per-org quiet hours deliberately delay non-urgent SMS that would otherwise land overnight. For example, if quiet hours are 21:00–08:00 local time and you trigger a marketing SMS at 23:00, we schedule it to fire at 08:01 the next day.
This is by design — sending overnight tanks open rates and gets you reported.
How to fix it
- If this is a transactional SMS that needs to land immediately (OTP, urgent order status), mark the template as
category = urgentin Settings → SMS templates. Urgent SMS bypasses quiet hours. - If quiet hours are too restrictive for your business, edit them in Settings → SMS → Quiet hours. They’re org-wide (or per-recipient when the client explicitly set theirs).
- If the delay is fine, wait — the SMS will fire automatically when the window opens.
Monthly quota exceeded
The symptom
Log row shows status = failed, error_code = quota_exceeded. Settings → SMS → Usage shows you at or over your monthly cap.
Why it happens
Each org has a monthly SMS budget configured in core.sms_settings. Hitting it stops further sends until the cap resets at the start of the next month (or you raise the cap).
How to fix it
- Confirm the usage in Settings → SMS → Usage. It shows the count, the cap, and the reset date.
- If you need more headroom this month, see Billing — the SMS add-on is metered separately.
- Once you raise the cap (or the month rolls over), retry. The next log row should fire normally.
- Long-term, audit which templates fire most. A noisy “every status update” template can chew through quota fast — consider digesting into one daily summary.
Carrier-level block
The symptom
Log row shows status = failed, error_code = carrier_blocked or Twilio 30007. Same body to other numbers works fine.
Why it happens
The carrier (T-Mobile, Verizon, ATT) has filtered this message based on content or sender reputation. Common triggers: URL shorteners, certain keyword patterns (“FREE”, “winner”), or your sender number being flagged for unrelated traffic.
This is rare for normal transactional templates and unfortunately opaque — carriers don’t tell us exactly why.
How to fix it
- Re-word the template to drop URL shorteners (use the full
https://app.peptideclients.com/…URL instead). - Drop suspicious phrases. Test with a clearly-transactional version first.
- If the carrier block persists across templates, try switching providers for that org. Set the org’s sender to a different number on the alternate provider in Settings → SMS → Sender numbers.
- For sustained carrier issues, write to {{CONTACT_EMAIL}} with the failing message body and the recipient’s carrier (if known).
Reading the diagnostics
Settings → SMS log lists every send attempt in the last 90 days. Columns:
| Column | What to look at |
|---|---|
sent_at | When we enqueued the message. Compare with delivered_at for latency. |
to | Recipient number, normalized to E.164. Confirm the format is right. |
body | First 80 characters of the rendered message. Useful to confirm template variables filled in. |
provider | sendblue (primary) or twilio (fallback). If you see traffic on the fallback, the primary is degraded — check provider status pages. |
status | queued → sent → delivered. Anything else (failed / opted_out / scheduled / suppressed) jumps to one of the sections above. |
error_code | Either our string (invalid_phone, quota_exceeded) or the upstream provider code (Twilio 30006, etc.). |
provider_message_id | Provider’s id. Click to deep-link to SendBlue or Twilio for the full upstream event timeline. |
When to write to support
Email {{CONTACT_EMAIL}} with:
- The recipient number (E.164) and the approximate send timestamp.
- The SMS log row — Copy as text. Includes status, error_code, provider_message_id.
- The sender number the org is configured to use.
- If you have it, a screenshot of the provider dashboard’s detail page for that message id.
- One sentence each on expected vs actual.
Related: SMS feature guide · Billing & quotas · Troubleshooting hub.