Building phone number validation system with AWS Pinpoint

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
endrescue 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
endThere. 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!