Skip to content

Instantly share code, notes, and snippets.

@kevboutin
Created April 8, 2024 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevboutin/67b2d879910ef91d77246d6387fbc517 to your computer and use it in GitHub Desktop.
Save kevboutin/67b2d879910ef91d77246d6387fbc517 to your computer and use it in GitHub Desktop.
SMS Utility using Twilio
const twilio = require('twilio');
const TAG = 'smsUtility';
/**
* Send an SMS text message.
*
* Example response from Twilio:
* {
* "account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
* "api_version": "2010-04-01",
* "body": "McAvoy or Stewart? These timelines can get so confusing.",
* "date_created": "Thu, 30 Jul 2015 20:12:31 +0000",
* "date_sent": "Thu, 30 Jul 2015 20:12:33 +0000",
* "date_updated": "Thu, 30 Jul 2015 20:12:33 +0000",
* "direction": "outbound-api",
* "error_code": null,
* "error_message": null,
* "from": "+15017122661",
* "messaging_service_sid": "MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
* "num_media": "0",
* "num_segments": "1",
* "price": null,
* "price_unit": null,
* "sid": "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
* "status": "sent",
* "subresource_uris": {
* "media": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXX/Media.json"
* },
* "to": "+15558675310",
* "uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages/SMXXXXXXXXXXXXXXXXXXXX.json"
* }
*
* @param {string} locale The recipient user ISO standard locale.
* @param {string} msg The message to send. The character limit for a single SMS message is 160 characters; however,
* most modern phones and networks support concatenation and segmenting and rebuild messages up
* to 1600 characters. Messages not using GSM-7 encoding are limited to 67 characters. Large
* messages are segmented into 153 character segments and sent individually then rebuilt by the
* recipients device. For example, a 161 character message will be sent as two messages, one with
* 153 characters and the second with 8 characters. Messages containing any UCS-2 characters are
* limited to 67 characters and will be concatenated into 67 character message segments, even if
* the messages contain less than 160 characters. Use the following tool for testing message
* splitting: http://chadselph.github.io/smssplit/
* @param {string} recipient The recipient phone number. The format should be like this: +15558675310, which is
* standard E.164 format (https://support.twilio.com/hc/en-us/articles/223183008-Formatting-International-Phone-Numbers).
* @param {string} sender The sender phone number or null to use the default from the configuration. This is the Twilio
* number purchased (https://www.twilio.com/console/phone-numbers/incoming). Use the E.164
* format.
* @param {object} config The Twilio configuration that consists of the default sender, account SID and auth token.
* @return {Promise} A promise with the result or error.
*/
exports.send = async (locale, msg, recipient, sender, config) => {
if (!sender) {
sender = config.fromNumber ? config.fromNumber : null;
}
if (msg && recipient) {
console.log(`${TAG}::Sending SMS text message to ${recipient} from ${sender}.`);
const countryCode = this.getCountryCode(locale);
const client = twilio(config.accountSid, config.authToken);
const recipientPhoneNumber = await this.normalizePhoneNumber(client, recipient, countryCode);
const message = await client.messages.create({
body: msg,
to: recipientPhoneNumber,
from: sender,
});
return message;
}
throw new Error(`Message or recipient is missing.`, msg, recipient);
};
/**
* Get the country code from the locale.
*
* @param {string} locale The ISO standard locale.
* @return {*} The country code or null if there was a problem.
*/
exports.getCountryCode = (locale) => {
if (!locale || locale.length < 2) {
return null;
}
return locale.slice(-2);
};
/**
* Normalize a phone number.
*
* @param {object} client The Twilio client.
* @param {string} phoneNumber The phone number to normalize.
* @param {string} countryCode The ISO standard country code.
* @return {Promise} A promise with the normalized phone number or error.
*/
exports.normalizePhoneNumber = (client, phoneNumber, countryCode) =>
new Promise((resolve, reject) => {
const hasPlus = phoneNumber.indexOf('+') !== -1;
// Remove non-digits, which includes spaces.
let result = phoneNumber.replace(/\D/g, '');
// Add plus back if originally existed.
if (hasPlus) result = `+${result}`;
client.lookups.v2
.phoneNumbers(result)
.fetch({ countryCode: countryCode.toUpperCase() })
.then((phoneInfo) => {
console.log(`${TAG}::Twilio response for phone number validation: ${JSON.stringify(phoneInfo)}`);
console.log(`${TAG}::The phone number, ${phoneInfo.phoneNumber} is valid.`);
resolve(result);
})
.catch((error) => {
console.error(
`${TAG}::Failed to validate phone number ${phoneNumber} for country ${countryCode}.`,
error
);
reject(error);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment