<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: A A</title>
    <description>The latest articles on DEV Community by A A (@aadeveloper).</description>
    <link>https://dev.to/aadeveloper</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4160174%2Ff5944533-26f2-4008-b804-a2ac51118f34.png</url>
      <title>DEV Community: A A</title>
      <link>https://dev.to/aadeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aadeveloper"/>
    <language>en</language>
    <item>
      <title>What a phone number tells you before you send an SMS</title>
      <dc:creator>A A</dc:creator>
      <pubDate>Sat, 03 Oct 2026 16:56:03 +0000</pubDate>
      <link>https://dev.to/aadeveloper/what-a-phone-number-tells-you-before-you-send-an-sms-cji</link>
      <guid>https://dev.to/aadeveloper/what-a-phone-number-tells-you-before-you-send-an-sms-cji</guid>
      <description>&lt;p&gt;If you send an SMS to a landline it disappears. No bounce, no error you can act on, and you are still billed for it. The same goes for a disconnected number and for plenty of VoIP numbers. The fix is to find out what a number actually is before you send to it.&lt;/p&gt;

&lt;p&gt;Here is what a phone number can tell you, and what it cannot.&lt;/p&gt;

&lt;p&gt;Start by normalizing to E.164&lt;br&gt;
Before anything else, get the number into E.164: a plus sign, the country code, then the national number, with no spaces, dashes or brackets.&lt;/p&gt;

&lt;p&gt;07400 123456      -&amp;gt;  +447400123456&lt;br&gt;
(202) 555-0143    -&amp;gt;  +12025550143&lt;br&gt;
Two things go wrong at this step.&lt;/p&gt;

&lt;p&gt;First, a national number with no country code is ambiguous. 07400123456 is a UK mobile, but a bare 7400123456 could belong to several countries. You need a default country to parse against, usually the country of the account that submitted the number.&lt;/p&gt;

&lt;p&gt;Second, people paste extensions, letters and stray leading zeros. Anything that fails to parse should stop here rather than being sent anywhere downstream.&lt;/p&gt;

&lt;p&gt;Line type is the decision you actually care about&lt;br&gt;
Once you have a valid E.164 number, the useful question is what kind of line it is.&lt;/p&gt;

&lt;p&gt;mobile: can receive SMS&lt;/p&gt;

&lt;p&gt;fixed_line: cannot receive SMS in most countries&lt;/p&gt;

&lt;p&gt;voip: sometimes can, often cannot, and a meaningful fraud signal at signup&lt;/p&gt;

&lt;p&gt;toll_free, premium_rate, pager: do not send&lt;/p&gt;

&lt;p&gt;A validation library on its own will not tell you this reliably. Number ranges are allocated by type, so a library that knows only the allocation table tells you what the range was issued as, not what the number is today.&lt;/p&gt;

&lt;p&gt;Portability is why the allocated range is not enough&lt;br&gt;
Most countries let a subscriber keep their number when they switch network. That is mobile number portability, and it means the carrier that was allocated a range is often no longer the carrier serving a given number in it.&lt;/p&gt;

&lt;p&gt;If you route SMS by allocated range in a country with portability, you pay the wrong carrier's rate and your delivery rate quietly drops. Getting the current network needs a live lookup, not a static table.&lt;/p&gt;

&lt;p&gt;What a lookup gives you back&lt;br&gt;
A useful response is more than valid: true. A real one looks like this:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "input_phone": "+447400123456",&lt;br&gt;
  "normalized_e164": "+447400123456",&lt;br&gt;
  "valid": true,&lt;br&gt;
  "country": "GB",&lt;br&gt;
  "country_name": "United Kingdom",&lt;br&gt;
  "region": "Europe",&lt;br&gt;
  "line_type": "mobile",&lt;br&gt;
  "carrier": "EE",&lt;br&gt;
  "sms_ready": true,&lt;br&gt;
  "call_ready": true,&lt;br&gt;
  "reason": "valid_mobile",&lt;br&gt;
  "cached": false,&lt;br&gt;
  "checked_at": "2026-06-30T10:04:11.204Z",&lt;br&gt;
  "credits_debited": 1&lt;br&gt;
}&lt;br&gt;
The fields worth building logic on:&lt;/p&gt;

&lt;p&gt;valid together with reason, so you know why a number failed and not just that it did&lt;/p&gt;

&lt;p&gt;line_type, the routing decision&lt;/p&gt;

&lt;p&gt;carrier, the current network after portability&lt;/p&gt;

&lt;p&gt;sms_ready and call_ready, if you would rather not write that logic yourself&lt;/p&gt;

&lt;p&gt;cached, so you can tell a fresh lookup from a cached one&lt;/p&gt;

&lt;p&gt;Three places the check pays for itself&lt;br&gt;
At signup. Reject a number that cannot receive your OTP before the user presses send, instead of after they wait for a code that was never going to arrive.&lt;/p&gt;

&lt;p&gt;Before a campaign. Split the list on sms_ready and suppress the rest. You pay per message either way.&lt;/p&gt;

&lt;p&gt;Before a dialler run. call_ready and line_type keep agents off numbers that will not connect.&lt;/p&gt;

&lt;p&gt;Cache, and deduplicate first&lt;br&gt;
Lookups cost money per number, so cache them. The network a number sits on does not change often, and a 24 hour cache absorbs most repeat traffic in practice.&lt;/p&gt;

&lt;p&gt;Deduplicate inside a batch before you send it, too. The same number turns up several times in one CSV more often than you would expect, and each copy would otherwise be a separate paid lookup.&lt;/p&gt;

&lt;p&gt;Trying it&lt;br&gt;
Disclosure: this is our own product. We built Carrier Reveal around exactly this, so treat the example below as one implementation rather than a recommendation. The API is one POST per number, or up to 100 in a batch:&lt;/p&gt;

&lt;p&gt;curl -X POST &lt;a href="https://api.phonelistcleaner.com/v1/lookup" rel="noopener noreferrer"&gt;https://api.phonelistcleaner.com/v1/lookup&lt;/a&gt; \&lt;br&gt;
  -H "Authorization: Bearer plc_" \&lt;br&gt;
  -H "Content-Type: application/json" \&lt;br&gt;
  -d '{"phone":"+447400123456","defaultCountry":"GB"}'&lt;br&gt;
The docs are at Carrier Lookup API | Carrier Reveal. Twilio Lookup and Numverify solve the same problem if you would rather use something you already have.&lt;/p&gt;

&lt;p&gt;The general point holds whatever you use: normalize first, check the line type, and never trust the allocated range in a country with number portability.&lt;/p&gt;

</description>
      <category>api</category>
      <category>sms</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
