SMS: unsendable numbers

When a provider tells us that a phone number can't receive SMS, we stop sending SMS to that number so you don't pay to text phone numbers that can't receive your messages.

Some phone numbers can’t receive SMS: they’re invalid, they belong to a landline, or the handset can’t be reached. Every attempt to send to these numbers costs you money, drags down your sender reputation, and clutters your delivery metrics with bounces.

To prevent this, Customer.io watches for SMS bounces. When a provider tells us that a phone number can’t receive SMS, we flag the person and stop sending SMS/MMS to that number. Customer.io does this for every SMS provider you can send through: Twilio, Infobip, Vonage, Sinch, and Bird. The way we handle bounced SMS messages is very similar to the way we handle SMS opt outs.

How it works

When an SMS bounces with a provider error telling us that the number can’t receive SMS, we set a reserved cio_sms_unsendable_reason attribute on the person’s profile. The value records why the number can’t receive SMS:

ValueWhat it means
invalidThe number isn’t a valid phone number that can receive SMS.
landlineThe number belongs to a landline, which can’t receive SMS.
unreachableThe carrier or handset can’t be reached for SMS.

The attribute also tells Customer.io to drop future SMS to that person’s phone number. We record these messages with the dropped metric, the same metric we use when we skip messages for opted-out recipients. Dropped messages don’t count as bounces and don’t cost you a provider request.

Dropped SMS messages don’t affect other message types

The cio_sms_unsendable_reason attribute is tied to the person’s phone attribute and only applies to SMS messages. Customer.io will still send other kinds of messages to the user—email, push, in-app, etc.

WhatsApp bounces don’t set or rely on this attribute. WhatsApp bounces don’t include information about why they bounced, so we don’t apply or observe the cio_sms_unsendable_reason attribute for WhatsApp messages.

If you use a different attribute to represent the phone number, the cio_sms_unsendable_reason attribute doesn’t apply. For example, if you store a preferred_phone attribute and try to send to that rather than phone, any change to the cio_sms_unsendable_reason attribute won’t apply.

Customer.io automatically clears cio_sms_unsendable_reason when a phone number changes

If you update a person’s phone attribute, we’ll clear the cio_sms_unsendable_reason attribute so the new number can receive SMS messages. You don’t need to remove the cio_sms_unsendable_reason attribute yourself.

Temporary errors don’t treat recipients as unsendable

Some provider errors describe a problem that might go away, like a phone that’s switched off or out of coverage. Twilio’s 30003 (unreachable destination handset) and 30006 (landline or unreachable carrier) are common examples, as is Bird’s unreachable. Customer.io doesn’t set cio_sms_unsendable_reason for these errors, because the same number might receive your next message.

If you want to stop texting numbers that keep failing with these errors, you can decide what “keeps failing” means for your audience. Create a segment with an SMS error-code condition, like “any SMS has bounced with error code 30003, 30006 at least 5 times in the past 30 days.” Then use the segment to exclude those people from your SMS messages.

Find and segment unsendable numbers

You can segment on the cio_sms_unsendable_reason attribute like any other attribute. This can help you find people who can’t receive SMS. You might do this either to exclude them from your automation workflows or to clean out inactive people. For example, you might create a segment with the following conditions:

  • Segment where cio_sms_unsendable_reason exists to find everyone with a number that can’t receive SMS.
  • Segment where cio_sms_unsendable_reason equals landline to find people whose number is a landline, so you can reach them on another channel instead.

cio_sms_unsendable_reason is a reserved attribute that Customer.io sets and clears for you. You can’t set or edit its value yourself.

Updated October 15, 2026