Infobip

Send text messages to one or many recipients through the Infobip SMS API from an outgoing REST connection.

This page will show you how to send text messages through Infobip from Python:

  • What to collect from the Infobip portal
  • Which security definition and outgoing REST connection to create
  • How to send a message from a service and read Infobip's response
  • How to send the same message to many recipients in one request

Collecting Infobip config

You'll need three details from the Infobip portal:

  • The API base URL, which is specific to each account and is shown on the portal's home page, e.g. xy1z2w.api.infobip.com
  • An API key, created on the API keys page of the portal
  • The sender, which is a number or an alphanumeric sender ID registered with the account

Creating Zato connections

First, create an API key definition under Security → API keys. Infobip expects the key in the Authorization header, after the word App and a space:

FieldValueNotes
NameInfobip SMS---
HeaderAuthorization---
API keyApp <API key>E.g. App 4a3f6e1c..., with the space
API key for Infobip

Next, create an outgoing REST connection under Connections → Outgoing → REST:

FieldValueNotes
NameInfobip SMS---
Hosthttps://<API base URL>E.g. https://xy1z2w.api.infobip.com
URL path/sms/3/messages---
Data formatJSON---
SecurityAPI key/Infobip SMSThe definition just created
REST connection to Infobip

Sending a message

The service below will:

  • Build the message from the input it received
  • Send it to Infobip
  • Return the ID Infobip gave the message and its status
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SendInfobipSMS(Service):

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

    def handle(self):

        input = self.request.input

        # The message in Infobip's format ..
        message = {
            'sender': 'Riverside',
            'destinations': [{'to': input.to}],
            'content': {'text': input.text},
        }

        # .. one request can carry many messages ..
        request = {'messages': [message]}

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

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

        # .. make sure Infobip accepted it ..
        if not response.ok:
            request_error = response.data['requestError']
            error = request_error['serviceException']
            reason = error['text']
            raise Exception(f'Infobip error: {reason}')

        # .. there is one result for each message ..
        messages = response.data['messages']
        result = messages[0]
        status = result['status']

        # .. and tell our caller what Infobip said.
        self.response.payload = {
            'message_id': result['messageId'],
            'status': status['groupName'],
        }

Numbers go to Infobip in E.164 form without the leading +, so the service is invoked with {"to": "12025550123", "text": "Your appointment is tomorrow at 9:00"}.

After invoking the service you'll see:

{"message_id": "4f7a2c9e1b3d5f6a8c0e", "status": "PENDING"}

The PENDING status group means Infobip has accepted the message and is delivering it.

Sending to many recipients

Say the clinic closes early one afternoon and every patient booked for that afternoon is to be told. A message can list any number of destinations, so one request sends the same text to all of them.

The service below will:

  • Build one destination for each number in its input
  • Send the message to all of them in one request
  • Return the ID and the status of the message to each number
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SendInfobipSMSToMany(Service):

    name = 'sms.infobip.send-to-many'
    input = 'phones', 'text'

    def handle(self):

        input = self.request.input

        # One destination for each number ..
        destinations = []
        for phone in input.phones:
            destinations.append({'to': phone})

        # .. the message with all of them ..
        message = {
            'sender': 'Riverside',
            'destinations': destinations,
            'content': {'text': input.text},
        }

        # .. send it in one request ..
        request = {'messages': [message]}
        conn = self.rest['Infobip SMS']
        response = conn.post(request)

        if not response.ok:
            request_error = response.data['requestError']
            error = request_error['serviceException']
            reason = error['text']
            raise Exception(f'Infobip error: {reason}')

        # .. Infobip returns one result for each destination ..
        results = []
        for item in response.data['messages']:
            status = item['status']
            result = {
                'phone': item['destination'],
                'message_id': item['messageId'],
                'status': status['groupName'],
            }
            results.append(result)

        # .. and our caller receives all of them.
        self.response.payload = results

After invoking the service you'll see:

[
  {
    "phone": "12025550123",
    "message_id": "4f7a2c9e1b3d5f6a8c0e",
    "status": "PENDING"
  },
  {
    "phone": "12025550124",
    "message_id": "8b1d3f5a7c9e2b4d6f8a",
    "status": "PENDING"
  }
]

Errors

When Infobip does not accept a request, it answers with a 4xx status and a JSON body whose requestError.serviceException.text says why, e.g.:

{
  "requestError": {
    "serviceException": {
      "messageId": "UNAUTHORIZED",
      "text": "Invalid login details"
    }
  }
}

The services above turn such a response into an exception with the text.

See also

PageWhat it covers
Your first SMSOne service that every other service sends text messages through
API keysEvery field of an API key definition, including the header it is sent in
Calling external REST APIsParameters, headers and the response object of a REST call

Learn more