{
  "layer": "MESSAGE_CONTENT",
  "name": "Message content",
  "url": "https://ekas.io/rules/10dlc/message-content/",
  "updated": "2026-07-25",
  "licence": "CC BY 4.0 — https://creativecommons.org/licenses/by/4.0/",
  "count": 122,
  "rules": [
    {
      "id": "MSG-008",
      "slug": "msg-008",
      "title": "Opt-in confirmation must be 20–255 characters",
      "statement": "The opt-in confirmation must be at least 20 and at most 255 characters.",
      "rationale": "The confirmation has the longest required element list of the three auto-replies — brand, programme, frequency, rates, HELP and STOP — so it is the one most likely to overrun. Truncation drops the tail, and the tail is where STOP lives.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bird",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Compress to 255 characters while keeping all six required elements. Abbreviate \"Msg & data rates may apply\" rather than dropping it.",
      "example": "Acme Coffee: you're subscribed to offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "catalogIds": [
        "CMP-007"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-009",
      "slug": "msg-009",
      "title": "HELP reply must be 20–255 characters",
      "statement": "The HELP response must be at least 20 and at most 255 characters.",
      "rationale": "Below 20 characters the reply cannot carry the brand name and a contact route, which are the two things it exists to deliver. Above 255 it exceeds the portable ceiling and is truncated at some providers — losing the tail, which is usually where the opt-out instruction sits.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bird",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Fit the HELP reply into 255 characters carrying the brand name, a real support contact, the rates notice and the opt-out instruction. Drop marketing language before dropping any of those.",
      "example": "Acme Coffee: for help email support@acmecoffee.com or call 415-555-0134. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-011",
      "slug": "msg-011",
      "title": "Opt-out confirmation must be 20–255 characters",
      "statement": "The STOP confirmation must be at least 20 and at most 255 characters.",
      "rationale": "This is the last message you are permitted to send, so it must fit in one segment and say everything it needs to in one pass. A truncated opt-out confirmation can lose the acknowledgement itself, leaving the consumer unsure whether the unsubscribe worked.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bird",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Fit the confirmation into 255 characters: brand name, the acknowledgement, and that no further messages will be sent.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-015",
      "slug": "msg-015",
      "title": "A message must not render as ten or more segments",
      "statement": "No sample message may be long enough to render as 10 or more SMS segments.",
      "rationale": "Every segment is billed and delivered separately, and long concatenated messages are reassembled unreliably across carriers — so a ten-segment message costs ten times as much and is the most likely thing in your programme to arrive broken. Reviewers read extreme length as a sender who does not understand the channel.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Shorten the message and move detail behind a link on your own domain. Aim for one or two segments — 160 GSM characters per segment, or 70 if the message contains any non-GSM character.",
      "example": "Acme Coffee: your order is ready. Details: https://acmecoffee.com/o/4471 Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-018",
      "slug": "msg-018",
      "title": "An MMS campaign must attach at least one sample media file",
      "statement": "A campaign that requests MMS capability must supply at least one sample media file alongside the sample message text.",
      "rationale": "MMS is approved on what the recipient will actually see, and for a picture message that is the picture — a reviewer who is shown only the caption has no way to tell a product photo from something that would fail SHAFT screening on sight. Businesses miss this because the sample text field accepts their submission happily and nothing on the form says the attachment is a separate requirement, so the rejection arrives for a field they never noticed was empty.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.mms_sample_media",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "MISSING_CONDITIONAL_FIELD",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Attach the actual image or video you will send to the campaign's MMS sample media, one file per sample message that carries media. Done when every MMS sample has a file next to its text, not just a caption describing one.",
      "example": "mms_sample_media: acme-summer-sale.png (image/png, 214 KB) — the same artwork the weekend promotion will send.",
      "pitfalls": [
        "Turning MMS on and leaving the attachments for later is the usual cause: the capability flag and the sample media are separate fields, and the flag is what puts you under this requirement."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-019",
      "slug": "msg-019",
      "title": "Each MMS sample file must sit under its per-type size ceiling",
      "statement": "Every attached MMS sample file must be at most 500 KB for GIF, JPEG and PNG images, and at most 600 KB for any other media type.",
      "rationale": "The ceiling is the handset's, not the reviewer's: an oversized MMS is transcoded or dropped somewhere in the carrier chain, so the recipient gets a broken message and the sender never learns why. Registration rejects it up front instead. This catches out businesses who attach the print-resolution asset their designer handed over, because nothing about a 3 MB PNG looks wrong until it is measured.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.mms_sample_media",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "INVALID_FIELD_VALUE",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Re-export each attachment under its ceiling — 500 KB for GIF, JPEG and PNG, 600 KB for everything else — and re-upload. Resizing to 1200 px on the long edge and exporting at 80% JPEG quality clears it for almost any promotional image. Done when every file in the sample media list reports a size below its own limit.",
      "example": "acme-summer-sale.png re-exported at 1200x628 → 214 KB (limit 500 KB for PNG).",
      "pitfalls": [
        "The tighter 500 KB bound is keyed on the file type, not on it being an image: a WebP or a short MP4 gets the 600 KB allowance, a PNG does not."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-020",
      "slug": "msg-020",
      "title": "MMS sample images must not be placeholder or stock artwork",
      "statement": "The attached MMS sample media must show the artwork the campaign will actually send, not a placeholder graphic or an unlicensed stock image standing in for it.",
      "rationale": "The picture is how a reviewer decides whether an MMS programme is what it claims to be, so a grey \"image goes here\" block or a generic stock photo leaves them judging the campaign on the caption alone — and campaigns that register with filler artwork are the ones that later send something else. The honest cause is usually sequencing: the artwork is still with the designer when the registration goes in, and a stand-in feels harmless.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.mms_sample_media",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "NON_COMPLIANT_MESSAGE_SAMPLES",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Replace each attachment with the finished creative for that message, carrying your own branding and your own products. Done when someone who has never seen your business could name it from the image alone.",
      "example": "acme-summer-sale.png: the Acme Coffee wordmark over a photo of the espresso blend on offer, with \"20% off this weekend\" set in the brand typeface.",
      "pitfalls": [
        "A licensed stock photo is still a fail here when nothing in the frame identifies the sender — the objection is anonymity, not the licence."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-026",
      "slug": "msg-026",
      "title": "Samples must not contain mustache or curly-brace template tokens",
      "statement": "Curly-brace and mustache tokens such as {order_id} or {{name}} must not appear in sample messages.",
      "rationale": "Square brackets are the convention reviewers read as \"this varies at send time\"; curly braces read as an unrendered template that leaked into the registration. The distinction is arbitrary but it is the one applied, and the fix costs nothing.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Convert every curly-brace or mustache token to square-bracket notation with a readable name.",
      "example": "Acme Coffee: hi [first name], order [order number] ships today. Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-031",
      "slug": "msg-031",
      "title": "Sample messages must be in English",
      "statement": "Sample messages must be written in English, or an English translation supplied alongside.",
      "rationale": "US reviewers assess against US requirements in English, and a sample they cannot read cannot be checked for the disclosures and content rules everything else depends on. Sending in other languages is fine — the registration artifact needs to be readable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Supply the samples in English. Where the programme genuinely sends in another language, include both the original and an English translation in the sample.",
      "example": "Acme Coffee: su pedido está listo. / Acme Coffee: your order is ready. Reply STOP to opt out.",
      "catalogIds": [
        "CMP-025"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-034",
      "slug": "msg-034",
      "title": "Samples must not be framed as test, demo or machine-to-machine traffic",
      "statement": "Sample messages and the campaign description must describe real messages sent to real subscribers, not test traffic, demonstrations, or machine-to-machine signalling.",
      "rationale": "10DLC is a production registration: the throughput and the trust that come with it are granted on the basis that a person receives each message and consented to it, so a campaign that says it is for testing is asking for a production route to carry something that is not production traffic. Developers land here honestly, registering during integration with exactly the samples their test harness sends — and reviewers cannot tell that from a sender who intends to keep the campaign in test framing indefinitely, so they refuse it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "CAMPAIGN_NON_COMPLIANT_CONTENT_NON_SUBSCRIBER",
          "remediable": true
        },
        {
          "provider": "AWS",
          "code": "NON_COMPLIANT_USE_CASE",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the samples with the messages the campaign will send once it is live, and rewrite the description so it names the real audience and what they will receive. Done when nothing in the submission would still be true if the word \"test\" were the point — a reviewer should not be able to tell you registered during integration.",
      "example": "Acme Coffee: your order #4471 is ready for pickup at our Mission St store until 6pm today. Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "Registering a throwaway campaign to test the integration and a real one later does not work either — the test campaign is refused on this rule, so the integration has nothing to test against."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-038",
      "slug": "msg-038",
      "title": "A registered domain can substitute for the brand name in a sample",
      "statement": "Where a sample does not carry the brand name, the brand's registered domain appearing in the message can satisfy sender identification.",
      "rationale": "Sender identification exists so the recipient knows who is texting them, and a link to acmecoffee.com does that as well as the words \"Acme Coffee\". Recording this explicitly prevents the brand-name check from producing false failures on messages that are perfectly identifiable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Prefer the brand name at the start of the message. Where space is genuinely tight, a link to the registered domain identifies the sender acceptably.",
      "example": "Order ready for pickup: https://acmecoffee.com/o/4471 Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-042",
      "slug": "msg-042",
      "title": "Every message must identify who is sending it",
      "statement": "The message body must identify the entity on whose behalf it is sent, and where relevant give a contact route.",
      "rationale": "Sender identification is the foundational content requirement: a consumer receiving a text from an unknown number has no way to distinguish a legitimate business from a scam. Everything else in the framework assumes the recipient can tell who is texting them.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "T-Mobile",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Lead every message with the brand name. Where the message is sent on behalf of another entity, name that entity rather than the platform.",
      "example": "Acme Coffee: your order #4471 is ready for pickup. Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-045",
      "slug": "msg-045",
      "title": "Samples must not impersonate another organisation",
      "statement": "Sample messages must not impersonate or misrepresent another business, bank, government agency, or technology company.",
      "rationale": "Impersonation is a Severity-0 finding that stops all messaging on the programme, and it attracts per-message violation fees rather than a rejection you can fix. It is also the category carriers escalate fastest, because it maps directly onto fraud.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR",
        "FTC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any content implying you are, or are authorised by, another organisation. Send only under your own registered brand identity.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-046",
      "slug": "msg-046",
      "title": "Samples must be consistent with the brand website",
      "statement": "Sample messages must describe a business consistent with what the brand website shows.",
      "rationale": "Reviewers read the samples with the website open, and a mismatch is the signature of a registration assembled from someone else's business. It is also how an undeclared use case gets discovered.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + brand website",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the samples to describe the products, services and locations the website actually shows.",
      "example": "Website sells coffee subscriptions → samples reference orders, roasts and store events.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-049",
      "slug": "msg-049",
      "title": "Opt-out instructions must name a recognised keyword",
      "statement": "Opt-out instructions in message bodies must use a widely accepted keyword, not an invented alternative.",
      "rationale": "Handsets and carrier platforms recognise a fixed keyword set, so an instruction like \"text 2 to opt out\" produces a reply nothing acts on — the consumer believes they have unsubscribed and keeps receiving messages. That is the exact scenario that turns into a complaint.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use STOP in message bodies. Reserve other keywords for the declared keyword list rather than advertising them as the primary route.",
      "example": "Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "\"Reply 2 to unsubscribe\" and \"text QUITNOW\" both fail — handsets do not route them."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-054",
      "slug": "msg-054",
      "title": "Universal opt-out keywords must be supported in copy and configuration",
      "statement": "STOP, END, CANCEL, UNSUBSCRIBE and QUIT must all be supported, both in the keyword configuration and honoured in practice.",
      "rationale": "These five are recognised at handset and carrier level regardless of configuration, so a consumer using any of them has effectively opted out whether your platform noticed or not. Declaring them aligns your suppression list with what has already happened.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add all five universal keywords to the configuration, ideally alongside REVOKE and OPTOUT.",
      "example": "STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-056",
      "slug": "msg-056",
      "title": "A no-reply route must disclose that replies are not received",
      "statement": "Where the sending route cannot receive inbound texts, every message must clearly disclose that replies are not monitored.",
      "rationale": "Telling a consumer to \"reply STOP\" on a route that discards inbound messages is worse than saying nothing: they believe they have opted out and keep receiving messages. If replies genuinely cannot be received, the opt-out route must be stated some other way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a route that receives replies. If you genuinely cannot, disclose it in every message and give a working alternative opt-out route.",
      "example": "Acme Coffee: your order is ready. This number does not receive replies — manage texts at https://acmecoffee.com/sms-preferences",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-058",
      "slug": "msg-058",
      "title": "A recurring programme must have an opt-in confirmation",
      "statement": "Every recurring message programme must send a confirmation message when a consumer opts in.",
      "rationale": "The confirmation is the consumer's receipt: it tells them the opt-in worked, what they joined, and how to leave. Without one, the first message they receive is marketing from an unfamiliar number, which is the single most reliable way to generate a spam report.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Configure an opt-in confirmation carrying the brand, programme name, frequency, rates notice, HELP and STOP.",
      "example": "Acme Coffee: you're subscribed to offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-060",
      "slug": "msg-060",
      "title": "The opt-in confirmation must be the first message sent",
      "statement": "The confirmation must precede any other messaging to a newly opted-in consumer.",
      "rationale": "Leading with marketing to someone who has just given a number, before confirming what they joined, inverts the relationship the confirmation exists to establish. CTIA sequences it deliberately: acknowledge first, then send.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.message_flow + campaign.optin_message",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Sequence the confirmation ahead of any campaign content in your automation, and describe that ordering in the message flow.",
      "example": "On opt-in we immediately send the confirmation; campaign content begins with the next scheduled send.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-061",
      "slug": "msg-061",
      "title": "The opt-in confirmation must name the registered brand",
      "statement": "The opt-in confirmation message must contain the registered brand name or DBA, matching the brand record.",
      "rationale": "The confirmation is the first message the programme ever sends, and it arrives at a moment when the recipient has typed a number into a form and may not remember which one. If it does not say who it is from, the rational response is to treat it as spam and report it — which is why a nameless confirmation produces complaints out of all proportion to its volume. Almost every provider checks this field specifically, and it is missed because the confirmation is usually written last, as a technical acknowledgement rather than as a message to a person.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message + brand.display_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "AWS",
        "Sinch",
        "Infobip",
        "Bird",
        "TCR",
        "Aerialink",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "5104",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30927",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "AWS",
          "code": "OPT_IN_MESSAGE_MISMATCH",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the registered brand name at the start of the opt-in confirmation, before anything else — use the name on the brand record rather than a programme nickname a reviewer cannot match to it. Done when someone who has forgotten signing up could tell from the first three words who is texting them.",
      "example": "optin_message: \"Acme Coffee Rewards: you are subscribed to offers and rewards updates. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.\"",
      "pitfalls": [
        "A programme name is not the brand name unless the brand name is inside it — \"Rewards Club\" fails where \"Acme Coffee Rewards\" passes, because the reviewer is matching against the registration."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-062",
      "slug": "msg-062",
      "title": "Opt-in confirmation must name the programme",
      "statement": "The confirmation must name the programme or describe the product, so the consumer knows which subscription they joined.",
      "rationale": "A business can run several programmes on one number, and \"you are subscribed\" tells the consumer nothing about which. Naming it is what lets them recognise the later messages as expected rather than unsolicited.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the specific programme in the confirmation, not just the brand.",
      "example": "Acme Coffee Rewards: you're subscribed to weekly offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-063",
      "slug": "msg-063",
      "title": "Opt-in confirmation must disclose message frequency",
      "statement": "The confirmation must state how often messages will arrive.",
      "rationale": "CTIA requires frequency in the confirmation specifically, and it is the disclosure most often present at the web opt-in and missing from the confirmation. It sets the expectation that determines whether the third message of the week reads as service or spam.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a frequency statement, matching whatever you promised at the opt-in.",
      "example": "Msg frequency varies.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-064",
      "slug": "msg-064",
      "title": "Opt-in confirmation must carry the rates disclosure",
      "statement": "The confirmation must state that message and data rates may apply.",
      "rationale": "The consumer may be billed per message by their carrier, and the confirmation is the first point at which they could act on that knowledge. It is a mechanically-checked element, so its absence is caught reliably.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Msg & data rates may apply.\" to the confirmation. Keep it even when tight on characters — it is mechanically checked, and dropping it is a certain finding while shortening the marketing copy is free.",
      "example": "Msg & data rates may apply.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-065",
      "slug": "msg-065",
      "title": "Opt-in confirmation must carry HELP instructions",
      "statement": "The confirmation must tell the consumer how to get help or give a support contact.",
      "rationale": "The confirmation arrives from a number the consumer has never seen before, so it is exactly the moment they may want to check who is texting them. Giving the HELP route here closes that loop before it becomes a complaint.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply HELP for help\" or a direct support contact to the confirmation.",
      "example": "Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-066",
      "slug": "msg-066",
      "title": "Opt-in confirmation must carry a working opt-out instruction",
      "statement": "The confirmation must include STOP instructions echoing a keyword the programme honours.",
      "rationale": "A consumer who realises immediately that they did not mean to subscribe should be able to leave from the first message. The keyword named must be one the platform actually honours, or the instruction is worse than useless.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message + campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to cancel\" and confirm STOP is in your declared opt-out keyword set.",
      "example": "Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-073",
      "slug": "msg-073",
      "title": "HELP response must name the brand or programme",
      "statement": "The HELP reply must contain the registered brand or programme name.",
      "rationale": "HELP exists so a recipient who does not recognise a number can find out who is texting them. A reply that omits the name answers the wrong question and leaves reporting the message as spam as the only remaining option.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Start the HELP reply with the brand or programme name, using the name the consumer would recognise from the opt-in rather than the legal entity name.",
      "example": "Acme Coffee Rewards: for help email support@acmecoffee.com. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-074",
      "slug": "msg-074",
      "title": "HELP response must give a real support contact",
      "statement": "The HELP reply must contain at least one support contact — a phone number, an email address, or a support URL.",
      "rationale": "CTIA requires help routes not to be dead ends. A reply naming the brand but offering no way to reach it satisfies the letter of \"respond to HELP\" while failing its entire purpose, and it is a named Severity-2 audit finding.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a monitored support email, a phone number that is answered, or a support page URL on the brand domain.",
      "example": "Acme Coffee: for help email support@acmecoffee.com or call 415-555-0134. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-075",
      "slug": "msg-075",
      "title": "HELP reply support contact must be on the brand domain",
      "statement": "The support email given in the HELP response must be on the registered brand domain.",
      "rationale": "A HELP reply pointing at a domain unrelated to the brand looks like a redirection to a third party, which is precisely what reviewers screen for. It also breaks the CTIA requirement that help routes actually reach the sender rather than a dead end.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message + brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a support address on the brand's own domain in the HELP reply, matching the brand website.",
      "example": "Acme Coffee: for help email support@acmecoffee.com. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-078",
      "slug": "msg-078",
      "title": "HELP must be a configured help keyword",
      "statement": "The literal keyword HELP must appear in the configured help keyword set.",
      "rationale": "HELP is the universal keyword handsets and consumers expect, and it must produce a response whether or not the requester is subscribed. Configuring only a custom alternative like INFO leaves the standard route silent.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add HELP to campaign.help_keywords, alongside any custom alternatives you support. Done when texting the bare word HELP produces the help reply for a subscriber and a non-subscriber alike.",
      "example": "help_keywords: HELP, INFO, SUPPORT",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-081",
      "slug": "msg-081",
      "title": "STOP confirmation must name the brand or programme",
      "statement": "The opt-out confirmation must contain the registered brand or programme name.",
      "rationale": "A consumer who unsubscribes from several programmes needs to know which one just stopped. An anonymous confirmation leaves them unsure whether the right subscription ended, which produces a second opt-out attempt or a complaint.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Start the opt-out confirmation with the brand or programme name, so a consumer unsubscribing from several programmes knows which one just ended.",
      "example": "Acme Coffee Rewards: you have been unsubscribed and will receive no further messages.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-082",
      "slug": "msg-082",
      "title": "STOP confirmation must acknowledge the request",
      "statement": "The confirmation must acknowledge that the opt-out request was received and acted on.",
      "rationale": "Without an explicit acknowledgement the consumer cannot tell whether their STOP registered, and the rational response to that uncertainty is to complain to the carrier. The acknowledgement is what closes the interaction.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State plainly that the consumer has been unsubscribed, in the past tense so it reads as a completed action rather than a request received.",
      "example": "Acme Coffee: you have been unsubscribed.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-083",
      "slug": "msg-083",
      "title": "STOP confirmation must state no further messages will be sent",
      "statement": "The confirmation must state explicitly that the consumer will receive no further messages.",
      "rationale": "Acknowledging the request and promising the consequence are different statements, and CTIA requires both. \"We got your request\" without \"you will receive no more messages\" leaves open whether anything actually changes.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the forward-looking statement that no further messages will be sent, alongside the acknowledgement — CTIA requires both, and an acknowledgement alone leaves open whether anything changed.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-088",
      "slug": "msg-088",
      "title": "Public URL shorteners are prohibited in every registration field",
      "statement": "No public URL shortener may appear in the description, message flow, samples, HELP reply, or either confirmation message.",
      "rationale": "Shorteners hide the destination, so carriers cannot assess where a message leads and block them categorically. Tools typically check only the sample messages, but the prohibition covers every field — a shortener in the HELP reply or the opt-in confirmation is rejected exactly the same way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.* (every text field)",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30892",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30963",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "8201",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace every shortened link with the full URL on your own domain. Note the two Twilio generations disagree on whether this is fixable — assume the strict reading, where it is not resubmittable.",
      "example": "https://acmecoffee.com/sale rather than https://bit.ly/3xY2z",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-095",
      "slug": "msg-095",
      "title": "Any shortener used must be dedicated to this sender",
      "statement": "Where a short domain is used, it must be dedicated to the exclusive use of the message sender.",
      "rationale": "The published standard is stricter than \"avoid bit.ly\": CTIA requires the shortener to have a web address and IP addresses dedicated to the sender exclusively. A branded short domain running on shared redirect infrastructure still fails, which surprises brands who did the obvious thing and bought a vanity domain.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a short domain you own on infrastructure only you use, or skip shortening and use full URLs on your primary domain — which sidesteps the requirement entirely.",
      "example": "https://acme.link/sale where acme.link is yours alone — or simply https://acmecoffee.com/sale",
      "pitfalls": [
        "A vanity domain on a shared shortener service does not satisfy the dedicated-infrastructure rule."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-098",
      "slug": "msg-098",
      "title": "Links must not pass through multi-hop redirects",
      "statement": "A URL in message content must not chain through multiple redirects that hide the eventual destination.",
      "rationale": "Redirect chains defeat the transparency the URL rules exist to create — the consumer, and the carrier, cannot know where the link ends up. It is also the standard technique for laundering a blocked destination through a clean-looking first hop.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] link destinations",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the final destination directly. Where click tracking is required, use a first-party tracking path on your own domain that redirects once.",
      "example": "https://acmecoffee.com/sale (direct) rather than a tracking chain through three third-party hosts.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-101",
      "slug": "msg-101",
      "title": "Links must not be intended to deceive or cause harm",
      "statement": "Embedded links must not be designed to mislead the recipient or deliver harm.",
      "rationale": "Phishing and malware delivery are Severity-0 findings carrying \"cease all messaging\", and the audit scope explicitly includes the landing page and any downloaded artifact — not just the SMS body. This is judged on intent and presentation, not on the URL string.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + link destinations",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any link whose presentation misrepresents where it leads or what it does. Link text and destination must match.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-103",
      "slug": "msg-103",
      "title": "The page a sample links to is screened as part of the campaign",
      "statement": "The landing page reached from a link in a sample message must be free of prohibited content, and is screened whether or not it is on the registered brand domain.",
      "rationale": "A message is three words and a link, so the link is where the content actually lives — which is why Twilio's prohibited-content scope covers \"campaign descriptions, sample messages, website content, and linked URLs\" rather than stopping at the registered domain. The gap this closes is real and routinely exploited: a clean sample pointing at a dispensary menu, a sportsbook offer, or an affiliate page passes every check that reads text fields. It also catches honest senders, because a campaign link often lands on a partner or marketplace page that the brand does not control and has never read as a compliance artifact.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] URL -> landing page",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open every URL that appears in a sample and read the page a reviewer will land on, including anything it redirects to. Where the destination carries prohibited content, point the message at a page you control that does not — and if the prohibited content is the product, the campaign is not registrable on it. Done when every linked destination could be shown to a reviewer without explanation.",
      "pitfalls": [
        "Screening only the registered brand website misses this entirely: the finding is about where the message goes, and campaigns frequently link somewhere the brand record never mentions."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-104",
      "slug": "msg-104",
      "title": "Link destinations must identify their owner",
      "statement": "A landing page linked from a message must unambiguously identify the site owner as a person or registered business, with contact information.",
      "rationale": "CTIA requires the message URL and every site it redirects to to identify the owner and carry contact details such as a postal address. This is a second, independent requirement beyond not using a shortener, and almost no tooling checks it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] link destinations",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Ensure every page linked from a message names the business and carries contact information, including a postal address in the footer.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-105",
      "slug": "msg-105",
      "title": "The advertised offer must match the landing page",
      "statement": "The offer described in a sample message must be the offer a recipient finds on the page the message links to.",
      "rationale": "Bait-and-switch is the complaint consumers actually make — they were told about a discount, clicked, and found a signup wall or a different price — and it is one of the named triggers for Twilio's deceptive-marketing code, which is not eligible for resubmission. Honest campaigns drift into it through timing rather than intent: the sample was written against last quarter's promotion and the page has moved on, or the deep link rots and lands on a generic homepage where the advertised offer is nowhere in sight.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] <-> landing page",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite each sample so it describes what is genuinely on the page it links to, or point the link at the page that carries the offer. Deep-link to the offer rather than to the site root, so the recipient sees the thing they were promised without hunting for it. Done when the headline on the landing page and the offer in the message say the same number.",
      "example": "Acme Coffee: 20% off all espresso blends this weekend. Shop https://acmecoffee.com/sale — and the page at /sale leads with \"20% off all espresso blends, this weekend only\". Reply STOP to opt out.",
      "pitfalls": [
        "A page that carries the offer somewhere below three screens of other content still reads as a switch — reviewers judge what is visible on arrival."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-106",
      "slug": "msg-106",
      "title": "Link domains must not appear in threat intelligence",
      "statement": "Domains appearing in message content must not be flagged by threat-intelligence sources.",
      "rationale": "Carriers screen destinations against reputation databases, so a link to a compromised or previously-abused domain gets the campaign filtered regardless of how clean the registration is. Shared hosting and recycled domains are the usual innocent cause.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] link domains",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check every domain you link against Google Safe Browsing and VirusTotal before registering, and move off any flagged host. A previously-abused domain will not clear simply by waiting.",
      "notes": "Needs a reputation lookup the product does not have; surfaced as a warning with the check the user should run themselves.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is every domain you link to in these messages clean on Google Safe Browsing and VirusTotal today?",
        "howToCheck": [
          "List the domains in the sample messages, including link-shortener destinations.",
          "Check each against Google Safe Browsing and VirusTotal.",
          "Move off any flagged host — a previously abused domain will not clear simply by waiting."
        ],
        "failureLooksLike": "A recycled domain or a shared host carries somebody else's history into your campaign, and the traffic is filtered regardless of how clean the registration is."
      }
    },
    {
      "id": "MSG-116",
      "slug": "msg-116",
      "title": "embeddedPhone must ignore the HELP support number",
      "statement": "The embeddedPhone attribute should be set when a sample contains a phone number other than the support number given in the HELP reply.",
      "rationale": "Publishing a support number in a HELP response is required, so counting it as an embedded phone number forces a true declaration that misrepresents the campaign. The attribute is meant to flag numbers inside marketing content, which is a redirection signal, not a support contact.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.embedded_phone + campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the embedded-phone attribute based on numbers in campaign content, excluding the support number you publish in the HELP reply.",
      "example": "Samples contain no numbers other than the HELP support line → leave \"messages contain phone numbers\" false.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-117",
      "slug": "msg-117",
      "title": "A sample must contain a phone number when embeddedPhone is declared",
      "statement": "When embeddedPhone is true, at least one sample must contain a phone number.",
      "rationale": "Mirror of the link rule: the attribute drives filtering for numbers inside message bodies, which is a redirection signal. Declaring it with no example leaves the reviewer unable to see what kind of number appears and why.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.embedded_phone + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Include a representative sample containing the number, or set the attribute false. Remember a HELP-reply support number does not by itself require the attribute.",
      "example": "Acme Coffee: questions about your order? Call 415-555-0134. Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-119",
      "slug": "msg-119",
      "title": "A sample must contain a URL when embeddedLink is declared",
      "statement": "When embeddedLink is true, at least one sample message must actually contain a URL.",
      "rationale": "The attribute tells carriers to apply link-specific filtering, so declaring it without demonstrating a link means the campaign is registered for treatment it never exercises — and a reviewer reads the mismatch as a careless or copied registration.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.embedded_link + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either include a representative link in a sample, or set the embedded-link attribute false if the programme genuinely sends no links.",
      "example": "Acme Coffee: 20% off this weekend. Shop https://acmecoffee.com/sale Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-120",
      "slug": "msg-120",
      "title": "Declared attributes must match sample content generally",
      "statement": "Every declared campaign attribute must be consistent with what the sample messages actually contain.",
      "rationale": "Attributes are self-declared and drive filtering, so a mismatch either over-restricts your own traffic or under-declares something carriers screen for. The link and phone pairs are checked individually; this catches the rest as a set.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign attributes + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Walk each attribute against the samples and correct whichever side is wrong.",
      "example": "Samples mention financing → direct lending: true.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-123",
      "slug": "msg-123",
      "title": "Age-restricted content requires the age-gated attribute",
      "statement": "Where sample or description content covers alcohol, firearms, or tobacco, the age-gated attribute must be set true.",
      "rationale": "The attribute is what tells carriers an age gate is in place, and the underlying categories carry per-message violation fees rather than rejections. Declaring it is the difference between a restricted campaign and an enforcement event.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.age_gated + campaign content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "CTIA",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the age-gated attribute true and implement real date-of-birth verification at opt-in. Note several providers refuse these categories entirely even with a gate.",
      "example": "Content references alcohol → age gated: true, with DOB collected at opt-in.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-131",
      "slug": "msg-131",
      "title": "Non-GSM characters silently force Unicode encoding",
      "statement": "Characters outside the GSM-7 alphabet — smart quotes, ™, ®, em dashes, emoji — cut the per-segment limit from 160 to 70.",
      "rationale": "A curly apostrophe pasted from a word processor more than doubles the cost of every message and can split a one-segment message into three, and nothing in the authoring flow warns you. The character is visually identical to the straight version, so this is almost impossible to spot by reading.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "LOW",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace smart quotes with straight quotes, em dashes with hyphens, and remove ™, ® and emoji from message bodies. Retype rather than pasting from a word processor.",
      "example": "Acme Coffee: you're subscribed — replace the curly apostrophe with a straight one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-137",
      "slug": "msg-137",
      "title": "No unsolicited bulk messaging",
      "statement": "Campaign content must not promote unsolicited bulk messaging, whatever the content of it is.",
      "rationale": "Spam is a category in its own right in the T-Mobile code of conduct and the CTIA principles, independent of whether the content would otherwise be allowed — a perfectly ordinary discount offer sent to people who did not ask for it is refused on the sending pattern alone. This matters because it is the one prohibition a business cannot read off its own message copy: the campaign looks fine and the defect is in where the list came from and how the traffic is shaped. It also carries runtime consequences rather than only registration ones, since carrier filters score volume and complaint rate continuously.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30007",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "4470",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Say in the message flow where each recipient's consent came from, in a way that names the surface and the wording they saw, and drop any part of the list you cannot trace to one. If the answer is that the list was acquired, the campaign is not registrable on that list — this is not a copy problem and rewriting the samples will not move it.",
      "pitfalls": [
        "A clean opt-in on the front of the list does not cover the back of it: reviewers ask about the whole audience, and an imported segment inside an otherwise organic list is what the finding is usually about."
      ],
      "notes": "Runtime enforcement continues after registration — Bandwidth 4470 is a billable per-message spam rejection and Twilio 30007 is carrier filtering — so passing this rule at registration does not settle it for the life of the campaign.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-142",
      "slug": "msg-142",
      "title": "The description must match the sample messages",
      "statement": "The campaign description must describe the messages the samples actually show.",
      "rationale": "The description is the claim and the samples are the evidence, so reviewers read them against each other first. A description promising appointment reminders beside samples full of promotions is the clearest possible signal that one of the two is untrue.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite whichever is wrong so the description enumerates the message types the samples demonstrate, and the samples cover the types the description promises.",
      "example": "Description mentions promotions, order updates and event invites → one sample of each.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-148",
      "slug": "msg-148",
      "title": "Samples must match what the printed or in-person call to action promised",
      "statement": "Where consent is collected on paper, at a point of sale, by QR code or by keyword, the samples must match the programme the printed call to action described.",
      "rationale": "An offline call to action is the whole of the consent record — the consumer never saw a web page, so the poster, the form or the counter card is the only thing they agreed to. When the samples describe a different programme there is nothing to fall back on, which is why reviewers compare the two directly and why the mismatch is fatal rather than fixable by adding a disclosure. It happens because the print run is long-lived: the card was designed for appointment reminders two years ago and the programme now sends offers, and nobody reprints a counter card to match a campaign registration.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] <-> offline CTA artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "pos",
          "qr",
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, point of sale, QR code and text-to-join keyword.",
      "universal": false,
      "remediation": "Read the artifact you uploaded as evidence and register the samples that match it. Where the programme has genuinely moved on, the artifact is the thing that has to change — reprint the card or the poster with the current programme described, collect consent against that, and upload the new version. Done when the content types in the samples all appear in the printed call to action.",
      "example": "Poster: \"Text SHELTER to 55512 for Riverside Shelter capacity updates and volunteer alerts. Msg frequency varies. Msg & data rates may apply.\" — samples show a capacity update and a volunteer alert, and nothing else.",
      "pitfalls": [
        "A QR code that has been reprinted but still points at the old landing page fails this on the destination rather than on the print, and the artifact looks correct in the upload."
      ],
      "catalogIds": [
        "CMP-157"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-149",
      "slug": "msg-149",
      "title": "Samples must match the declared opt-in method",
      "statement": "Sample content must be consistent with how consent was collected, not only with the use case.",
      "rationale": "The opt-in method sets expectations about what arrives: someone who texted a keyword for a discount code expects offers, while someone who ticked a box at checkout expects order updates. A mismatch means the consent obtained does not cover the messages sent.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + consent method",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Align the samples with what the opt-in surface promised. Where you send more than the opt-in described, widen the disclosure rather than narrowing the samples.",
      "example": "Keyword opt-in advertised as \"text JOIN for offers\" → samples are offers, not appointment reminders.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-152",
      "slug": "msg-152",
      "title": "Transactional campaigns must contain no promotional element",
      "statement": "A campaign declared transactional or informational must have no coupon, offer, upsell, or promotional content in its samples.",
      "rationale": "Transactional use cases receive better throughput and lighter filtering precisely because they are not marketing. A single coupon inside an otherwise transactional set means the campaign obtained that treatment under a false description, which is why one offending sample is enough to reject.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove promotional content from transactional samples, or move the campaign to MIXED and collect promotional-grade consent.",
      "example": "ACCOUNT_NOTIFICATION: \"Acme Coffee: your payment of $24.50 was received.\" — no offer attached.",
      "pitfalls": [
        "A discount code appended to an order confirmation converts the whole campaign into marketing."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-155",
      "slug": "msg-155",
      "title": "Sweepstakes content requires the sweepstakes use case",
      "statement": "Where samples contain sweepstakes or contest content, the sweepstakes use case must be declared.",
      "rationale": "Sweepstakes carry their own eligibility and official-rules requirements, and several providers subject them to special business review. Running one under a general marketing use case bypasses that review, so reviewers force the correction rather than accepting the label.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Sinch",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare the sweepstakes use case and publish official rules linked from the message, or remove contest content from the campaign.",
      "example": "Use case: SWEEPSTAKE, with official rules at acmecoffee.com/rules linked in the message.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-161",
      "slug": "msg-161",
      "title": "SHAFT screening covers every campaign field, not a sample of them",
      "statement": "The SHAFT screen — sex, hate, alcohol, firearms, tobacco and profanity — must be applied to every sample message and to the campaign description and message flow, and a single hit anywhere fails the whole campaign.",
      "rationale": "Reviewers screen the submission as one document, so four clean samples and a fifth that mentions the bar tab is a rejected campaign, not a mostly-passing one. The innocent version of this is very common: the samples are written carefully and the description is written last, in a hurry, naming the product the samples were careful to avoid. Because the rejection names the campaign rather than the field, operators resubmit with the samples polished again and are refused a second time for text they never re-read.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "AT&T",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "4100",
          "remediable": false
        },
        {
          "provider": "Twilio",
          "code": "30883",
          "remediable": false,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Find which letter was hit before changing anything, because the letters do not share a fix. Sex and hate have no path at any provider. Firearms has none under the strict reading. Alcohol and tobacco have a narrow one that runs through a 21+ date-of-birth age gate rather than through the message copy. The per-letter rule for the category you hit carries the actual path; this one only tells you the screen covers every field you submitted.",
      "pitfalls": [
        "Re-reading only the samples is the usual second failure — the description and the message flow are screened on the same pass and are where the product name most often survives."
      ],
      "notes": "Deliberately HARD_STOP rather than RETRY_FIELD even though some letters are fixable: the umbrella verdict does not know which letter fired, and a blind regenerate-and-resubmit loop against a SHAFT rejection burns a campaign at Twilio, where the Gen-2 codes are not eligible for resubmission.",
      "catalogIds": [
        "CMP-161"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-163",
      "slug": "msg-163",
      "title": "Family-friendly styling must not carry adult themes",
      "statement": "Content presented as suitable for a family audience must not carry adult themes, and must not depict children or characters aimed at children in a sexualised or otherwise unsuitable way.",
      "rationale": "The harm is that nothing on the surface warns anyone: the message reads as a cartoon promotion, so it is forwarded, shown to a child, and only then understood. Telnyx and Bird both name this shape specifically because keyword screening is blind to it — every word in the message is clean, and the adult content lives in what the styling is pointing at. The honest version is a mascot or cartoon identity attached to an adult product, adopted because it tested well and without anyone thinking about who else receives the text.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Telnyx",
        "Bird",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Bird",
          "code": "703",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Drop the child-facing styling, the mascot and the cartoon voice, and describe the product to adults in adult terms. If the product itself is adult, note that restyling only settles this rule — the underlying content is still judged by the adult-content prohibition, which has no path on 10DLC.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-164",
      "slug": "msg-164",
      "title": "No underage, non-consensual or illegal sexual themes",
      "statement": "Campaign content must not promote underage, non-consensual or otherwise illegal sexual themes, simulated or real.",
      "rationale": "This is the one band of content where carriers act on a single message and escalate outside the ordinary rejection process, because the exposure of carrying it is criminal rather than commercial. It sits apart from the general adult-content prohibition because simulation does not soften it: fiction, roleplay and \"barely legal\" framing are treated identically to the real thing, which is the distinction operators of legal adult businesses most often get wrong.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Telnyx",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "There is no compliant framing and no age gate that reaches this content — remove it from the campaign and from every page a message links to. If the finding is a false positive on safeguarding or clinical copy, say in the description who the recipients are and what the service does, in the terms a clinician would use.",
      "notes": "AT&T additionally bans how-to material about pedophilia or non-consensual contact. Content promoting the sexual exploitation of minors is a separate obligation (catalog MSG-166) that routes to human handling rather than to an automated verdict, and no rule in this registry decides it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-170",
      "slug": "msg-170",
      "title": "No content promoting, glorifying or inciting violence",
      "statement": "Campaign content must not promote violence, threats or physical harm.",
      "rationale": "T-Mobile treats depictions and endorsements of violence as illicit content in their own right, independent of whether any protected group is targeted — so a campaign can clear the hate-speech screen and still be refused here. The consumer-protection reason is blunt: SMS is a channel with no way to warn or filter before the words are on the screen, and the recipient did not choose to see them. Businesses hit this through marketing register rather than intent, with copy about destroying the competition or killing it at the gym read literally by a screen that has no sense of humour.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "T-Mobile",
        "CTIA",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30954",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the violent framing and describe what the business actually does. Where the copy was figurative, the fix is to say the literal thing instead: \"our fastest sale of the year\" rather than \"prices we are murdering\". If violence is genuinely the subject — an entertainment title, a game — no source publishes a carve-out for promoting it by SMS.",
      "notes": "Twilio codes hate and violence to the same Gen-2 code, 30954, so a rejection under it does not tell you which of the two fired. The registry keeps them apart because the copy that causes each is different: MSG-SHAFT-HATE is content aimed at a group, this is content about harm.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-171",
      "slug": "msg-171",
      "title": "Messages must not harass, intimidate or threaten the recipient",
      "statement": "Message content must not be designed to harass, bully or intimidate the person receiving it, and must not threaten them.",
      "rationale": "This is hostility pointed at the individual holding the phone rather than at a group, and it is the shape complaints arrive in most often — the recipient reports the message because it frightened them, not because it offended them. Honest businesses land here through pressure copy and enforcement language: escalating reminders, \"final warning\", \"we will be forced to take action\", each of which reads as a threat on a small screen with no context around it. AT&T states it plainly — message content that deceives or threatens consumers is not permitted — and there is no volume threshold below which it is tolerated.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "CTIA",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30954",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the copy to state the position and the recipient's options neutrally, and delete anything that predicts a consequence in order to frighten rather than to inform. A message that says what is owed, by when, and how to resolve it carries the same information without the pressure — and if the programme genuinely depends on the pressure, it is not registrable.",
      "pitfalls": [
        "Softening the wording while keeping an escalating cadence does not settle this: repetition aimed at wearing the recipient down is itself the harassment, and the samples are read as a sequence."
      ],
      "catalogIds": [
        "MSG-172"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-175",
      "slug": "msg-175",
      "title": "Content must not invade privacy, defame, or otherwise cause harm",
      "statement": "Message content must not invade privacy, create a safety risk, or be unlawful, abusive, malicious, misleading, excessively violent, obscene or defamatory.",
      "rationale": "CTIA's residual harm clause is what catches the damage nobody wrote a category for — a message that names a third party's debt, address or medical appointment to somebody else, or that asserts something ruinous about a named business. The recipient of a privacy leak is not the person harmed by it, which is why no complaint-driven check finds these and why the clause is written as a sweep. Businesses reach it innocently through personalisation: a template that renders the wrong record, or a referral message that tells the recipient why their friend needs the service.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Strip any third-party detail out of the message body and address the recipient about their own relationship with you only. Where personalisation pulls in another person's data — a referrer, a dependent, a co-signer — replace it with a reference the recipient already holds, and remove any assertion about a named business you cannot document.",
      "pitfalls": [
        "A message that is accurate can still fail this: disclosing a true fact about someone who is not the recipient is the violation, not getting it wrong."
      ],
      "notes": "This is CTIA MPBP §5.3.1, which is written as a catch-all covering several harms that have dedicated rules elsewhere. It is kept whole rather than split so the limbs nobody else owns — privacy invasion, safety risk and defamation — are not lost between rules; the criteria route the overlapping limbs so a single defect is reported once.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-177",
      "slug": "msg-177",
      "title": "Content must not depict or facilitate substance abuse",
      "statement": "Message content must not depict, promote or be designed to facilitate the abuse of alcohol, drugs, tobacco or other controlled substances, including where the abuse is implied rather than stated.",
      "rationale": "This is the limb of the alcohol prohibition that an age gate does not reach, and confusing the two costs operators a build cycle: gating the site stops minors seeing the offer, and does nothing about copy that sells drinking to excess as the point. AT&T and the CTIA messaging principles both extend it to non-verbal implication, so an emoji, an image or a wink carries the meaning as well as a sentence does. Bars and event promoters hit this constantly and innocently, because \"bottomless\", \"all you can drink\" and \"we will not judge how many\" are ordinary trade language that reads, to a screen, as encouragement to drink to harm.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the quantity, the challenge and the intoxication as the selling point, and price or describe the offer instead. Do not reach for the age gate here — it settles who may see an alcohol offer, not what the offer says, so building one leaves this finding exactly where it was. Done when nothing in the copy would read as encouragement if a regulator read it aloud.",
      "pitfalls": [
        "An emoji can carry the whole violation on its own: a row of beer glasses beside \"Friday\" is the implication the rule is written about, and deleting the words does not remove it."
      ],
      "notes": "Graded BLOCKING on the strict reading. The research records the AT&T alcohol-abuse row at HIGH and the CTIA abuse-reference row at BLOCKING; the registry takes the stricter of the two and records the disagreement here rather than averaging it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-179",
      "slug": "msg-179",
      "title": "Alcohol content must not be aimed at people under the drinking age",
      "statement": "Alcohol brands, and drinks designed to resemble alcoholic ones, must not be marketed in a way that appeals to or targets people under 21.",
      "rationale": "An age gate is the whole basis on which alcohol messaging is permitted at all, so copy built to appeal to minors defeats the mechanism rather than failing to install it — which is why T-Mobile treats this as its own refusal rather than as an age-gating defect. Alcohol-resembling drinks are named explicitly because they slip the gate by construction: an alcohol-free brand extension in the same livery is sold to the same audience with none of the restrictions, and the brand association is the marketing. The innocent cause is a genuine 0% product line marketed on exactly the assets built for the alcoholic one.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "706",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30456",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the youth-facing appeal — the cartoon, the school or campus timing, the sweet-confectionery framing, the under-21 talent — and market the drink to adults. Note that the age gate is not the answer to this finding: gating the site is already required for alcohol content, and a campaign that targets minors is refused whether or not the gate exists.",
      "pitfalls": [
        "A zero-alcohol product does not escape this when it carries the parent brand's identity — the brand is what is being marketed, and it is an alcohol brand."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-182",
      "slug": "msg-182",
      "title": "No explosives, fireworks or pyrotechnics content",
      "statement": "Campaign content must not promote explosives, fireworks or pyrotechnics.",
      "rationale": "Twilio files explosives under the same code as firearms and toll-free verification extends the SHAFT \"F\" to fireworks explicitly, which surprises seasonal retailers who reason — correctly, in their own state — that they hold every licence the law asks for. The prohibition is about the channel rather than the licence: a text is forwarded, screenshotted and read by people the seller never sold to, and carriers will not carry the promotion of something that injures thousands of people a year across a medium with no controls on who sees it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30957",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30469",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the product from the campaign entirely — the age gate that works for alcohol has no equivalent here, and no provider publishes a licensed-retailer exemption. Where the programme is genuinely operational rather than promotional, say so in the description: crew scheduling, safety notices and public event information carry no product offer and are a different campaign.",
      "notes": "The allowances above are the reframings that remove the product-promotion element, not documented exemptions — no source publishes a carve-out for public-display announcements. Treat them as the shape worth attempting, and expect the description to have to make the absence of a sale explicit.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-183",
      "slug": "msg-183",
      "title": "No weapons that are not legal in all fifty states",
      "statement": "Campaign content must not promote weapons that are not lawful in every US state.",
      "rationale": "This is a legality sweep rather than a category ban, and it is the rule that catches sellers who are entirely lawful where they stand: switchblades, brass knuckles, certain knives, tasers and high-capacity magazines are ordinary retail in some states and criminal to possess in others. A campaign is one national object — the numbers reach every state — so carriers apply the most restrictive state rather than the sender's. Bandwidth and Infobip both refuse on the fifty-state test specifically, which is why a firearms-clean knife retailer still gets rejected and cannot see why.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bandwidth",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7009",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "7011",
          "remediable": false
        },
        {
          "provider": "Infobip",
          "code": "7009",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take the state-restricted lines out of the message copy and register the campaign on the products that are lawful everywhere, or on services rather than product. The test is nationwide legality, not your own licence, so the fix is to check each item you name against the strictest state rather than to add a disclaimer or an age gate.",
      "pitfalls": [
        "Adding \"where legal\" to the message does not settle this — carriers apply the fifty-state test to the campaign, not to each recipient, and the disclaimer is read as an admission."
      ],
      "notes": "The same code number resolves differently by namespace, so the remediability is recorded per provider rather than averaged: Bandwidth Proxy 7009 and 7011 are terminal, while Infobip 7009 is conditional on the product set being lawful nationwide. Kept apart from MSG-SHAFT-FIREARMS deliberately: that rule is the firearms category test, this one is a legality test that catches sellers who hold no firearms at all. A knife retailer trips this and passes that.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-190",
      "slug": "msg-190",
      "title": "No kratom or drug-paraphernalia content",
      "statement": "Campaign content must not promote kratom, drug paraphernalia and smoking accessories.",
      "rationale": "Kratom is unscheduled federally and sold openly in most states, which is exactly why operators are blindsided: carriers screen it alongside cannabis rather than by legal status, and the paraphernalia ban reaches the pipe, the grinder and the rolling paper even where nothing controlled is being sold with them. The consumer-protection reason is the same one behind the cannabis ban — a channel with no age assurance and no way to check what the product actually contains — and the FDA import alerts on kratom are what keeps it there.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Sinch",
        "Infobip",
        "Plivo",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30469",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "701",
          "remediable": false
        },
        {
          "provider": "Infobip",
          "code": "7011",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take kratom and accessory product out of the message copy and out of any page a message links to. There is no age gate or licence that opens this — a shop that sells other goods can register on those, but the campaign has to be describable without naming the restricted lines, including in the description and the message flow.",
      "pitfalls": [
        "Screening only the samples leaves the usual hit in place: the shop's name and the linked landing page are read on the same pass, and a store called anything with \"smoke\" or \"head shop\" in it is assessed on that alone."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-206",
      "slug": "msg-206",
      "title": "No sweepstakes, raffle or contest-entry content",
      "statement": "Campaign content must not promote sweepstakes, raffles, prize giveaways or contest entry.",
      "rationale": "Twilio files sweepstakes under gambling and refuses the category outright for \"high abuse potential and regulatory complexity\", while TCR and Sinch publish a Sweepstakes use case you can register under — so the same giveaway is a legitimate programme on one route and a dead campaign on another. That split is what makes this expensive: a marketing team runs the giveaway that worked last year, registers it as MARKETING, and loses the campaign rather than being told to change one dropdown. Prize mechanics also carry real consumer harm on SMS, because a giveaway is the cheapest way ever invented to buy a phone number from someone who was not thinking about what they were joining.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AWS",
        "Bandwidth",
        "Telnyx",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30945",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30462",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check whether your route publishes a Sweepstakes use case before you do anything else, because that decides whether this is a dropdown change or the end of the campaign. Where it exists, register under it and link the official rules from the message. Where it does not, replace the chance-based mechanic with a certain one — every subscriber gets the discount — and remove the draw from the samples, the description and the opt-in copy.",
      "pitfalls": [
        "Moving the draw out of the samples but leaving \"enter to win\" in the opt-in call to action does not settle it: the incentivised-opt-in limb is judged on the message flow, and that is where the offer usually lives."
      ],
      "notes": "Genuine authority divergence rather than a strictness difference: Twilio 30945 and toll-free 30462 refuse the category, while TCR and Sinch (CR1105, CR2009, CR3009) provide a Sweepstakes use case to register it under. Kept BLOCKING on the strict reading, with the use-case route named in the remediation so a user on a permissive route is not told to abandon a legitimate programme.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-209",
      "slug": "msg-209",
      "title": "A charity programme may not run games of chance or recurring donations",
      "statement": "A campaign registered under the CHARITY use case must not solicit lottery, sweepstakes or raffle entries, and must not enrol donors in recurring donations.",
      "rationale": "The charity use case buys favourable treatment — higher throughput, a lighter consent standard than marketing — on the basis that the traffic is mission communication to supporters. Games of chance and recurring billing are the two things that turn it into a payment funnel, and both carry the harms the concession was never meant to cover: a raffle is gambling however good the cause, and a recurring donation enrolled by text is a subscription the donor cannot see, cancel or reconcile. Charities are caught out by this constantly and in complete good faith, because the raffle at the fundraising dinner and the monthly giving programme are the two most normal things a small charity does.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30462",
          "remediable": false
        }
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Take the draw and the recurring enrolment out of the text programme and keep both on the web, where the donor sees the terms and the billing schedule. The messages may link to them; they may not run them. Done when no sample asks for an entry, a ticket or a monthly commitment, and every donation ask in the campaign is for a single gift.",
      "example": "Riverside Shelter: our winter beds are at capacity tonight and we need blankets. Give once at https://riversideshelter.org/give Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "Linking to the raffle page rather than selling tickets in the thread is the fix; describing the raffle in the message and linking to it is still soliciting entries and still fails."
      ],
      "notes": "CTIA dropped this from the Short Code Monitoring Handbook at v1.9 while T-Mobile section 6.7 retains it, so it is a live requirement on the strictest reading rather than a historical one. Divergence recorded; strictest reading kept.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-210",
      "slug": "msg-210",
      "title": "A political donation programme may not include gaming-related fundraising",
      "statement": "A campaign registered under the POLITICAL use case must not raise funds through lotteries, sweepstakes, raffles or any other game of chance.",
      "rationale": "Political fundraising by text already sits under the most restrictive consent and vetting regime in the framework, and attaching a prize draw to it compounds two separate exposures: gambling regulation, which is state by state, and campaign-finance law, which requires every contribution to be attributable to a qualified individual giving their own money. A raffle ticket is not a contribution in that sense, so the mechanic breaks the record-keeping the programme is required to keep. Committees reach it because \"enter to win dinner with the candidate\" is a standard grassroots tactic on every other channel.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Remove the draw from the text programme and ask for the contribution directly. Where the prize event genuinely exists, describe it as an event with an entry price handled on the web, not as a chance the message sells. Done when every fundraising sample asks for a contribution and nothing in the campaign offers a chance to win.",
      "example": "Riverside Shelter Action Fund: we are $4,000 short of our filing deadline. Chip in at https://riversideshelter.org/contribute Reply STOP to opt out, HELP for help.",
      "notes": "CTIA states the prohibition for political donation programmes specifically. MSG-209 is the same gaming ban for charities and adds the recurring-donation limb, which is not part of this one — the two are kept apart because they are gated on different use cases and do not say the same thing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-214",
      "slug": "msg-214",
      "title": "No student-loan programme content",
      "statement": "Campaign content must not promote student-loan refinancing, consolidation or forgiveness programmes.",
      "rationale": "Student-loan messaging is refused as a category because the fraud around it is industrial: forgiveness and consolidation offers by text are the delivery mechanism for advance-fee scams that charge borrowers for a free federal process. Carriers cannot tell a legitimate refinancer from that by reading a message, so they refuse the category and let the servicers who have a genuine relationship register on it. The line that decides your campaign is whether you service the loan you are texting about.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AT&T",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the acquisition offer. If you service the loans yourself, say so in the description in those words — who originated the loan, who services it, and that every recipient is an existing borrower — and keep the samples to account events rather than offers. Done when no message would make sense to somebody who is not already your customer.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-215",
      "slug": "msg-215",
      "title": "Samples must not collect information to qualify a consumer for a loan",
      "statement": "Sample messages must not ask the recipient to supply information used to qualify, price or route a loan application.",
      "rationale": "A text asking for the last four of a social security number or the year and mileage of a car is a loan lead-generation funnel wearing the clothes of a service message, and it is refused for two reasons at once: the lending prohibition, and the fact that no legitimate lender collects underwriting data by SMS reply. The consumer harm is direct — an SMS thread is unencrypted, sits in the handset indefinitely, and is the highest-yield place a phishing operator can imitate. Businesses that genuinely lend land here by moving a working web form into a text conversation because the completion rate is better.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Nothing about the phrasing rescues this, because the objection is to collecting the data in the thread at all. Move the whole qualification step behind an authenticated page on your own domain and let the message do nothing but link to it — and if the campaign has no purpose beyond routing loan enquiries, it is not registrable whatever the messages say.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-221",
      "slug": "msg-221",
      "title": "Debt relief and credit repair stay prohibited even with first-party consent",
      "statement": "Debt consolidation, debt reduction and credit repair content is refused whether or not the sender collected the consent itself.",
      "rationale": "Consent is the answer to almost everything else in this framework, so operators reasonably assume it is the answer here too — and it is the one place it is not. Carriers refuse these categories on the business model rather than on the permission, because the consumer harm they are protecting against is the service itself: the fees, the credit damage and the misrepresentation happen to people who did agree to hear from the sender. Stating it as its own rule matters because the neighbouring first-party carve-outs are real, and a user who has just read that a creditor may chase its own invoice will otherwise conclude the same reasoning rescues a debt-settlement offer.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bird",
        "Infobip",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30949",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30950",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not answer this by strengthening the consent record — a signed opt-in, a recorded call and a double opt-in all leave the verdict where it is. The campaign is registrable only without the debt-relief and credit-repair service in it, so remove those lines from the description, the samples and the flow, and register on whatever else the business does.",
      "notes": "MSG-DEBT is the semantic screen and is what catches euphemism; this is the deterministic backstop, and it exists because the objection users actually raise is consent rather than wording. Matching on an explicit lexicon on purpose: the point is to answer the consent question before a round trip is spent on it, not to be the primary detector.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-226",
      "slug": "msg-226",
      "title": "No forex, binary options or speculative-derivative content",
      "statement": "Campaign content must not promote forex, binary options, contracts for difference and other speculative derivatives.",
      "rationale": "These instruments are the ones retail regulators warn about most loudly, and SMS is where the promotion happens because it reaches people outside any suitability check. Twilio names them separately from the general stock and crypto ban because the promotion pattern is different — signal groups, copy-trading and leverage offers rather than tips on a named security — and because binary options are outright banned to retail investors in several jurisdictions the traffic reaches.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30948",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the solicitation. Account servicing for people who already hold an account with a regulated entity is a separate, registrable category — frame the campaign that way, name the regulator and the registration in the description, and keep the samples to account events with no market commentary in them.",
      "notes": "Twilio 30948 also covers unregistered-securities offerings, which the catalog holds as a separate obligation requiring external data and human review (MSG-228). No rule in this registry decides that one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-227",
      "slug": "msg-227",
      "title": "No promises of unusually high or guaranteed returns",
      "statement": "Campaign content must not promote schemes promising guaranteed, risk-free or unusually high financial returns.",
      "rationale": "A guaranteed return is the single most reliable marker of investment fraud, which is why it is screened as its own category rather than left to the deception rules: no lawful investment can promise one, so the claim is refused on its face without anyone having to establish what the underlying product is. Honest businesses reach it through marketing habit — \"guaranteed savings\", \"risk-free trial\", a headline percentage with the conditions in the next sentence — and the screen reads the headline.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30948",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete the guarantee and the headline number from the message copy. Where the rate is genuinely contractual — a deposit account, an insured product — say what it is and who insures it in the same sentence rather than leading with the figure. Done when no sample makes a promise about money that the business could not be held to in writing.",
      "pitfalls": [
        "Adding \"terms apply\" or a disclaimer link does not settle this: the screen reads the claim, and a disclaimer confirms that the headline overstated it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-243",
      "slug": "msg-243",
      "title": "No relaying one-time passcodes on behalf of other service providers",
      "statement": "Campaign content must not promote persistent relaying of one-time passcodes for other service providers.",
      "rationale": "A verification code is the most trusted message a consumer receives, and its trust rests entirely on it coming from the service being logged into. A sender relaying codes for other companies breaks that: the recipient cannot tell whose login is being confirmed, and the sending brand cannot be held to the consent because it has no relationship with the recipient. It is also the exact shape traffic-pumping fraud takes, which is why it is refused as a pattern rather than assessed message by message.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "40322",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register each business that owns the login under its own brand and campaign so the codes go out under the name the consumer is signing into. Where you are the platform, that means registering on behalf of each customer rather than pooling them under yours. Done when every sample names a service the registered brand actually operates.",
      "notes": "Telnyx 40322 is recorded as \"Permanent — fix content\" in one source and as remediable in another; the strict reading is kept. Telnyx is the only source that publishes this in these words, so the rule is left universal rather than provider-tagged: it is the same consent-chain and sender-identity requirement the whole framework applies, and tagging it Telnyx-only would drop a real obligation for every other route. Twilio and Bandwidth reach the same outcome through the ISV-versus-end-business rules instead.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-245",
      "slug": "msg-245",
      "title": "No fake-prize, impersonation or advance-fee patterns",
      "statement": "Campaign content must not promote fake prizes, impersonated brands and advance-fee solicitations.",
      "rationale": "These three patterns share a mechanism — manufacture a reason the recipient is owed something, then extract a payment or a credential to release it — and Twilio screens them together because they are what its fraud code was written about. They are separated from general deception because they are recognisable by shape rather than by verification: nobody has to establish what the business really does to know that a prize nobody entered for is not real. The registration-time signal is often the brand record itself, where the details do not match anything a search would find.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30959",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any sender identity other than the registered brand, any prize claim with no promotion behind it, and any payment asked for before a benefit is delivered. Where you act for a named principal, say so in the description and register under a brand that matches what the recipient will see. Done when the sender name in every sample is the brand on the registration.",
      "notes": "Twilio 30959 is \"a permanent disqualification from A2P 10DLC messaging\" — the rejection ends the account's route, not just the campaign, so this must never be surfaced as something to edit and resubmit.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-246",
      "slug": "msg-246",
      "title": "No manufactured urgency",
      "statement": "Messages must not manufacture urgency — neither a security pretext such as an account about to be suspended, nor a commercial countdown, scarcity or deadline that is not real.",
      "rationale": "Urgency is the mechanism every SMS scam runs on, because a person who believes they have ninety seconds does not check the sender. Carriers therefore screen for the pattern rather than for the underlying truth, which is why an honest retailer running a fake countdown that resets every night is filtered alongside the phishing traffic it resembles. Twilio names both halves — \"your account will be suspended\" as the security pretext, fake countdowns and false scarcity as the commercial one — and codes them to rejections that are not eligible for resubmission.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30960",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take the deadline out unless it is real and you can show when it was set. A genuine sale that ends on Sunday can say so; a countdown that restarts, a \"last chance\" sent monthly, or a stock figure nobody is counting cannot. Where the message concerns a real account event, describe what happened and let the recipient act in their own time rather than telling them what is about to be lost.",
      "pitfalls": [
        "Softening the wording while keeping the mechanism does not settle it — \"ending soon\" on a promotion that never ends is the same finding as a countdown clock."
      ],
      "notes": "Absorbs the catalog's separate artificial-urgency row (MSG-247). The two are one judgement about the same defect: MSG-246 is the security pretext, MSG-247 the commercial one, and a rule that split them would report one message twice.",
      "catalogIds": [
        "MSG-247"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-249",
      "slug": "msg-249",
      "title": "No fraud or scam message content",
      "statement": "Campaign content must not promote fraud or scam content — deception for financial or personal gain.",
      "rationale": "This is the category carriers act on fastest and punish hardest, because a fraudulent text costs a real person real money within minutes of delivery and the carrier is the one who delivered it. T-Mobile grades it Severity-0 Tier 1 at $2,000 per violation, which is per message rather than per campaign, and Twilio describes its Gen-2 code for it as a permanent disqualification from A2P 10DLC. Nothing else in the registry escalates that far, and nothing else attaches to the account rather than to the campaign.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "Twilio",
        "Telnyx",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30959",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30884",
          "remediable": false,
          "generation": "gen1"
        },
        {
          "provider": "Telnyx",
          "code": "40322",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "There is no compliant version of this and no reframing to attempt. If the finding is a false positive on genuine transactional copy, the fix is to make the message verifiable rather than to soften it: name the actual transaction, link only to your own domain, and never ask the recipient to send money or credentials by reply.",
      "notes": "The financial exposure is worth stating alongside the rejection: T-Mobile prices Severity-0 Tier 1 phishing and fraud at $2,000 per violation and Tier 2 illegal content at $1,000, assessed per message rather than per campaign. Twilio 30959 is described as permanent and may trigger account-level enforcement beyond the campaign.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-250",
      "slug": "msg-250",
      "title": "No simulated-phishing or security-testing programmes",
      "statement": "Campaign content must not promote simulated phishing and security-awareness test messaging.",
      "rationale": "A simulated phishing text is indistinguishable from a real one to every system that handles it, so it trains carrier filters on the sender, generates the same consumer complaints, and reaches the same people — including employees who left months ago and whoever now holds a recycled number. T-Mobile fines it at Tier 1, the same band as actual phishing, which surprises security teams badly because the intent is defensive and the programme was signed off internally. Intent is not a defence the network can see.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30884",
          "remediable": false,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30007",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Move the simulation off SMS. Run the exercise on channels you control end to end — corporate email, an internal app, a managed device — where the deception never touches the public network. Where the awareness programme itself is worth texting about, register it on honest copy: reminders, enrolment and results, with nothing designed to be mistaken for something else.",
      "pitfalls": [
        "Internal authorisation and a signed engagement letter do not change the verdict: the carrier is assessing the traffic on its network, and the employer's consent is not the recipient's."
      ],
      "notes": "T-Mobile prices this at Severity-0 Tier 1, $2,000 per violation — the same band as real phishing, and assessed per message.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-255",
      "slug": "msg-255",
      "title": "No impersonating a third-party brand on the landing page or in a linked file",
      "statement": "Neither the landing page a message reaches nor any file it offers for download may present itself as belonging to a business other than the registered brand.",
      "rationale": "Impersonation moved to the landing page precisely because message text is screened and pages historically were not: the text says \"your delivery\", the page wears a courier's livery, and the recipient hands over card details to something they believe is the courier. CTIA extends the prohibition to linked downloads for the same reason — a PDF invoice on somebody else's letterhead does the same work as a cloned page. Legitimate senders hit this through reseller and white-label arrangements where the page genuinely carries a partner's branding and nobody thought of it as impersonation.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] -> landing page + linked download",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the destination page identify the registered brand above the fold — name, logo and contact route — and remove any third-party branding you are not authorised to display. Where you act for a named principal, say so on the page in the form \"X, an authorised agent of Y\" rather than presenting as Y. Done when the page names the sender before it names anyone else.",
      "pitfalls": [
        "A footer disclaimer does not settle it: reviewers judge what the page presents itself as on arrival, and a logo at the top outweighs a line of small print at the bottom."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-261",
      "slug": "msg-261",
      "title": "The call to action must not misdescribe the programme",
      "statement": "Neither the call to action nor subsequent messages may use deceptive language, or misdescribe what the programme sends, how often, or what it costs.",
      "rationale": "This is deception measured against the promise rather than against the truth: a consumer who agreed to \"occasional order updates\" and receives daily promotions was misled at the point of consent, whichever message you read on its own. It is the one deception rule that reaches back to the opt-in copy, which is why it catches programmes that are entirely honest about each message and dishonest about the shape of the whole. The innocent cause is almost always drift — the CTA was written for the programme as it was launched, and nobody rewrote it when marketing was added.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + CTA copy + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the call to action to describe the programme you are actually registering — every content type it sends, the real frequency, and the cost position — then check the samples against it line by line. Done when a recipient reading only the CTA would be unsurprised by every sample in the campaign.",
      "example": "CTA: \"Text me Acme Coffee offers, rewards updates and order notifications. Msg frequency varies (about 4/month). Msg & data rates may apply.\" — matching a sample set that contains all three kinds.",
      "pitfalls": [
        "Adding the missing content type to the CTA usually changes the consent standard as well: a programme that admits it sends promotions needs promotional-grade consent, so the CTA edit and the use case have to move together."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-262",
      "slug": "msg-262",
      "title": "No sender whose business is buying, selling or sharing consumer information",
      "statement": "Campaign content must not promote the buying, selling or sharing of consumer information as a business model.",
      "rationale": "Every consent in the framework is held by one business for one relationship, so a sender whose business is moving consumer records between parties cannot hold the consent it is relying on — the person agreed to hear from somebody else. Twilio applies this to the sender rather than to the message, and explicitly to non-profits and political committees as well, because list-swapping is how their traffic is built too. That is the surprise: an organisation can have impeccable message copy and still be refused on what it does for a living.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30468",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This is settled on the business model, so the fix is not in the message copy: stop selling or sharing consumer data for other parties' marketing, and make the privacy policy say so in the standard form — mobile information is never sold, rented, traded or shared with third parties or affiliates for their own marketing. Where a vendor genuinely only delivers the messages, name it as a processor rather than as a partner. Done when the policy, the website and the description all describe one business holding one relationship.",
      "pitfalls": [
        "The website and the privacy policy are read alongside the campaign, so a permissive data-sharing clause left in a generated policy fails this even when the business does not actually sell anything."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-263",
      "slug": "msg-263",
      "title": "An abandoned-cart message must carry an opt-out notice",
      "statement": "Every abandoned-cart reminder must tell the recipient how to stop receiving them.",
      "rationale": "The cart reminder is the message most likely to feel like surveillance: it arrives because the recipient looked at something and did not buy it, and it is the one people most want to stop on the spot. CTIA singles it out for a per-message opt-out notice for that reason, rather than letting it rely on the notice in some earlier message of the programme. Retailers omit it in good faith because the reminder is short by design and every character is fighting for the click — which is exactly the pressure the rule exists to overrule.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "cart-reminder message body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to opt out\" to the cart-reminder template itself, not only to the welcome message. Done when the reminder carries it on its own, read in isolation, with no earlier message in the thread.",
      "example": "Acme Coffee: you left the Ethiopia Guji in your basket. Finish up at https://acmecoffee.com/cart Reply STOP to opt out.",
      "notes": "Universal rather than tagged, because TCR has no use case for abandoned-cart messaging — it is a shape a MARKETING or MIXED campaign takes, and no fact on the registration declares it. The condition is carried in the criteria, which open with a PASS boundary for every campaign that sends no cart reminders.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-264",
      "slug": "msg-264",
      "title": "An abandoned-cart message must not complete the purchase in the thread",
      "statement": "A cart reminder must not collect payment information, accept purchase approval by keyword reply, or otherwise complete the transaction — the recipient must finish the purchase through a direct URL.",
      "rationale": "A purchase confirmed by replying Y is a charge authorised by a single character, sent from a device somebody else may be holding, with no price, no total and no terms on screen at the moment of authorisation. T-Mobile and CTIA both refuse it, and the harm is not hypothetical: the dispute rate on keyword-confirmed purchases is what put the rule in the handbook. Merchants build it because it converts, and because the platform offering the feature does not mention that carriers refuse the campaign that uses it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "cart-reminder message body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the keyword purchase step with a direct link to the checkout page for that basket, and take every request for card, billing or address detail out of the message body. Done when the only thing a reply can do is stop the messages, and every purchase completes on your own site with the total on screen.",
      "example": "Acme Coffee: your basket is still here — 2 bags of Ethiopia Guji, $34 total. Check out at https://acmecoffee.com/cart/4471 Reply STOP to opt out.",
      "pitfalls": [
        "A \"reply Y to confirm and we will charge your card on file\" flow is exactly the prohibited pattern, and it is the one most cart platforms offer as a headline feature."
      ],
      "notes": "Universal for the same reason as MSG-263: no registration fact declares a cart programme, so the condition lives in the criteria and opens with a PASS boundary.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-266",
      "slug": "msg-266",
      "title": "A forwarded or referred message must say why the recipient received it",
      "statement": "A message sent because another consumer referred or forwarded it must identify the person who sent it and state why the recipient is receiving it.",
      "rationale": "A referral message reaches somebody who never opted in to anything — the consent, such as it is, belongs to their friend — so the only thing standing between it and an unsolicited commercial text is the recipient being able to see immediately that a person they know is behind it. Without the name and the reason it is indistinguishable from spam and is reported as such, which damages the sending number for every other recipient. Businesses omit the name because their referral tooling does not have it, or because the message reads better without it, and neither is a reason the carrier recognises.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "viral message body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the referring person's name and the reason into the first sentence of the referral template, before the offer — a [referrer name] token and a clause saying they sent it. Done when a recipient who has never heard of your business can tell, from the first line, who thought of them and why.",
      "example": "Acme Coffee: [referrer first name] sent you a free bag of our Ethiopia Guji because they thought you would like it. Claim it at https://acmecoffee.com/r/4471 Reply STOP to opt out.",
      "pitfalls": [
        "Naming only the business does not satisfy this — the required identification is of the consumer who sent it, and that is the part referral tooling most often cannot supply."
      ],
      "notes": "Universal rather than tagged: no fact on the registration declares a referral or refer-a-friend programme, so the criteria carry the condition and open with a PASS boundary for campaigns that have none. Note the interaction with the consent rules — a referral message reaches somebody who did not opt in, and satisfying this rule does not settle whether the programme may send it at all.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-267",
      "slug": "msg-267",
      "title": "A political donation programme must confirm intent and certify eligibility",
      "statement": "A message-originated political contribution must be answered with a message confirming the contributor's intent to give and carrying the eligibility certification — at least 18, own funds, not a foreign national, not a federal contractor.",
      "rationale": "Campaign-finance law puts the burden of establishing that a contribution is lawful on the committee receiving it, and a text has none of the surrounding form a web donation page would carry. The reply is where the certification has to live, because it is the only place the contributor can see it before the charge completes. Committees miss it because the message-originated flow feels like a confirmation receipt rather than a legal instrument, so they write it as one — and a receipt that does not ask the contributor to affirm anything leaves the committee unable to show the contribution was lawful.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "political donation MT body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Write the reply to the donation keyword so it does two things in order: restate the amount and ask the contributor to confirm they intend to give it, then set out the four eligibility statements they are certifying by confirming. Done when the reply contains a confirmation step the contributor must answer and all four certifications are legible in the same message.",
      "example": "Riverside Shelter Action Fund: reply YES to confirm your $25 contribution. By replying YES you certify that you are at least 18, a US citizen or lawful permanent resident, using your own funds, and not a federal contractor. Msg & data rates may apply. Reply STOP to opt out.",
      "pitfalls": [
        "Putting the certification behind a link does not satisfy this — the contributor must be able to read what they are certifying in the message they are replying to."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-290",
      "slug": "msg-290",
      "title": "Political content is refused outright on platforms that prohibit it",
      "statement": "A political campaign cannot be registered through an ISV whose own terms prohibit political message content, whatever the carriers allow.",
      "rationale": "Political messaging is registrable at TCR under a dedicated use case with its own vetting, so a committee that clears that vetting reasonably expects to be able to send — and then finds its platform refuses the category as a matter of its own policy. Klaviyo does exactly this. Discovering it after the vetting is complete and the election calendar is running is the expensive way to find out, and there is no appeal because nothing has gone wrong with the registration: the platform simply does not carry this business.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.usecase + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Klaviyo"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ],
        "providers": [
          "KLAVIYO"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL, and the submission target is KLAVIYO.",
      "universal": false,
      "remediation": "Move the programme to a route that carries political traffic. That is a commercial decision only you can make — pick a provider whose acceptable-use policy permits political content, register the brand and campaign there, and budget for the political vetting to be redone against the new route. Nothing in the campaign record needs to change, so do not spend a cycle editing it.",
      "notes": "Off-platform and provider-tagged. What the user has to do themselves: confirm the acceptable-use policy of any replacement platform in writing before rebuilding, because this is a vendor term rather than a carrier rule and no error code or vetting result will warn them. Deliberately not generalised — most routes carry political traffic, and applying Klaviyo's position as a superset would refuse legitimate campaigns everywhere else. Source: prohibited-content.md.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you confirmed in writing that the replacement platform's acceptable-use policy permits political content?",
        "howToCheck": [
          "Read the acceptable-use policy, then get the answer in writing from the vendor. This is a vendor term, so no error code or vetting result will warn you.",
          "Budget for the political vetting to be redone against the new route.",
          "Do not spend a cycle editing the campaign record — nothing in it is the problem."
        ],
        "failureLooksLike": "A committee clears political vetting, expects to send, and finds its platform refuses the category as a matter of its own policy. There is no appeal, because nothing has gone wrong with the registration."
      }
    },
    {
      "id": "MSG-293",
      "slug": "msg-293",
      "title": "Verbal opt-in grants no relief on sample content",
      "statement": "Samples must carry the brand name and opt-out instructions even when consent was collected verbally rather than on a web form.",
      "rationale": "Collecting consent by phone changes what evidence you file, and nothing about what the messages have to say — but the belief that it does is widespread, because the agent already identified the business on the call and already said the customer could stop at any time. The recipient is a different person by the time the text arrives: hours later, looking at an unknown number, with none of the call in front of them. AWS states the requirement explicitly for exactly this reason, and it is the anti-exemption that stops an offline consent method quietly suppressing the two rules every message owes.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "SAMPLE_MESSAGE_MISMATCH",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Add the registered brand name to the start of every sample and \"Reply STOP to opt out\" to the end, exactly as a web-opt-in campaign would. Done when a sample read cold, with no memory of the call, still says who is texting and how to stop it.",
      "example": "Acme Coffee: your support ticket 4471 is now with a specialist. We will text an update within one business day. Reply STOP to opt out.",
      "pitfalls": [
        "Naming the brand in the verbal script does not discharge this — the script is consent evidence, the samples are message content, and reviewers check them separately."
      ],
      "notes": "Deliberately overlaps XBC-010 and MSG-SAMPLES-OPTOUT, which are universal and already require both elements. It exists as its own rule because the catalog records the anti-exemption separately: an author narrowing either of those to web surfaces would silently exempt verbal campaigns, and this rule is what would go red.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-296",
      "slug": "msg-296",
      "title": "Platform sample specifications must be met where an ISV imposes one",
      "statement": "Where the campaign is registered through an ISV that publishes its own sample specification, the samples must satisfy it — for HubSpot, exactly two samples, each carrying the business name, opt-out instructions including STOP, a privacy-policy link, and bracketed personalisation.",
      "rationale": "An ISV sits between the business and TCR and applies its own acceptance rules first, so a submission that would clear every carrier requirement is still rejected at the platform without ever reaching a carrier. HubSpot publishes the strictest sample specification found anywhere — a superset of what any carrier asks — and businesses meet the carrier bar, submit, and are refused by their own tooling with no reference to a carrier rule they can look up. It is worth stating separately because the fix is cheap and the confusion is expensive: nothing is wrong with the campaign, only with the shape of it for one route.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "HubSpot"
      ],
      "applicability": {
        "providers": [
          "HUBSPOT"
        ]
      },
      "applicabilityText": "Applies when the submission target is HUBSPOT.",
      "universal": false,
      "remediation": "Submit exactly two samples and put all four elements in each: the business name at the front, \"Reply STOP to opt out\", a link to your public privacy policy, and at least one [bracketed] personalisation token. Done when both samples carry all four — HubSpot checks each sample independently rather than the set as a whole.",
      "example": "sample1: \"Acme Coffee: hi [first name], 20% off all espresso blends this weekend. Privacy: https://acmecoffee.com/privacy Reply STOP to opt out.\"\nsample2: \"Acme Coffee: hi [first name], order [order number] is ready for pickup. Privacy: https://acmecoffee.com/privacy Reply STOP to opt out.\"",
      "pitfalls": [
        "The privacy policy behind the link has to carry the mobile-information non-sharing statement and the rates disclosure — a link to a generic generated policy satisfies this rule and is then rejected on the policy itself."
      ],
      "notes": "Provider-tagged rather than applied as a superset, and that is the whole point of the tag here: this is one vendor's contract, not a carrier requirement, so imposing it on everyone would fail compliant registrations for not doing something no carrier asks. The check returns UNCLEAR while the submission target is unknown, because \"we do not know which platform you are using\" is the honest answer and returning a vacuous PASS would hide the requirement from the users who owe it. Source: aws-and-resellers.md section A, HUBSPOT-REG.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-ADULT-DATING",
      "slug": "msg-adult-dating",
      "title": "No adult dating or companionship solicitation",
      "statement": "Campaign content must not promote adult dating, companionship, or hookup services.",
      "rationale": "Dating content marketed on sexual availability falls under the SHAFT sex prohibition and carries an unusually high complaint rate, because recipients frequently did not knowingly opt in. Mainstream relationship services are judged separately on how they present themselves rather than on the category label.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove sexually suggestive framing and restrict messaging to non-explicit service notifications for users who opted in inside your own product. If the service is adult-oriented by nature, 10DLC is not an available channel.",
      "catalogIds": [
        "MSG-165"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-BAIL-BONDS",
      "slug": "msg-bail-bonds",
      "title": "No bail bonds or related solicitation",
      "statement": "Campaign content must not promote bail bonds and related legal-financial solicitation.",
      "rationale": "Bail bond marketing reaches people at a moment of acute distress, frequently using data obtained from arrest records rather than consent. Carriers treat unsolicited outreach on that basis as a per-se harm, independent of the legality of the underlying business.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This solicitation model cannot be registered. Messaging existing clients about their own case is a different category — describe it that way and remove any acquisition content.",
      "catalogIds": [
        "MSG-230"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-CANNABIS",
      "slug": "msg-cannabis",
      "title": "No cannabis, CBD, or hemp-derived product content",
      "statement": "Campaign content must not promote cannabis, CBD, hemp, or related products.",
      "rationale": "Cannabis is prohibited regardless of state legality, because where federal and state law diverge the industry applies federal law. This surprises operators in legal states more than any other rule, and Twilio states explicitly that keyword detection alone is insufficient — euphemistic framing is caught and treated the same way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30940",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Cannabis-related messaging cannot be registered on 10DLC in the US regardless of state licensing. Do not attempt to re-frame it — euphemism detection is explicit in the vetting criteria.",
      "catalogIds": [
        "MSG-188"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-CHARITY-SOLICIT",
      "slug": "msg-charity-solicit",
      "title": "Charitable solicitation requires the charity use case and real status",
      "statement": "Campaign content must not promote charitable donation solicitation outside the charity use case.",
      "rationale": "Donation solicitation gets favourable treatment and correspondingly stricter eligibility, including verified 501(c)(3) status. Soliciting donations under a marketing use case bypasses that check and is rejected on the mismatch rather than on the content.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register under the CHARITY use case with your 501(c)(3) EIN and tax-exempt status declared on the brand, or remove the donation ask from the campaign content.",
      "example": "Entity type: NON_PROFIT · Tax exempt status: 501(c)(3) · Use case: CHARITY — or drop the ask: \"Riverside Shelter: our winter programme opened today. Details: https://riversideshelter.org/winter Reply STOP to opt out.\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-COLLECTIONS-FIRST-PARTY",
      "slug": "msg-collections-first-party",
      "title": "First-party collections messaging must not use third-party framing",
      "statement": "Campaign content must not promote debt collection presented as third-party activity.",
      "rationale": "First-party collection — a business chasing its own invoice — is registrable, while third-party collection is not. Businesses routinely describe their own collections in agency language, which reads as the prohibited category and gets rejected for something they are not doing.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the messaging as first-party — your business, your customer, your invoice — and remove agency framing and any threatening language from the samples.",
      "example": "Acme Coffee: your invoice #4471 for $240 is now 14 days overdue. Pay at https://acmecoffee.com/pay/4471 or reply HELP to reach us. Reply STOP to opt out.",
      "notes": "Retryable rather than a hard stop, because the usual defect is describing legitimate first-party activity badly.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-CRYPTO",
      "slug": "msg-crypto",
      "title": "No cryptocurrency or high-risk investment solicitation",
      "statement": "Campaign content must not promote cryptocurrency, stock alerts, or high-risk investment offers.",
      "rationale": "Investment solicitation over SMS is heavily associated with pump-and-dump and fraud, so carriers refuse the category rather than assess individual senders. A regulated institution servicing existing accounts is judged separately.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30947",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30946",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Investment solicitation cannot be registered. Account servicing for existing customers of a regulated institution is a separate category — frame the campaign that way if it applies.",
      "catalogIds": [
        "MSG-223",
        "MSG-225"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-DEBT",
      "slug": "msg-debt",
      "title": "No third-party debt collection, relief, or credit repair content",
      "statement": "Campaign content must not promote third-party debt collection, debt relief, or credit repair services.",
      "rationale": "Third-party debt content combines a regulated activity with a high complaint rate, and carriers refuse it as a category. First-party collection — a business contacting its own customer about its own invoice — is treated differently, which is the distinction that decides whether your campaign is registrable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30943",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30949",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30950",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Third-party debt services cannot be registered. If you are collecting your own receivables from your own customers, say so explicitly in the description — that is a different, registrable category.",
      "notes": "Three Gen-2 codes, one per limb: 30943 third-party collection, 30949 debt reduction or consolidation, 30950 credit repair. All three are \"not eligible for resubmission\", so a rejection under any of them ends the campaign rather than starting a fix loop.",
      "catalogIds": [
        "MSG-218",
        "MSG-220"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-DECEPTIVE",
      "slug": "msg-deceptive",
      "title": "Content must not be deceptive, misleading, or impersonating",
      "statement": "Messages must not misrepresent the sender, imitate another brand, or make claims the business cannot support.",
      "rationale": "Deception and impersonation are Severity-0 findings that stop all messaging on the program, and they are the categories carriers escalate fastest because they map directly onto fraud. Unlike most defects, these attract per-message violation fees rather than a rejection you can fix and resubmit.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR",
        "FTC"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any content implying an affiliation the business does not have, any urgency claim that is untrue, and any sender identity other than the registered brand.",
      "catalogIds": [
        "MSG-244"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-DESC-QUALITY",
      "slug": "msg-desc-quality",
      "title": "Campaign description must explain who, what, and how they opted in",
      "statement": "The description must state who receives the messages, what those messages contain, and how recipients consented.",
      "rationale": "The description is the first thing a reviewer reads and the basis on which everything else is judged. A description that restates the use-case name gives them nothing to verify against, and is the most frequently cited reason for a campaign-description rejection.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write three things explicitly: the audience, the message content, and the opt-in source. Name the business and the website rather than describing a generic program.",
      "example": "Acme Coffee sends weekly promotional offers, new product announcements, and loyalty rewards updates to customers who opted in via the checkout consent checkbox at acmecoffee.com or in person at our Mission St store. Messages include discount codes and store event invitations.",
      "pitfalls": [
        "\"Marketing messages to our customers\" restates the use case and will be rejected as too vague."
      ],
      "catalogIds": [
        "CMP-033"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-FIELD-SIMILARITY",
      "slug": "msg-field-similarity",
      "title": "Description, message flow, and samples must not be the same text",
      "statement": "The description, message flow, and sample messages must each contain distinct content rather than repeated boilerplate.",
      "rationale": "These three fields answer different questions — what the campaign is, how consent is collected, and what gets sent. Pasting the same paragraph into all three leaves two of those questions unanswered and is explicitly coded as a rejection reason.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.message_flow + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30911",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write each field to its own purpose: the description explains the program, the message flow explains consent collection, and the samples show real messages.",
      "example": "Description: what the program sends and to whom. Message flow: \"Customers opt in at acmecoffee.com/signup by ticking…\". Samples: the actual texts.",
      "catalogIds": [
        "MSG-030"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-FIREARMS-ADJACENT",
      "slug": "msg-firearms-adjacent",
      "title": "No firearms-adjacent product marketing",
      "statement": "Campaign content must not promote firearms accessories, ammunition, or related product marketing.",
      "rationale": "The firearms prohibition covers ammunition, magazines, and components as well as complete weapons, which surprises retailers who reason that accessories are not weapons. Ranges, training and membership services remain registrable where they sell no product.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove product content of every kind, including accessories and ammunition. Service messaging for ranges, training and memberships remains registrable.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-GAMBLING",
      "slug": "msg-gambling",
      "title": "No gambling or sportsbook content",
      "statement": "Campaign content must not promote gambling, casino, sportsbook, or lottery content.",
      "rationale": "Gambling is banned by some providers and gated behind a special use case by others. The consumer-harm rationale is the same one behind age gating, and the divergence between providers means the safe default is to treat it as blocking.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30944",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm whether your provider offers a gambling or sweepstakes use case before proceeding; several refuse the category outright. Where a sweepstakes use case exists, register under it rather than under marketing.",
      "catalogIds": [
        "MSG-202"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-HIGH-RISK-FINANCIAL",
      "slug": "msg-high-risk-financial",
      "title": "No payday, short-term, or high-interest lending content",
      "statement": "Campaign content must not promote payday loans, short-term lending, or high-interest credit offers.",
      "rationale": "High-risk lending is the category most associated with consumer harm complaints on SMS, and it is coded separately from the ordinary direct-lending attribute. The distinction matters: declaring direct lending is a fixable attribute problem, while promoting payday-style credit is a business-model rejection with no remedy.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This business model cannot be registered. Regulated lenders sending servicing messages to existing customers are a different category — if that is you, remove the acquisition-style offers and set the direct-lending attribute instead.",
      "catalogIds": [
        "MSG-212"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-ILLEGAL",
      "slug": "msg-illegal",
      "title": "No content promoting illegal activity",
      "statement": "Campaign content must not promote illegal goods, services, or activity.",
      "rationale": "Promotion of illegal activity is a Severity-0 finding carrying \"cease all messaging\" rather than a rejection, and it is assessed against federal law even where a state permits the activity. The scope includes the landing page and any downloadable artifact, not only the message body.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This cannot be registered on any channel. Where the activity is state-legal but federally prohibited, federal law is the standard applied.",
      "catalogIds": [
        "MSG-256",
        "MSG-257"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-JOB-LEADGEN",
      "slug": "msg-job-leadgen",
      "title": "No third-party job-board or staffing lead generation",
      "statement": "Campaign content must not promote third-party job alerts or staffing lead generation.",
      "rationale": "Job-alert messaging is a classic lead-generation shape: the consumer opted in on an aggregator and the messages come from employers or agencies they never heard of. Where the employer collected the consent directly, the same content is registrable — the distinction is who holds the relationship.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Restrict messaging to candidates who opted in directly with the sending entity about that entity's own roles. Aggregated or purchased candidate lists cannot be registered.",
      "catalogIds": [
        "MSG-237"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-LEAD-GEN",
      "slug": "msg-lead-gen",
      "title": "No third-party lead generation or multi-level marketing",
      "statement": "Campaign content must not promote third-party lead generation, affiliate marketing, or MLM recruitment.",
      "rationale": "Lead generation depends on messaging people whose consent was collected by someone else, which is the transfer of consent the whole framework prohibits. MLM recruitment is refused for the same structural reason plus its complaint profile.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30468",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This cannot be registered. If you run your own referral programme and collected the consents yourself, describe it that way and make clear no third-party list is involved.",
      "catalogIds": [
        "MSG-234",
        "MSG-238",
        "MSG-239"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-PHARMA",
      "slug": "msg-pharma",
      "title": "No prescription drug or controlled-substance promotion",
      "statement": "Campaign content must not promote prescription drugs, controlled substances, or unapproved health products.",
      "rationale": "Promoting prescription medication over SMS raises both regulatory and safety exposure, and carriers refuse the category. Pharmacy operations — refill reminders and pickup notices to existing patients — remain registrable, and confusing the two is the common failure.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30941",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove promotional drug content. Reframe as operational patient messaging — refills, pickups, appointments — which is registrable under CUSTOMER_CARE or ACCOUNT_NOTIFICATION.",
      "catalogIds": [
        "MSG-194",
        "MSG-196"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-PHISHING",
      "slug": "msg-phishing",
      "title": "Content must not solicit credentials or sensitive data",
      "statement": "Messages must not ask recipients for passwords, full card numbers, SSNs, or account credentials.",
      "rationale": "Credential solicitation over SMS is indistinguishable from smishing from the carrier’s side, and it is filtered and fined accordingly. Legitimate businesses do not need it — verification belongs behind an authenticated session, not in a text reply.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any request for credentials or full financial identifiers. Link to an authenticated page on your own domain instead of collecting data by reply.",
      "catalogIds": [
        "MSG-248"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-REAL-ESTATE-WHOLESALE",
      "slug": "msg-real-estate-wholesale",
      "title": "No unsolicited property-acquisition or wholesaling outreach",
      "statement": "Campaign content must not promote real-estate wholesaling and unsolicited property-acquisition outreach.",
      "rationale": "\"We want to buy your house\" texting is sent to property owners from public records with no consent whatsoever, and it is one of the highest-complaint categories on the network. Ordinary real-estate practice with consenting clients is unaffected.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Messaging property owners sourced from public records cannot be registered — there is no consent to point at. Restrict to clients who opted in directly with your brokerage.",
      "catalogIds": [
        "MSG-232",
        "MSG-233"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-DISTINCT",
      "slug": "msg-samples-distinct",
      "title": "Sample messages must be materially different from one another",
      "statement": "Each sample must illustrate a genuinely different message the campaign will send, not a near-duplicate of another sample.",
      "rationale": "The point of multiple samples is to show a reviewer the range of what will actually be sent. Three variations on the same sentence demonstrate nothing about the rest of the program, so reviewers read near-duplicates as padding to satisfy a field count and reject the campaign as unrepresentative. Exact-duplicate detection catches only the laziest version of this; the common failure is three messages that differ by a product name.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace near-duplicate samples with messages covering genuinely different scenarios from the same campaign — for a marketing campaign, one promotion, one product launch, one loyalty or event message rather than three discount offers.",
      "example": "1. \"Acme Coffee: 20% off all beans this weekend. Shop https://acmecoffee.com/sale Reply STOP to opt out, HELP for help.\"\n2. \"Acme Coffee: your rewards balance is 240 points — enough for a free drink. Reply STOP to opt out.\"\n3. \"Acme Coffee: our Mission St store opens Saturday at 8am, first 50 customers get a free pastry. Reply STOP to opt out.\"",
      "pitfalls": [
        "Changing only a discount percentage or a product name between samples is not material difference.",
        "Do not solve this by making one sample an opt-out confirmation — samples must show campaign content."
      ],
      "catalogIds": [
        "MSG-029"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-NOT-ONLY-ADMIN",
      "slug": "msg-samples-not-only-admin",
      "title": "Samples must show campaign content, not only opt-out or help replies",
      "statement": "The sample set must include actual campaign messages, not exclusively confirmations, HELP replies, or opt-out acknowledgements.",
      "rationale": "Administrative auto-replies are required separately and are configured in their own fields. A sample set made only of them shows a reviewer nothing about what the campaign actually sends, which is the one thing samples exist to demonstrate.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace administrative samples with real campaign messages. Put the HELP and STOP replies in the dedicated auto-reply fields where they belong.",
      "example": "Acme Coffee: your table for 4 on Fri 7pm is confirmed. Reply STOP to opt out, HELP for help.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-OPTOUT",
      "slug": "msg-samples-optout",
      "title": "At least one sample must show opt-out language",
      "statement": "Sample messages must demonstrate that recipients are told how to stop receiving messages.",
      "rationale": "Reviewers use the samples to confirm the opt-out instruction reaches consumers in the actual message body, not just in the registration paperwork. Where every sample omits it, the reasonable inference is that live traffic omits it too.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "CTIA",
        "Bandwidth",
        "Plivo",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "8100",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to opt out\" to your samples. Include it in every promotional sample; for transactional programs at minimum the first message in a series.",
      "example": "Acme Coffee: 20% off all beans this weekend. Reply STOP to opt out, HELP for help.",
      "catalogIds": [
        "MSG-047"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-REPRESENTATIVE",
      "slug": "msg-samples-representative",
      "title": "Sample messages must be realistic, not placeholders",
      "statement": "Samples must be actual messages the campaign will send, with real brand and content specifics.",
      "rationale": "Reviewers compare your samples against your website and use case to decide whether the program is what it claims to be. Generic filler makes that impossible, and it correlates strongly with campaigns that later send something entirely different — which is why it is treated as a signal of bad faith rather than a formatting problem.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite each sample as a message you would genuinely send tomorrow, naming your real brand, real products or services, and a real destination URL on your own domain.",
      "example": "Acme Coffee: your order #4471 is ready for pickup at our Mission St store until 6pm today. Reply STOP to opt out, HELP for help.",
      "catalogIds": [
        "MSG-022",
        "MSG-024"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-USECASE",
      "slug": "msg-samples-usecase",
      "title": "Sample messages must match the declared use case",
      "statement": "The content of the samples must be consistent with the campaign use case selected.",
      "rationale": "The use case determines the consent standard, the throughput a carrier grants, and the filtering applied to your traffic. A campaign registered as ACCOUNT_NOTIFICATION that sends promotions is receiving treatment it did not qualify for, so the mismatch is a registration integrity problem rather than a labelling error.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Bandwidth",
          "code": "8200",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either change the use case to match what you actually send, or replace the off-pattern samples. If you genuinely send both promotional and transactional content, select MIXED and show both — but note that MIXED requires promotional-grade consent.",
      "example": "For ACCOUNT_NOTIFICATION: \"Acme Coffee: your payment of $24.50 was received. View receipt: https://acmecoffee.com/r/4471 Reply STOP to opt out.\"",
      "pitfalls": [
        "A single promotional sample inside an otherwise transactional set is enough to trigger this — Bandwidth codes it separately as 8200."
      ],
      "catalogIds": [
        "MSG-141"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SEO-SPAM",
      "slug": "msg-seo-spam",
      "title": "No unsolicited agency or SEO service pitching",
      "statement": "Campaign content must not promote unsolicited SEO, web design, or marketing agency solicitation.",
      "rationale": "Cold agency pitching to businesses scraped from directories is unsolicited commercial messaging with no consent behind it, and it is one of the most frequently reported patterns on the network. Messaging clients who engaged you is entirely different.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Cold B2B prospecting by SMS cannot be registered — the recipients never consented. Restrict messaging to clients who opted in with you directly.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-ALCOHOL",
      "slug": "msg-shaft-alcohol",
      "title": "Alcohol content requires robust age gating",
      "statement": "Campaign content must not promote alcohol sales or promotion.",
      "rationale": "The A in SHAFT, and the letter that genuinely has a path. Alcohol sits in a contested band: Bandwidth, Bird and Telnyx accept it behind a date-of-birth age gate, Twilio kills the rejected campaign but takes a new one once the gate exists. Because the strictest authority treats the submitted campaign as terminal we surface it as blocking, but the fix is real, and telling an operator otherwise costs them a legitimate programme.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30955",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30956",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "706",
          "remediable": true
        },
        {
          "provider": "Telnyx",
          "code": "706",
          "remediable": true
        },
        {
          "provider": "Bird",
          "code": "706",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Build a 21+ age gate that collects a full date of birth — day, month and year typed in, not a yes/no prompt and not a tick-box — on the opt-in flow and on every website a message links to, then set the age-gated attribute on the campaign. Done when a visitor who enters an under-21 date is refused and cannot proceed by going back. Expect to file a NEW campaign rather than edit this one: Twilio treats the rejected campaign as spent even though it advises building exactly this gate.",
      "pitfalls": [
        "A gate that only appears on the home page does not settle it — the requirement runs to every linked destination, and reviewers test the deep link from the sample message rather than the site root.",
        "Age gating answers who may see the offer and nothing about what the offer says: copy that sells drinking to excess is judged separately by MSG-177 and the gate does not touch it."
      ],
      "notes": "Providers genuinely diverge, and the divergence is in the remediability rather than the prohibition: Twilio 30955/30956 are non-resubmittable for the rejected campaign while Bandwidth DCA2 706, Telnyx 706 and Bird 706 all accept a resubmission once a DOB gate exists, and Twilio toll-free 30456 is conditional on 21+ with full date-of-birth collection. Strictest reading kept for the verdict, the workable path kept in the remediation.",
      "catalogIds": [
        "MSG-178"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-FIREARMS",
      "slug": "msg-shaft-firearms",
      "title": "No firearms sales or promotion",
      "statement": "Campaign content must not promote firearms, ammunition, or weapon parts.",
      "rationale": "The F in SHAFT, and the letter where the published guidance most tempts you into the wrong fix. T-Mobile, Verizon, Bandwidth and Bird all treat firearms as age-gateable, so a dealer reads the codes, builds the gate, and is then refused anyway by Twilio and AWS, which ban the category outright with no path. Under the strict-superset rule the ban is what the registry applies, which means the age gate is wasted work here even though it is the correct answer one letter over.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30957",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30457",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "702",
          "remediable": true
        },
        {
          "provider": "Bird",
          "code": "702",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not build an age gate for this: unlike alcohol, gating does not open a path at the strictest providers and Twilio publishes no conditional route on short code, toll-free or long code. Remove product content of every kind — complete firearms, ammunition, magazines and components — and register on services instead: range bookings, training and safety courses, membership renewals and event logistics are registrable when nothing is offered for sale. Done when no message, no description sentence and no linked page names a product with a price.",
      "pitfalls": [
        "Accessories are inside the category, not outside it — optics, magazines and ammunition are refused on the same code as the weapon, which is what catches retailers who moved their copy to \"gear\" and resubmitted."
      ],
      "notes": "Discharges the catalog's remediation constraint (MSG-185) as well as the content prohibition — the withheld age-gate path is the substance of that row. MSG-185 is recorded as Twilio-conditional in the catalog; the rule is left universal because the catalog's own reconciliation resolves the divergence to the ban, and a provider tag here would hand the age-gate path back to everyone who is not on Twilio.",
      "catalogIds": [
        "MSG-181",
        "MSG-185"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-HATE",
      "slug": "msg-shaft-hate",
      "title": "No hate speech",
      "statement": "Campaign content must not promote hate speech or content attacking a protected group.",
      "rationale": "The H in SHAFT. This is one of the few categories where carriers act on a single message rather than a pattern, because the reputational exposure of carrying it is immediate. It is also the letter with the most uniform treatment — every provider that publishes a code refuses without condition, so unlike alcohol or tobacco there is no provider to shop for and nothing to build.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30954",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "705",
          "remediable": false
        },
        {
          "provider": "Infobip",
          "code": "7012",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the content entirely. There is no compliant framing, no age gate and no use case that carries it, and no provider offers a conditional path — so unlike the alcohol and tobacco letters there is nothing to build and nothing to confirm with your provider first.",
      "notes": "Twilio codes hate and violence to one Gen-2 code (30954), so a rejection under it does not say which fired. MSG-170 holds the violence side and MSG-171 the hostility aimed at the recipient.",
      "catalogIds": [
        "MSG-168"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-SEX",
      "slug": "msg-shaft-sex",
      "title": "No adult or sexually explicit content",
      "statement": "Campaign content must not promote sexually explicit or adult content.",
      "rationale": "The S in SHAFT, and the one letter with no path at all. Carriers treat adult content as unsuitable for a channel with no age verification at the transport layer, and Twilio codes it as not eligible for resubmission. Operators arrive here expecting the alcohol treatment — build an age gate, come back — and that expectation is the expensive part: acting on it burns a campaign that cannot be recovered, on top of the build.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Telnyx",
          "code": "703",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not build an age gate for this — it is not the fix, and no amount of verification makes adult content registrable on 10DLC under any use case. The only thing that clears the finding is that the content is not adult: if the campaign is a clinical or sexual-health service being read as adult, say who the recipients are and what the service does in the terms a clinician would use. Otherwise adult programmes need a different channel and a different carrier relationship entirely.",
      "pitfalls": [
        "Bandwidth's Proxy path and Infobip both publish a conditional age-gate code for sexual content, which reads as a route and is not one under the strict reading — spending a build cycle on it costs you the campaign at Twilio and gains nothing anywhere else."
      ],
      "notes": "Discharges the catalog's remediation constraint (MSG-167) as well as the content prohibition: the rule exists as much to withhold the age-gate path as to detect the category. Divergence recorded — Bandwidth Proxy 7006 and Infobip 7006 are conditional on a robust age gate, while Twilio 30953, Bandwidth DCA2 703, Bird 703, Telnyx 703 and AWS all refuse outright. Canonical resolution is terminal.",
      "catalogIds": [
        "MSG-162",
        "MSG-167"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-TOBACCO",
      "slug": "msg-shaft-tobacco",
      "title": "No tobacco, vape, or nicotine product content",
      "statement": "Campaign content must not promote tobacco, vaping, or nicotine products.",
      "rationale": "The T in SHAFT, and the letter that splits in two. Twilio permits tobacco on short code behind an age gate and never on long code or toll-free — which is to say never on 10DLC — while vape, e-cigarettes and e-liquid are refused on every channel including the one that carries tobacco. Retailers who sell both read the tobacco allowance and assume it covers the vape wall, and that assumption is what the rejection is usually about.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30958",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30458",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "707",
          "remediable": true
        },
        {
          "provider": "Telnyx",
          "code": "707",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take the product out of the 10DLC campaign. The age gate that rescues alcohol reaches tobacco only on short code, which is a different registration on a different number type — and it does not reach vape, e-cigarettes, e-liquid or pods on any channel at all, so separating the vape lines out is the first thing to check. Cessation and nicotine-replacement programmes are a distinct category and stay registrable when the copy is about quitting rather than about the product.",
      "pitfalls": [
        "Splitting the vape products into a second campaign does not help — the vape ban is by channel, not by campaign, so the second campaign is refused on the same code."
      ],
      "notes": "Bandwidth (\"Alcohol and Tobacco can be supported with robust age-gating and proper opt-in\" on 10DLC), Telnyx 707 and Bird 707 all accept tobacco behind a date-of-birth gate, so this is a genuine divergence rather than a strictness difference. Strictest reading kept; the vape half is uniform across every source.",
      "catalogIds": [
        "MSG-180"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SPAM-STYLE",
      "slug": "msg-spam-style",
      "title": "Messages must not use spam-signal formatting",
      "statement": "Messages must avoid all-caps shouting, excessive punctuation, and obfuscated wording used to evade filters.",
      "rationale": "Carrier filters score message shape as well as content, so these patterns reduce deliverability even on an approved campaign. Character substitution and spacing tricks are treated as deliberate filter evasion, which escalates from filtering to enforcement.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write samples in ordinary sentence case with normal punctuation, and remove any character substitution.",
      "example": "Acme Coffee: 20% off all beans this weekend. Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "\"FR€E\" or \"C.A.S.H\" reads as deliberate filter evasion, which is worse than the word it hides."
      ],
      "catalogIds": [
        "MSG-126",
        "MSG-127"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SWEEPSTAKES-DECEPTIVE",
      "slug": "msg-sweepstakes-deceptive",
      "title": "No prize or winner claims without a genuine promotion",
      "statement": "Campaign content must not promote prize, winner, or sweepstakes claims not backed by a real promotion.",
      "rationale": "A \"you have won\" message with no underlying promotion is straightforward deception and is filtered as fraud rather than as marketing. Genuine sweepstakes are registrable under the sweepstakes use case with the required official rules.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove prize claims unless a real promotion exists with published official rules. Where it does, register under the sweepstakes use case and link the rules from the message.",
      "catalogIds": [
        "MSG-207"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-TELEHEALTH-ACQUISITION",
      "slug": "msg-telehealth-acquisition",
      "title": "No telehealth or supplement acquisition marketing",
      "statement": "Campaign content must not promote telehealth acquisition marketing and unproven supplement claims.",
      "rationale": "Health-outcome marketing over SMS combines regulatory exposure with a high rate of overstated claims, and weight-loss and hormone offers in particular are screened aggressively. Clinical operations messaging to existing patients is registrable and should be framed that way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove acquisition and outcome-claim content. Patient operations messaging — appointments, refills, results availability — is registrable under CUSTOMER_CARE or ACCOUNT_NOTIFICATION.",
      "catalogIds": [
        "MSG-251"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-TIMESHARE",
      "slug": "msg-timeshare",
      "title": "No timeshare or vacation-club solicitation",
      "statement": "Campaign content must not promote timeshare, vacation club, or holiday-ownership solicitation.",
      "rationale": "Timeshare marketing has a long enforcement history for high-pressure and deceptive tactics, and carriers screen it as a category. Ordinary travel and hospitality messaging is unaffected, so the line is the ownership-sales pitch rather than the industry.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove ownership-sales content. Reservation, itinerary and stay-service messaging to your own customers remains registrable under CUSTOMER_CARE or ACCOUNT_NOTIFICATION.",
      "catalogIds": [
        "MSG-231"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-URL-OWNERSHIP",
      "slug": "msg-url-ownership",
      "title": "Links must resolve to a domain the brand owns",
      "statement": "URLs in messages must point at the brand’s own domain or a dedicated branded short domain, and must identify the owner.",
      "rationale": "Public shorteners hide the destination, so carriers cannot assess where a message leads and block them categorically. The standard is stricter than \"avoid bit.ly\": the shortener must be dedicated to the sender, and the destination must identify the business, because shared redirect infrastructure is what makes a link untraceable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30892",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30963",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use full URLs on your own domain, or a short domain dedicated exclusively to your business. Confirm the destination page identifies the business by name and carries contact details.",
      "example": "Shop the sale: https://acmecoffee.com/sale  —  or a dedicated short domain such as https://acme.link/sale",
      "pitfalls": [
        "A branded short domain on shared redirect infrastructure still fails the dedicated-IP requirement.",
        "The same defect is remediable under the old code and NOT resubmittable under the new one — assume the strict reading."
      ],
      "catalogIds": [
        "MSG-094",
        "MSG-099",
        "MSG-100"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-VARIABLES",
      "slug": "msg-variables",
      "title": "Template variables must use square-bracket notation",
      "statement": "Personalisation placeholders in samples should use [square brackets] rather than code syntax.",
      "rationale": "Square brackets are the convention reviewers expect, and they make clear which parts vary at send time. Raw code syntax reads as an unfinished integration and invites the reviewer to treat the sample as a placeholder rather than a real message.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Convert placeholders to square-bracket notation and give them readable names describing the value.",
      "example": "Acme Coffee: hi [first name], your order [order number] ships today. Reply STOP to opt out.",
      "pitfalls": [
        "Bracket placeholders belong in samples only — never send them in live traffic."
      ],
      "catalogIds": [
        "MSG-025"
      ],
      "phase": "approval",
      "automated": true
    }
  ]
}
