➕ Sending Automated Messages via WhatsApp with Sunshine Conversations and Zendesk

➕ Sending Automated Messages via WhatsApp with Sunshine Conversations and Zendesk

Learn how to leverage the Sunshine Conversations API included in Zendesk Suite to automatically send out messages to your customers.

In a previous article I wrote about how you can now use the new Sunshine Conversations in Zendesk Suite to work around the 24h limit for WhatsApp conversations.

There are however a lot more (complex) use cases possible via the Conversations API. Think about sending confirmations or reminders for bookings. Send password reset codes, confirm subscriptions or changes in order status,.. the list goes on.

All this is possible by leveraging two kinds of conversation types: messages, and notifications, whoch both allow you to send messages over social channels like WhatsApp, Instagram, Twitter,...

Conversation Types

There are two types of conversation updates you can send to your customers.

The first are comments. These are replies to existing conversations and can be in the form of Agent replies (via Agent Workspace), free text or templates (both via API). The important part here is that as long as you are in an existing conversation with a customer over e.g. WhatsApp or Instagram DM, you're allowed to send anything to them.

The other type are notifications. These allow you to send a message to anyone without the requirement of an existing conversation. You can however only send approved message templates and these notifications come at a higher cost (you're also limited to the amount you can send depending on your verifications state)

Templates for e.g. WhatsApp are created via the WhatsApp business portal, other platforms have similar requirements. Check out this article for more info.

Sending messages

There's a few ways we can send out messages:

  1. Automated via Zendesk triggers/automations, similar to how we solved the WhatsApp issue, this is ideal for reminders, confirmation messages,...
  2. Automated via external platforms like your webshop, booking tool,...
  3. Manually by agents in Agent Workspace who can use e.g. a button to trigger a template.

Options (1) and (2) require a webhook and some worker or script that handles the message. (3) can be done via a sidebar app in Zendesk. In this article we'll explore both options.

Let's dive in!

Messages via webhooks

The easiest way to make a scalable way to interact with Sunshine Conversations is to setup a script somewhere that takes messages and users as input, and handles the flow to the Sunshine Conversation endpoints.

For this tutorial I'll assume you have access to Cloudflare Workers (free!) and will handle the WhatsApp channel.

You can copy the entire workflow from the example GitHub repository and paste it into a blank Cloudflare Worker via your browser. Just remember to create the required variables as defined below.

GitHub - verschoren/outbound_messaging
Contribute to verschoren/outbound_messaging development by creating an account on GitHub.

Requirements

Creating variables

I stored all these inside Cloudflare Workers as Environment Variables:

Script Overview

The basis script we build has two endpoints: /notifications and /messages to handle the two use cases mentioned above. Notifications can be send to anyone, but require a template. Messages can be send to active conversations and can contain messages or templates.

  1. We check the url pathname for /notifications or /messages
  2. We get the input await request.json()
  3. We handle any errors that occur.

Sending Notifications

Notifications require two inputs: a user identifier (phone number) and a template name from an approved WhatsApp template.

//POST https://domain.workers.dev/notifications
{
	"phone":"1234567890",
	"template":"template_name"
}

All logic for sending conversations is contained in the sendNotification() function. It first creates a payload that references the phone, template and integration ID, and then contacts the Smooch API endpoint.

async function sendNotification(phone, template, env){
  var payload = {
    "destination": {
        "integrationId": env.sunco_integration_id,
        "destinationId": phone
    },
    "author": {
        "role": "appMaker"
    },
    "messageSchema": "whatsapp",
    "message": {
        "type": "template",
        "template": {
            "namespace": "XXXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXX",
            "name": template,
            "language": {
                "policy": "deterministic",
                "code": "en"
            }
        }
    }
  }

  const api_endpoint = `https://api.smooch.io/v1.1/apps/${env.sunco_app_id}/notifications`
  const sunco_key = btoa(env.sunco_key_id + ":" + env.sunco_secret_key);

  const init = {
    body: JSON.stringify(payload),
    method: "POST",
    headers: {
      "content-type": "application/json",
      "authorization": "Basic " + sunco_key
    },
  };

  const response = await fetch(api_endpoint, init);
  const results = await response.json();
  return results;
}

Sending Comments

Where sending notifications is pretty straightforward, sending comments is a bit more complicated. Assuming you start from a known Zendesk user:

  1. You need to find the Sunshine Conversations User ID for your user
  2. You then need to find the active conversation for that user.
  3. You can then send your message.

The comments endpoint expects the following JSON payload. Requester is the user ID of a Zendesk user and message can be any freeform text we want.

// POST https://domains.workers.dev/messages
{
	"requester":"1234567890",
	"message":"message"
}
🔍
If you only know the email of a user, you can search for a user based on the email via https://domain.zendesk.com/api/v2/search.json?query=type:user email:[email protected]" and return results[0].id

User Identity

Assuming you have a user ID from a Zendesk user, you can use that to return the identities for that user. Most users have only one Messaging identity, namely from the channel they first contacted you. If you have a tendency to merge your users you might need to expand this function to filter out the right one based on the type.

We need to target the Zendesk API for this, and specifically the /users/{user_id}/identities endpoint.

async function getIdentities(requester_id, env){
  const api_endpoint = `https://${env.zendesk_domain}.zendesk.com/api/v2/users/${requester_id}/identities.json`
  const zendesk_key = btoa(env.zendesk_admin_email + "/token:" + env.zendesk_token);

  const init = {
    method: "GET",
    headers: {
      "content-type": "application/json",
      "authorization": "Basic " + zendesk_key
    },
  };

  const response = await fetch(api_endpoint, init);
  const results = await response.json();
  for (let index = 0; index < results.identities.length; index++) {
    const identity = results.identities[index];
    if (identity.type == "messaging"){
      var messaging_id = identity.value;
      return messaging_id;
    }
  }
  return '';
}

Conversation ID

Once we have our user id in messaging, we can use that to find the active conversation for that user. Remember, comments can only be send to active conversations, so we need to target the right one.

This happens via the Smooch API, which is the old brandname for what's now called Sunshine Conversations (SunCo). By default SunCo only has a single conversation per user, and WhatsApp only allows for one anyway, so we can safely return the first conversation we find.

async function getConversationId(messaging_id, env){
  const api_endpoint = `https://api.smooch.io/v2/apps/${env.sunco_app_id}/conversations?filter[userId]=${messaging_id}`
  const sunco_key = btoa(env.sunco_key_id + ":" + env.sunco_secret_key);

  const init = {
    method: "GET",
    headers: {
      "content-type": "application/json",
      "authorization": "Basic " + sunco_key
    },
  };

  const response = await fetch(api_endpoint, init);
  const results = await response.json();
  return results.conversations[0].id;
}

Send Message

Now that we know which conversation to target we can finally send the message. This happens by configuring a new payload that contains the message from our original POST payload.

Note that we use zd:agentWorkspace as a source. This way any reply will surely arrive back to your agents in Zendesk!

async function sendMessage(conversation_id, message, env){
  const api_endpoint = `https://api.smooch.io/v2/apps/${env.sunco_app_id}/conversations/${conversation_id}/messages`
  const sunco_key = btoa(env.sunco_key_id + ":" + env.sunco_secret_key);

  var message_payload = {
    "content": {
      "type": "text",
      "text": message
    },
    "author": {
      "type": "business"
    },
    "source": {
      "type": "zd:agentWorkspace"
    }
  }
  const init = {
    body: JSON.stringify(message_payload),
    method: "POST",
    headers: {
      "content-type": "application/json",
      "authorization": "Basic " + sunco_key
    },
  };

  const response = await fetch(api_endpoint, init);
  const results = await response.json();
  return results;
}

How to use this.

Now that we have a deployed script that can handle any WhatsApp message, you can use this to POST any kind of message to your customers, and this flow is ideal for automated API flows.

If you want an easy way to integrate you could invoke this script via e.g. Zapier as a webhook target and sent a reminder to a user based on a Google calendar with appointments to remind customers of an upcoming event.

Sidebar App

The previous flow described how you could create a script you could invoke via a POST command to trigger messages and notifications to customer.

There is however another way you can use Sunshine Conversations and that is leveraging sidebar apps in Agent Workspace. In the repository below you'll find a fully functional sidebar app for Zendesk that allows you to:

  • Send a message to a user from the ticket sidebar.
  • Send a template to a user from both the ticket sidebar as well as a user profile.

The settings of the app allow you to enter two approved templates and require you to enter the same app_id, secret_key, key_id, integration_id as you entered for the worker script above.

You can download the app via the GitHub repository below. It's a fully functional private app you can modify or install in your instance.

GitHub - verschoren/outbound_messaging
Contribute to verschoren/outbound_messaging development by creating an account on GitHub.

Code Highlights

This being a Zendesk app makes the app flow a bit different than our script.

Get user info

Since our app runs in both the ticket_sidebar and the user_sidebar we can use the client.context(); function to get the location of our app, and we use client.metadata(); to securely retrieve our API credentials we store in the apps' settings.

var client = ZAFClient.init();
var metadata,context;
var recipient,phone;
var conversation_id,key,integration_id
 
metadata = await client.metadata();
context = await client.context();

Once we have our location we need to get the user information we want to sent a message to.

if (context.location == 'ticket_sidebar'){
    recipient = await client.get('ticket.requester')
} else {
    recipient = await client.get('ticket.requester')
}

phone = await getUser(recipient);

async function getUser(requester){
    return await client.request({
        url: '/api/v2/users/' + requester.id,
        type: 'GET',
        dataType: 'json'
    }).then(function(data) {
        $('#phone').html(data.user.phone.replaceAll(' ', ''));
        return data.user.phone.replaceAll(' ', '');
    });
}

And now that we have the user, similar to how we did it in our script, we can get the messaging identity of the user from our recipient.identities object. The rest of the app flows similar to how we handled our earlier script.

How to use the app

Agents can reply to conversations and send random messages to customers via the Message Field, although, honestly, in this case using the Agent Workspace is more logical. They can also send predefined templates via the two buttons underneath the text field.

You could imagine expanding this app and integrating it with your CRM, booking system or others, to allow agent to send specific templates with relevant placeholders to their end users.

Conclusion

So, there you have it, two different ways we can interact with Sunshine Conversations, one ideal for automations, and one to make it possible for agents to interact with Sunshine Conversations via a sidebar app.