Compliance checks
Every message you send, through the API, the console, a broadcast, a
scheduled send, a recurring schedule or an automation, goes through the same
checks in the same order before it reaches a carrier. You call
POST /v1/messages; the checks are on by default.
A send that fails a check is blocked: it is not sent, it is not charged, the API says why, and the attempt is in your log with the reason.
+14155550123 Blocked Quiet hours
+12125550148 Blocked Previously opted out
+13125550190 Delivered
The checks, in order
The first check that fails is the one you hear about.
| # | Check | What it looks at | Returns | failure.code | Runs in |
|---|---|---|---|---|---|
| 1 | Opt-out list | The recipient has opted out of your messages (replied STOP, or was imported or recorded as opted out). One list for the whole account, every number and customer. | 403 recipient_opted_out | opted_out | Live and sandbox. Send to +15005550011, or opt a number out with the consent API. |
| 2 | Destination | The number is in a US or Canadian area code. Other +1 regions are refused. | 403 forbidden | destination_not_allowed | Live |
| 3 | Sender registration | A US local number may text the public only once it is linked to an approved registration. Until then it reaches only your verified numbers. | 403 sender_not_registered | sender_not_registered | Live |
| 4 | Per-recipient rate | No more than 30 messages to one number in an hour. | 429 rate_limited | rate_limited | Live. Send to +15005550012. |
| 5 | Verified recipients (free accounts) | A free account texts only numbers it has verified. | 403 recipient_not_verified | recipient_not_verified | Live |
| 6 | Quiet hours | A marketing message outside 8:00am to 9:00pm in the recipient's local time (read from the area code) is refused, or held until 8:00am when you ask for that. Transactional messages and one-time codes are not checked. | 403 quiet_hours | quiet_hours | Live and sandbox. Send to +15005550015 with category "marketing". |
| 7 | Content screen | Prohibited content and public link shorteners are refused; restricted categories are refused until approved for your account. | 403 forbidden | content_blocked | Live |
| 8 | Message allowance | Your plan's daily and monthly message allowance. | 429 quota_exceeded | quota_exceeded | Live and sandbox |
| 9 | Spend limit | The monthly spending limit you set, if you set one. | 403 spend_limit_reached | spend_limit_reached | Live |
| 10 | Prepaid credits | The balance covers this message's price. | 402 insufficient_credits | insufficient_credits | Live |
After the last check the message goes to the carrier. What a carrier does with it afterwards (filtering, an unreachable phone) is a delivery failure, not a block.
This table is generated from the list the send pipeline is tested against, so it cannot fall out of step with the code.
What a blocked send looks like
The request answers with the status and code in the table, and a failure
object you can show to a person:
{
"error": {
"code": "quiet_hours",
"message": "It is 10:41pm for this recipient (America/New_York, from the number's area code). Marketing messages are sent between 8:00am and 9:00pm in the recipient's local time, so this one was not sent and nothing was charged. ...",
"param": "to",
"recipient_local_time": "22:41",
"timezone": "America/New_York",
"send_after": "2026-10-06T12:00:00.000Z",
"category": "marketing",
"category_basis": "request",
"message_id": "msg_8Fq2ZkT0aLw4nXcV",
"failure": { "code": "quiet_hours", "title": "Held back for quiet hours", "explanation": "...", "action": "Recommended: ...", "carrier_code": null },
"request_id": "req_8Fq2ZkT0aLw4nXcV7pHs"
}
}The attempt is stored as a message with status: "blocked" and the same
failure. Its price is 0, it counts toward no allowance or limit, and
it is not part of the conversation with that number.
- Console. Logs shows the status Blocked and the reason. Filter by Blocked, then by reason. Compliance shows blocked sends for the last 7 and 30 days, by reason.
- API.
GET /v1/messages?status=blockedlists them;&blocked_reason=quiet_hoursnarrows to one reason.failure_codematches failed and blocked messages alike. - Webhook.
message.blocked, withreason,message_idandfailure. - Export. The messages CSV has
reason_codeandreasoncolumns.
Two refusals a looping client can repeat without end (the per-recipient rate and the message allowance) stop adding rows after 500 an hour per account. They are still refused and still counted.
Reason labels
The label is what Logs and the CSV show for each code.
failure.code | Label | Status |
|---|---|---|
carrier_filtered | Filtered as spam | failed |
unreachable | Phone unreachable | failed |
invalid_number | Number not in service | failed |
opted_out | Previously opted out | blocked |
content_blocked | Content policy | blocked |
rate_limited | Sending too fast | blocked |
landline | Number can't receive texts | failed |
carrier_rejected | Rejected by carrier | failed |
spend_limit_reached | Spend limit | blocked |
insufficient_credits | Out of credits | blocked |
sender_not_registered | Sender not registered | blocked |
quiet_hours | Quiet hours | blocked |
recipient_not_verified | Not a verified test recipient | blocked |
quota_exceeded | Quota | blocked |
destination_not_allowed | Destination not served | blocked |
topic_unsubscribed | Unsubscribed from topic | blocked |
unknown | Not delivered | failed |
Quiet hours
Marketing messages are sent between 8:00am and 9:00pm in the recipient's local time. That is the federal calling-time window for telephone solicitations. A marketing message outside it is refused, or held until 8:00am, depending on how it was sent.
Which messages are marketing
Only marketing messages are checked. In order:
- You said so.
"category": "marketing","transactional"or"otp"on the request always wins. - A broadcast or batch is marketing unless it says otherwise.
- A message from a number registered for the Marketing use case is marketing.
- Everything else is transactional and is sent at any hour.
So if you send codes, receipts and alerts today and do nothing, nothing
changes. A number registered as Mixed or Low-volume sends both kinds: label
its marketing sends with "category": "marketing". The category is your
statement about your own traffic. We do not read message text to decide it.
Never checked, whatever the category: verification codes (Verify), opt-out confirmations and HELP replies, a reply to someone who texted that number in the last 4 hours, and sends to your own verified test numbers.
Local time
We read the recipient's time zone from the area code of their number, for every US and Canadian area code. An area code tells us where a number was issued, not where the phone is: someone who moved and kept their number is read in their old time zone. It is the best signal available without location data, and it is an approximation.
- An area code that spans two time zones is treated as quiet when it is quiet in either one.
- A number we cannot place (toll-free, or outside the US and Canada) is not held. Nothing is guessed.
- Daylight time, and the places that do not observe it (Arizona, Hawaii, Saskatchewan), follow the standard time zone database.
Refuse or hold
curl https://api.joinsimplesms.com/v1/messages \
-H "Authorization: Bearer $SIMPLESMS_API_KEY" -H "Content-Type: application/json" \
-d '{"to": "+12125550148", "body": "20% off this week. Reply STOP to opt out.",
"category": "marketing", "quiet_hours": "defer"}'quiet_hours | In quiet hours |
|---|---|
"block" | Refused with 403 quiet_hours. The body has recipient_local_time, timezone and send_after, the next allowed time. |
"defer" | Held. The response is 202 with a scheduled message set for the recipient's next 8:00am, plus a deferred object. It is listed and can be canceled like any scheduled message, and runs every check again when it is sent. |
Without quiet_hours, the default depends on what is sending:
| Sent by | Default |
|---|---|
POST /v1/messages, the console composer | Refuse |
POST /v1/messages with scheduled_at | Refuse when you schedule it, if the scheduled time is in quiet hours |
| Broadcasts and batches | Hold, recipient by recipient |
| Recurring schedules | Hold |
| Automations | Hold |
A broadcast sent at 10:00pm Eastern reaches recipients on the West Coast right away and recipients on the East Coast at 8:00am their time.
A held message that is somehow still in quiet hours when it comes due is held again, at most 3 times, and then blocked.
Account default
Console → Compliance → Quiet hours sets the default for your account:
- Refuse single sends, hold the rest (the default).
- Hold everything until 8:00am, single sends included.
- Off, if you have your own quiet-hours handling. This is never a
default, it takes a second confirmation, and it is recorded in your
audit log as
quiet_hours.updated.
A quiet_hours value on a request always wins over the account default.
Test it
Quiet hours work the same with a test key. Two ways to trigger them:
- Send to
+15005550015with"category": "marketing". For this number it is always night. - Send to any real-looking number with
"category": "marketing": it is read against its area code, as in live mode. Nothing is delivered.
What quiet hours do not do
- They do not apply any state rule. Some states set narrower hours, different weekend hours, or holiday restrictions for solicitations. Only the federal 8:00am to 9:00pm window is applied. If you send into such a state, schedule accordingly.
- They do not know where a phone is, only where its number was issued.
- They do not decide whether a message is marketing. You do.
What is not checked
These are not part of the pipeline. Nothing on this page should be read as covering them.
- Reassigned numbers. We do not check whether a number has changed hands since the person gave you consent. No reassigned-number database is queried.
- Litigator and complainer lists. We do not screen recipients against any list of known litigants or serial complainants.
- Do-not-call registries. We do not check national or state do-not-call lists.
- Consent itself. We store the opt-in evidence you give us and enforce opt-outs, but we cannot verify that a recipient agreed to hear from you. That stays your responsibility.
- State-specific sending hours. Quiet hours use the federal 8:00am to 9:00pm window everywhere. Stricter state windows, weekend hours and holiday rules are not applied.
- The recipient's actual location. Local time is read from the number's area code, which says where the number was issued, not where the phone is.
You remain responsible for having consent for every recipient, for classifying your traffic correctly, and for following the laws that apply to your messages. These checks enforce specific rules by default; they are not legal advice and do not make a messaging program compliant on their own. See the Messaging Policy.