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 DeliveredThe 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 | Account open | The account is open and not suspended. A closed account sends nothing (403 account_closed); a suspended one sends nothing until our team restores it (403 tenant_suspended, recorded as account_suspended). | 403 account_closed | account_closed | Live and sandbox |
| 2 | 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. |
| 3 | Sending not paused | All live sending is paused after three messages blocked as phishing, impersonation or fraud reached two or more people within 7 days, or by our team, until our team lifts it. Keys keep working; a batch or scheduled message waits; your test recipients are exempt. | 403 account_paused | account_paused | Live |
| 4 | Destination | The number is in a US or Canadian area code. Other +1 regions are refused. | 403 forbidden | destination_not_allowed | Live |
| 5 | 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 |
| 6 | Per-recipient rate | No more than 30 messages to one number in an hour. | 429 rate_limited | rate_limited | Live. Send to +15005550012. |
| 7 | Burst to one person | No more than 10 messages from your account to one person in 5 minutes, or 25 while they are texting you back. Your test recipients are exempt. | 429 rate_limited | rate_limited | Live |
| 8 | One person, few numbers | A person your account texted from two of its numbers in the last 24 hours is not texted from a third: carriers treat it as filter evasion. A reply to someone who texted that number is never refused. | 403 sender_rotation | sender_rotation | Live |
| 9 | Verified recipients (free accounts) | A free account texts only numbers it has verified. | 403 recipient_not_verified | recipient_not_verified | Live |
| 10 | Matches the registration's use case | What kind of message this is comes from the sending number's registration and the message's wording, not from the category you send: a category can make a message stricter ("marketing") but never looser. A message outside the registration's use case is refused: promotional wording (an offer, a discount, a promo code, "shop now") on a non-marketing registration, category "marketing" on one, or anything but a one-time code from a two-factor registration. On a mixed or low-volume registration a message is marketing unless it is a one-time code or you sent it with category "transactional" and it has no promotional wording. From a number with no registration, a broadcast, category "marketing" or promotional wording makes it marketing. Then, for a number linked to a registration (not a two-factor one), an automated classifier compares each new kind of message (names, numbers and links masked) with the registration's use case, description and example messages: a kind that clearly doesn't match is refused, promotional content on a mixed or low-volume registration counts as marketing, and a kind it can't place is held for our team (see Review below). It only ever makes a message stricter. The first message of a kind waits for it; later ones reuse the answer. If it can't run, a new kind is not sent (503 screen_unavailable; batches and scheduled messages retry for about 15 minutes). A registration that says its messages contain no phone numbers refuses one with a phone number written in it, such as (415) 555-0132 (not your own test recipients). Links must be on the website domain of the registration (or of a toll-free number's verification), a domain in its filed example messages or links or one our team added, and must not redirect elsewhere (see Links); a registration or verification that declared no links refuses them. Before the content checks, we add your business name to a person's first message and opt-out wording to marketing when they are missing (see What we add). | 403 campaign_mismatch | campaign_mismatch | Live |
| 11 | Quiet hours | Any message outside 8:00am to 9:00pm in the recipient's local time (read from the area code; 8:00pm for Florida, Maryland and Oklahoma numbers, by state law) is held until 8:00am; a single marketing send is refused unless you ask for it to be held. Only one-time codes, replies within 4 hours of the recipient's own text, and your test numbers go out at night. | 403 quiet_hours | quiet_hours | Live and sandbox. Send to +15005550015: marketing is refused, anything else is held. |
| 12 | Opt-in on file | A marketing message goes only to a number with an opt-in on file: one recorded through the consent API, a START reply, or a contact import that marks it opted in. From a number linked to a registration the opt-in must be for that registration (campaign_id), and that holds for every recurring message from it, receipts and alerts included, not only marketing; a non-marketing message can carry the opt-in itself ("consent": {"source", "collected_at"}), and a reply within 4 hours of the recipient's own message needs none. From a number with no registration, an account-level opt-in counts for marketing and other messages are not checked. One-time codes are never checked. Your verified numbers are exempt. | 403 consent_required | consent_required | Live and sandbox. Send to +15005550016 with category "marketing". The other 500-555 numbers count as opted in. |
| 13 | Re-confirmation after 30 days | A marketing message from a number linked to a registration, to a number that has not been messaged from it in 30 days: we text the recipient first to confirm they still want messages, hold this message, and send it by itself if they reply YES within 7 days (reconfirmation_pending). No reply, and the held message is dropped and marked reconfirmation_expired; marketing to that number is refused until they opt in again. Your verified numbers are exempt. Transactional messages and one-time codes are not held. An admin can turn this off, with an acknowledgment that is audit-logged. | 403 reconfirmation_pending | reconfirmation_pending | Live and sandbox. Record an opt-in for a registration (campaign_id) dated more than 30 days ago, then send marketing from a number linked to it. |
| 14 | Registration paused | Marketing from a number whose registration is paused because more than 4% of its recipients opted out in 24 hours (with at least 100 sent), or any message from a number whose registration our team paused for review. Either stays paused until our team resumes it. An opt-out-rate pause does not stop transactional messages; your verified numbers are exempt from both. | 403 campaign_paused | campaign_paused | Live |
| 15 | Content screen | Every live message is checked against the Messaging Policy: our content rules, then a moderation model and a scam classifier, which is told the business name and website on the sending number's registration or toll-free verification so your own alerts pass and imitations of other companies do not. Phishing, impersonation and scams, prohibited content (including everything carriers refuse even with consent, such as cannabis, gambling, payday loans, crypto and stock alerts) and public link shorteners are refused; alcohol is refused until approved for your account. A message the screen could not check is not sent: 503 screen_unavailable with Retry-After (batches and scheduled messages wait and retry for about 15 minutes first). | 403 forbidden | content_blocked | Live |
| 16 | Review of new kinds of message | During an account's first 30 days of live sending, each new kind of message from a number linked to a registration is reviewed by our team before it goes out (messages that differ only in names, numbers, amounts or links are one kind). The first ones are held (held_for_review, nothing charged); once we approve the kind, every held message is sent and later ones go straight out. A kind we reject is refused (review_rejected), and held messages not reviewed within 3 days are dropped (review_expired). A kind the registration check above can't place is held the same way at any age of the account. Your verified numbers and one-time codes from a two-factor registration are exempt. | 403 held_for_review | held_for_review | Live |
| 17 | Daily cap per business | Carriers cap how many messages a business may send a day across all its registered numbers, by its tax ID (EIN): 2,000 a day unless its carrier vetting score allows more. A batch or scheduled message over the cap waits for the next day (UTC); a direct send is refused with a Retry-After. | 429 rate_limited | rate_limited | Live |
| 18 | New-account daily people | In an account's first 30 days live, the different people it texts a day are capped at the larger of 200 and 3x the daily volume it declared. People already texted that day are never refused; a batch or scheduled message waits for the next day (UTC). | 429 rate_limited | rate_limited | Live |
| 19 | State limit on marketing texts | Florida, Maryland and Oklahoma allow 3 marketing texts to one person in 24 hours (read from the area code). A 4th from your account is refused, whatever it is about; a batch or scheduled message waits until it is allowed. Replies within 4 hours of the person's own text and your test numbers don't count. | 429 rate_limited | rate_limited | Live |
| 20 | Registration sending pace | Carriers limit how fast one registration may send: 240 messages a minute across its numbers (75 for low-volume). A batch or scheduled message over the pace waits for the next minute; a direct send is refused with a Retry-After. | 429 rate_limited | rate_limited | Live |
| 21 | Message allowance | Your plan's daily and monthly message allowance. | 429 quota_exceeded | quota_exceeded | Live and sandbox |
| 22 | Spend limit | The monthly spending limit you set, if you set one. | 403 spend_limit_reached | spend_limit_reached | Live |
| 23 | 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). 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 |
campaign_mismatch | Outside registration use case | blocked |
content_blocked | Content policy | blocked |
screen_unavailable | Content check unavailable | 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 |
consent_required | No opt-in on file | blocked |
reconfirmation_pending | Awaiting re-confirmation | blocked |
reconfirmation_expired | Did not re-confirm | blocked |
held_for_review | Waiting for review | blocked |
review_rejected | Not approved in review | blocked |
review_expired | Review timed out | blocked |
campaign_paused | Registration paused | blocked |
sender_rotation | Too many of your numbers | blocked |
recipient_not_verified | Not a verified test recipient | blocked |
quota_exceeded | Quota | blocked |
destination_not_allowed | Destination not served | blocked |
account_closed | Account closed | blocked |
account_suspended | Account suspended | blocked |
account_paused | Sending paused for review | blocked |
topic_unsubscribed | Unsubscribed from topic | blocked |
unknown | Not delivered | failed |
Quiet hours
Every message is sent between 8:00am and 9:00pm in the recipient's local time, and 8:00am to 8:00pm to numbers with a Florida, Maryland or Oklahoma area code. That is the federal calling-time window for telephone solicitations and those states' narrower one, and we apply it to all traffic, not only marketing. A message outside it is held until 8:00am, or refused if it is a single marketing send. This cannot be turned off.
Sent at any hour: one-time codes (judged by the message's wording, not its
category), a reply to someone who texted that number in the last
4 hours, the "Reply YES to confirm" text
a hosted opt-in page sends right after someone signs up, and sends to your
own verified test numbers.
Which messages are marketing
What a message is comes from the sending number's registration and the
message's wording, not from a label. Carriers approve a registration for
one use case and treat anything outside it as a campaign violation, so we
check it before they do (matches the registration's use case).
A "category" on the request can make a message stricter ("marketing"),
never looser:
| Sending number's registration | The message is |
|---|---|
| Two-factor authentication | A one-time code. Anything else is refused (campaign_mismatch) |
| Account, customer care, delivery, fraud and security alerts, higher education, public service, polling | Not marketing. Promotional wording (an offer, a discount, a promo code, "shop now") or "category": "marketing" is refused (campaign_mismatch) |
| Marketing | Marketing, always |
| Mixed, Low-volume | Marketing, unless it is a one-time code, or you sent "category": "transactional" and it has no promotional wording |
| None (the sandbox, a number not yet linked) | Marketing when it is a broadcast or batch, has promotional wording, or you sent "category": "marketing"; otherwise not |
The registration check. For a number linked to a registration (any use case but two-factor), and for a toll-free number verified through the console's form, the first message of each new kind is also compared with the registration or toll-free verification as you filed it (its use case, description and example messages) by an automated classifier. "Kind" means the text with names, numbers, amounts, emails and links masked, so "Your order 1234 shipped" and "Your order 5678 shipped" are one kind. What it can do:
- Refuse a kind that clearly doesn't match the registration, or a
promotional one on a registration that isn't for marketing:
403 campaign_mismatchwithmismatch: "off_use_case"(or"promotional"),checked_by: "consistency_check"and thefingerprint. The message says why. - Count it as marketing when it is promotional on a mixed or low-volume registration, so every marketing check applies.
- Hold it for our team when it can't tell (
403 held_for_review, nothing charged), at any age of the account.
It never makes a message looser than the table above. Its answer is kept per
kind, so later messages of the same kind don't wait for it; editing the
registration checks each kind again. If it can't run, a new kind is not
sent (503 screen_unavailable). Think it got one wrong? Contact support:
our team can approve that kind, and it then goes straight out.
Whether a message is marketing decides one thing for quiet hours: a single marketing send is refused by default, anything else is held.
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 whose area code shows no time zone (toll-free, for example) is held to every time zone in the contiguous US: it is texted between 11:00am and 9:00pm Eastern.
- 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.jointextkit.com/v1/messages \
-H "Authorization: Bearer $TEXTKIT_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, a message that is not marketing is held. For a
marketing message 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. An
automatic carrier retry that would land in quiet hours fails with
quiet_hours instead of going out, and is not charged.
Account default
Console → Compliance → Quiet hours sets the default for your account:
- Refuse single marketing sends, hold the rest (the default).
- Hold everything until 8:00am, single marketing sends included.
There is no off setting. A change 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
+15005550015. For this number it is always night: a marketing message is refused, anything else is held. Only a message that reads as a one-time code goes out. - Send to any real-looking number: it is read against its area code, as in live mode. Nothing is delivered.
What quiet hours do not do
- They apply only Florida's, Maryland's and Oklahoma's 8:00pm end. Other states' narrower hours, weekend hours and holiday restrictions are not 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 may be sent at all. Consent, and the hours that apply where your recipients are, remain yours.
Links
From a number linked to a registration, or a toll-free number verified through our form, a message may link only to:
- the website domain on your registration or verification, and its subdomains;
- a domain in the example messages (or, for toll-free, the links in messages) you filed;
- a domain our team added for the registration at your request (a booking or payment page you use, for example).
Anything else is refused with 403 campaign_mismatch (mismatch:
link_domain), and so is a link that redirects off those domains: we
visit each link once for each kind of message and follow up to five
redirects, without reading the page. We ask with HEAD (GET only if your
site refuses HEAD), so a one-time link is not used up by the check. If the
registration says its messages contain no links, any link is refused
(mismatch: link). A domain made to look like another
(paypa1.com, one letter off your own, an
internationalized look-alike) is refused as phishing (403 forbidden,
failure.code content_blocked). Your own test recipients are exempt.
Public link shorteners are always refused.
What we add
Carriers expect two things in messages from a registered number, and we add them when they are missing instead of refusing the message (from a number linked to a registration; never to a one-time code, or to a reply within 4 hours of a message from the person):
- Your business name on a person's first message from that number. If the text does not name your business (any case or punctuation, with or without "LLC" or "Inc."), we put "Your Business: " in front, once per person and number.
- Opt-out wording on marketing: on the first marketing message to a person, and again if they have not had opt-out wording from that number in 30 days. If the text has none ("Reply STOP…", "Text STOP to end" and the like count), we add " Reply STOP to opt out." at the end.
The message's body is the text as sent, and disclosures_added
(brand, opt_out) says what we added, on the API response, the
message.sent event and in Logs. The added words can make a message one
segment longer, and each segment is billed (rate plus carrier fee).
Content checks run on the text as sent.
A registration also says whether its messages contain phone numbers. If
it says they don't, a message with one written as a phone number (such as
(415) 555-0132 or +1 415 555 0132; a bare run of ten digits, like an order
number, does not count) is refused with 403 campaign_mismatch: remove the
number, or contact support to update the registration.
Opt-in on file
A message goes only to a number with an opt-in on file when it is:
- marketing (which messages are marketing), from any number; or
- a recurring non-marketing message (a receipt, an alert, a reminder) from a number linked to a registration. Carriers expect consent for those too, and ask for proof of it when they audit a campaign.
Without one it is refused with 403 consent_required, stored in your log
as a blocked message with that reason, and not charged. Never checked:
one-time codes, a non-marketing reply within 4 hours of a message from
the recipient, and non-marketing messages from a number with no
registration.
An opt-in is on file when you recorded it, and the number has not opted out since, in any of these ways:
POST /v1/consent/{phone}with"status": "opted_in"(addproof: where and how they agreed, the disclosure they saw; andcampaign_id, the registration they opted in to);- Record opt-in, a console button on the contact's page (or on Add contact, "They agreed to get texts"), or right from Send message when a message is refused for having none. It asks how they agreed, when, the page for a web form and the wording they saw, and records it for your registration with the same rules as the API (an opt-in given out loud is not offered for a marketing registration: those need written consent);
- a contact import row with an
opt_incolumn (and acampaign_idcolumn for the registration); - the person texting START (or another opt-in keyword) to your number, which records an opt-in for that number's registration;
- a sign-up on the registration's hosted opt-in page;
- for a non-marketing message only,
consenton the send itself ({ "source": "checkout", "collected_at": "..." }), which records the opt-in for the sending number's registration when none is on file.
Which opt-in counts depends on the sending number. From a number linked
to a registration, the opt-in must be for that registration: consent is
given to one business for one kind of message, and a reminders opt-in is
not a promotions opt-in. From a number with no registration (the sandbox,
toll-free and Canadian numbers, a number not yet linked), any opt-in on
file with the account counts. The refusal names the registration it looked
for (registration_id) and consent_scope. An opt-out is never narrower
than the account. See consent is per registration
and proof for marketing opt-ins.
Your verified numbers and your own phone are exempt, so you can always try a marketing message on yourself. A broadcast skips each recipient without an opt-in and sends to the rest; the skipped ones appear in its results and in your log.
We check that an opt-in is on file, not that the person really agreed or that the consent covers what you send: that stays yours to hold and produce on request (Messaging Policy).
Re-confirmation after 30 days
Some carriers require that a person who has not heard from a marketing program in 30 days confirms they still want it before the next promotional text. The check runs right after the opt-in check, for a marketing message from a number linked to a registration, when the recipient has not been messaged from that number in 30 days (the opt-in's own date counts as contact):
- The message is held, and one re-confirmation text goes out first in your business's name, with STOP and HELP: "Acme: It has been a while. Reply YES to keep getting texts from Acme. Msg & data rates may apply. Reply STOP to cancel, HELP for help."
- The request answers
403 reconfirmation_pendingwithexpires_atandheld: true; the held message is in your log asblockedwith that reason, andmessage.blockedcarries it. Nothing is charged. - If they reply YES (or Y, OK, SURE, CONFIRM, START, JOIN) within
7 days, the held message is queued and sent by itself through the
whole pipeline, and
message.opted_infires withreconfirmed: trueandreleased_message_ids. Marketing to them resumes. - Other marketing messages to the same number while the question is open
answer
403 reconfirmation_pending(withheld: false): the person is asked once, not once per message. - No reply in 7 days: the held message is dropped, its row in Logs
changes to
reconfirmation_expired(announced again onmessage.blockedwith the samemessage_id), and marketing to that number is refused with403 reconfirmation_expireduntil they opt in again: a YES or START reply, or a new opt-in you record with proof.
Transactional messages, one-time codes, marketing from a number with no registration, and your verified test numbers are never held. A STOP ends the question like any other opt-out.
Turning it off
An admin can turn re-confirmation off on the console Compliance page, after
acknowledging that the account handles it itself; the acknowledgment and the
change are recorded in the audit log as
reconfirmation.updated. It is never off by default.
Test it
In the sandbox, link a test number to a registration with use case
marketing, record an opt-in for that registration with
"collected_at" more than 30 days ago, and send a marketing message to the
number: the request answers reconfirmation_pending and the sandbox
records the re-confirmation text. Simulate a YES with
POST /v1/test/inbound and the held message goes out on the next queue
run.
Review of new kinds of message
During your account's first 30 days of live sending, our team looks at each new kind of message from a number linked to a registration, or a toll-free number verified through the console's form, before it goes out. It runs after the content screen, so we only ever look at text the screen passed.
A kind of message is the text with the parts that change masked: names,
numbers, amounts, dates, codes and email addresses, and links reduced to
their domain. "Hi Sam, order 1234 shipped" and "Hi Alex, order 5678
shipped" are one kind; a broadcast or batch is one kind for all its
recipients, whatever its {{merge}} fields hold.
- The first messages of a new kind are held: the request answers
403 held_for_reviewwithheld: true,fingerprint(the kind's id),registration_idandexpires_at. Each is in your log asblockedwith that reason and announced onmessage.blocked. Nothing is charged. - We aim to review within one business day. Approved: every held
message of that kind is sent by itself through all the checks again
(quiet hours and the opt-out list included), its row in Logs says it was
sent as a new message, and later messages of that kind go straight out.
template.approvedis in your audit log. - Rejected: the held messages are dropped, their rows change to
review_rejected(announced again onmessage.blockedwith the samemessage_id), and later messages of that kind from that registration are refused with403 review_rejected. The reason is in the message, and your audit log records it astemplate.rejected. This applies after your first 30 days too. - Not reviewed within 3 days: the held messages are dropped and their
rows change to
review_expired. Send again: if the kind has been approved since, it goes straight out.
Up to 10,000 messages of one kind are held; past that a send answers
held_for_review with held: false and is not kept, so send it again
once the kind is approved. A broadcast's results count its held messages as
not sent; once approved they go out as individual messages.
Exempt: your verified numbers and your own phone (so you can try anything on yourself first), one-time codes from a two-factor registration, and the confirmation text of a hosted opt-in page. Keyword replies, auto-replies and Verify codes are not held. A quick way through: send each kind of message to yourself, then once to a real recipient, early.
Our team can keep reviewing an account after 30 days, or stop sooner; the
change is in your audit log as template_review.updated.
Sending pace
Carriers limit how fast one registration may send. Every live message from a number linked to a registration counts against the registration's pace, whichever of its numbers sends it:
| Registration | Pace |
|---|---|
| Low-volume | 75 messages a minute |
| Every other use case | 240 messages a minute |
- Batches, broadcasts and scheduled messages over the pace wait for the next minute and go out then. Nothing fails and nothing is charged twice; the batch simply takes longer.
- A direct send (
POST /v1/messages, the console) over the pace is refused with429 rate_limitedand aRetry-After, recorded asblocked, not charged. Send it again, or use a batch. - Carriers also cap how many messages a business may send some of their
subscribers in a day. A message refused for that comes back
failedwithrate_limited: send it again the next day.
These are the carriers' defaults for a new registration; a registration with a higher limit is not paced faster yet.
Daily caps
Two daily limits count people and messages per day (UTC). Over either, a
batch, broadcast or scheduled message waits for the next day and goes out
then; a direct send is refused with 429 rate_limited and a
Retry-After until midnight UTC, recorded as blocked, not charged.
Your verified test recipients are never counted or refused.
| Cap | Limit |
|---|---|
| Per business | Messages from numbers linked to registrations, across every account of a business, by its tax ID (EIN): the carriers' cap of 2,000 a day for a brand without a vetting score. If your brand has a vetting score that allows more, contact us and we raise it. |
| New accounts | In an account's first 30 days live, the different people it texts a day: the larger of 200 and 3x the daily volume declared when it asked for live access. People already texted that day are never refused. Contact us to raise it. |
Per-person limits
- Bursts. No more than 10 messages from your account to one person in
5 minutes, or 25 while they are texting you back (they wrote within
the last hour). Over it:
429 rate_limitedwith aRetry-After. - One person, few numbers. A person your account texted from two of
its numbers in the last 24 hours is not texted from a third:
403 sender_rotation, naming the two numbers to use. Carriers treat one person reached from many numbers as dodging their filters. A reply to someone who texted the number is never refused, and a sender pool (sticky by default) keeps each person on one number. - State limit on marketing. Florida, Maryland and Oklahoma allow 3
marketing texts to one person in 24 hours, read from the area code. A 4th
from your account, whatever it is about, gets
429 rate_limitedwith aRetry-After; a batch, broadcast or scheduled message waits until it is allowed. Replies within 4 hours of the person's own text don't count.
The enforcement ladder
Every registration is judged on its last 7 days: complaints (replies like "spam", "who is this", "never signed up", "reported" or "wrong number", and verified reports from people we texted, see below), the share of its messages carriers filtered, its opt-out rate, strikes, and kinds of message our team rejected. Rates count once it has sent 200 messages in the 7 days.
| Step | What happens | Reached at |
|---|---|---|
| Warning | An email, a compliance.flagged webhook event, a banner in the console | Complaints over 0.1%, filtered over 5%, opt-outs over 0.5%, one strike, or a rejected kind of message |
| Half speed | The registration sends at half its pace and half its business's daily cap; batches simply take longer | Complaints over 0.3%, filtered over 15%, opt-outs over 2%, two strikes or two rejected kinds of message in 7 days |
| Paused | Its numbers send nothing (403 campaign_paused); compliance.paused fires | Complaints over 0.5%, opt-outs over 4%, or one of its numbers filtered on 95% of at least 20 messages over 3 days |
Each step is recorded in your audit log with its reasons.
A step's webhook event (compliance.flagged for a warning or half speed,
compliance.paused when paused, compliance.resumed when our team
lifts it) carries scope ("registration" with registration_id, or
"account"), the level, and the reasons. The automatic pause of
all sending sends compliance.paused with scope: "account".
The console shows a banner with the reasons and an Appeal form (owners
and admins). A registration moves back down only when our team moves it,
with a reason.
Strikes and the automatic pause. A message blocked as phishing,
impersonation or fraud, a kind of message our team rejected, texting people
from a third number on three or more people in a day, or abuse our team
confirms is a strike. Three phishing, impersonation or fraud strikes to two
or more people within 7 days pause all of your account's live sending
(403 account_paused) until our team reviews it. Your keys and data keep
working and batches wait.
Complaints and reports. We read replies for complaint phrases and keep only that a complaint happened, never the reply. A "wrong number" reply also opts that person out. Anyone can report a text at /report; when it matches a text your number sent them in the last 30 days, it counts as a complaint and they are opted out. Their number is never shown to you.
Opt-out rate
Carriers watch how many people opt out of a brand's messages. We count, for each registration over the last 24 hours, the messages carriers accepted from its numbers and the people who opted out by texting them (STOP, or a plain "stop texting me").
| Opt-out rate (24h) | What happens |
|---|---|
| Above 0.5%, with at least 200 sent | A warning: email to your account's owner and admins, a compliance.flagged webhook event, and a review by our team. Nothing is paused. At most once a day per registration |
| Above 4%, with at least 100 sent | Marketing from the registration's numbers is paused: refused with 403 campaign_paused, stored as blocked, not charged. Transactional messages and one-time codes keep going. compliance.paused fires, and the registration shows marketing_paused |
A paused registration stays paused until our team has looked at it with you
and resumes it (compliance.resumed); the 24-hour count then starts again.
Opt-outs you record yourself (the consent API, an import) are not counted.
Your verified numbers are exempt, so you can still test.
A high opt-out rate almost always means some people did not expect the messages: check the opt-in, that every message names your business, and how often you send.
Test it
With a test key, +15005550016 never has an opt-in: send it a marketing
message to see the refusal. The other 500-555 numbers count as opted in. A
real-looking number is checked exactly as in live mode, so record its opt-in
first.
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. For marketing messages we check that an opt-in is on file, and we enforce opt-outs, but we cannot verify that a recipient really agreed to hear from you, or that the consent covers what you send. That stays your responsibility.
- Other state sending rules. Quiet hours use the federal 8:00am to 9:00pm window, and 8:00pm for Florida, Maryland and Oklahoma numbers. Other states' stricter hours, 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.