Building phone number validation system with AWS Pinpoint

Shrinath M Aithal
Aug 31, 2018 · 3 min read
Random image with dirty laptop

Lets say I have a CSV with phone numbers in it. I want to verify or filter those phone numbers for geo location like city, state, region, carrier, timezone, and type of phone (mobile/landline/voip/etc). Quick googling brings few links related to SMS: Numverify, Nexmo, etc., most required a subscription or is limited in some way.

What caught my attention is Amazon’s offering — Pinpoint. It is a broad offering that can help with marketing campaigns, user engagement, analytics on engagement, etc., which includes SMS service and phone number validation.

At $0.006 per call (at the time of writing), it does not cost much. There may be other free tools out there, but I am skeptical about how shady they turn out to be, caching my valuable database; Plus, if I am already using other AWS components for various needs, it is a really quick integration to verify phone numbers with something as simple as a CLI call, with AWS credentials/profile already setup —

aws pinpoint phone-number-validate --number-validate-request IsoCountryCode="+1",PhoneNumber="+14085550100"

Output looks like this-

This is good enough for randomly checking phone numbers. But I have a mammoth CSV of few Gigabytes to process. A quick and dirty solution in RoR since I already have a rails project running -

Add required AWS SDK in Gemfile

#Gemfile
gem 'aws-sdk-pinpoint'

Drop this method wherever it fits appropriately, probably in under /lib under a class-

PHONE_COLUMN = 34.freeze

def process_phone_numbers(filename)
pinpoint = Aws::Pinpoint::Client.new
CSV.foreach(filename, headers: true) do |row, i|
query = {
number_validate_request: {
iso_country_code: '+1',
phone_number: '+1' + row[PHONE_COLUMN]
}
}
response = pinpoint.phone_number_validate(query).number_validate_response
pp response
end
end

This is simply pretty-printing the responses to screen, not much useful. Lets create another CSV file with only those rows which satisfy a certain condition, say only mobile numbers. Modify the above file to write a row to new CSV conditionally.

First, we need a new CSV file. I create that by inserting _verified to the filename that already exists.

def process_phone_numbers(filename)
pinpoint = Aws::Pinpoint::Client.new

verified_filename = filename.dup
verified_filename.insert(-5, '_verified')
...

Next, I open that CSV to write and loop through conditionally

def process_phone_numbers(filename)
pinpoint = Aws::Pinpoint::Client.new

verified_filename = filename.dup
verified_filename.insert(-5, '_verified')

CSV.open(verified_filename, 'wb') do |v_csv|
CSV.foreach(filename, headers: true) do |row, i|
{
number_validate_request: {
iso_country_code: '+1',
phone_number: '+1' + row[PHONE_COLUMN]
}
}

response = pinpoint.phone_number_validate().number_validate_response
next unless response[:phone_type] == "MOBILE" v_csv << row
v_csv.flush
Rails.logger.info "⛳️ Wrote #{row[PHONE_COLUMN]} to file"
end
end
end

Now I actually have a CSV file with only rows that contain mobile numbers! And yes, I have a flag in my log. ⛳️

What if I had multiple rows having same number? What if it was a user/people database where multiple people shared same number? What if AWS threw error? (It does sometimes if you send a bad number)

Cache it. Catch it.

For this, I separate out the AWS query part and validating part to new functions.

def valid_mobile?(number)
r = phone_number_validate number
r[:phone_type] == 'MOBILE'
end

def
pinpoint_client
@pinpoint_client ||= Aws::Pinpoint::Client.new
end

def
phone_number_validate(number)
Rails.cache.fetch(number, expires_in: 1.year.from_now) do
query = { number_validate_request: { iso_country_code: '+1', phone_number: number } }
r = pinpoint_client.phone_number_validate(query)
r.number_validate_response.to_h
end
rescue StandardError => e
Rails.logger.error e.message
raise e # Lets stop if there are problems. Anyway there is cache.
end

The original method should now look like this to make use of above-

def process_phone_numbers(filename)

verified_filename = filename.dup
verified_filename.insert(-5, '_verified')

CSV.open(verified_filename, 'wb') do |v_csv|
CSV.foreach(filename, headers: true) do |row, i|

next unless valid_mobile? row[PHONE_COLUMN]

v_csv << row
v_csv.flush
Rails.logger.info "⛳️ Wrote #{row[PHONE_COLUMN]} to file"
end
end
end

There. Now we have a phone_number_validate method that can be reused, has been cached. The process_phone_numbers method can process CSV.

Of course, there are other corner cases to handle, like skipping empty rows, handling numbers in format +1(123)-(456)-7890, etc. There are enough StackOverflow questions covering this, like using supremely elaborate regex.

These, I leave it as exercise to the reader!

Shrinath M Aithal

Written by

Developer, DevOps, cloud architecture enthusiast from Bengaluru, India

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade