How to send text messages (SMS)

Create SMS connections and send text messages from Zato services

There are several popular SMS providers you can choose from for sending messages, and you communicate with each of them

using REST connections, where:

  • A connection holds the provider's address and the path of its endpoint for sending messages
  • A security definition holds the credentials, and the connection attaches them to each request
  • Your service builds the message in the provider's format and posts it through the connection

Step 1 - Choose a provider

Each provider has its own page with the details to collect from its console, the security definition and the connection to create:

ProviderSecurity definitionData format
TwilioBasic AuthForm data
VonageBasic AuthJSON
InfobipAPI keyJSON
Africa's TalkingAPI keyForm data

This tutorial uses Twilio.

Step 2 - Create the connection

Create the security definition and the outgoing REST connection described on the Twilio page, with the connection named Twilio SMS.

The connection can be used as soon as you click OK, and no restart is required.

Step 3 - Write the service

Say a clinic sends its patients text messages - reminders, changes to appointments and results ready for pick-up - and several services are to send them. The service below is the one place they all send from:

  • It takes the recipient's number and the text of the message
  • It builds the message in Twilio's format and sends it
  • It returns the ID Twilio gave the message and its status

Take the code below and deploy it, then invoke it from the Dashboard's IDE under Services → IDE with {"to": "+12025550123", "text": "Your appointment is tomorrow at 9:00"} on input.

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SendSMS(Service):

    name = 'sms.send'
    input = 'to', 'text'

    def handle(self):

        input = self.request.input

        # The message in Twilio's format ..
        message = {
            'To': input.to,
            'From': '+12025550100',
            'Body': input.text,
        }

        # .. get the connection to Twilio ..
        conn = self.rest['Twilio SMS']

        # .. send the message ..
        response = conn.post(message)

        # .. make sure Twilio accepted it ..
        if not response.ok:
            code = response.data['code']
            reason = response.data['message']
            raise Exception(f'Twilio error {code}: {reason}')

        # .. and tell our caller what Twilio said.
        self.response.payload = {
            'message_id': response.data['sid'],
            'status': response.data['status'],
        }

Step 4 - Run it

After invoking the service you'll see the ID of the message and its status as below:

{"message_id": "SM9f2c5a1e8b7d4c3a2f1e0d9c8b7a6f5e", "status": "queued"}

The message reaches the phone within seconds, and the audit log of the connection records the request and Twilio's response.

Sending from other services

Other services do not need to know which provider sends their messages, or in which format. They invoke sms.send with a number and a text.

The service below sends a reminder for an appointment:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SendAppointmentReminder(Service):

    name = 'clinic.send-appointment-reminder'
    input = 'phone', 'location', 'starts_at'

    def handle(self):

        input = self.request.input

        # The text of the reminder ..
        text = f'Your visit at {input.location} is on {input.starts_at}.'

        # .. the request to the service that sends text messages ..
        request = {'to': input.phone, 'text': text}

        # .. and send the reminder through it.
        response = self.invoke('sms.send', request)

        self.response.payload = response

A scheduled job can invoke this service each morning for the next day's appointments, and a REST channel can invoke it for the appointments booked online.

Changing providers

Because every service sends through sms.send, a change of provider is a change to that one service and to the connection it uses, and no other service changes.

For instance, with a Vonage connection named Vonage SMS, the message and the call become:

# The message in Vonage's format ..
message = {
    'message_type': 'text',
    'channel': 'sms',
    'to': input.to.lstrip('+'),
    'from': '12025550100',
    'text': input.text,
}

# .. sent through the connection to Vonage.
conn = self.rest['Vonage SMS']
response = conn.post(message)

Phone numbers

All four providers expect numbers in the international E.164 form, which is a country code followed by the number, e.g. +12025550123. Twilio and Africa's Talking take the number with its leading +, while Vonage and Infobip take it without one, e.g. 12025550123.

See also

PageWhat it covers
TwilioSend through Twilio and test for free with credentials that never deliver or charge for a message
VonageSend through Vonage with your organization's name as the sender, in countries that allow one
InfobipTell everyone at once - one request sends the same text to any number of recipients
Africa's TalkingTry every message for free in a sandbox that shows it on a simulated phone instead of delivering it
REST outgoing connectionsMake sure no message is lost, with timeouts, retries and a queue for messages the provider could not take
Calling external REST APIsCall any other REST API the same way, with parameters, headers and status checks

Learn more