Creating side conversations with Zendesk APIs
Side conversations allow agents to send messages via email, Slack, or Microsoft Teams to individuals outside the primary discussion of a ticket while keeping those messages organized within the ticket. See About side conversations. The messages that make up a side conversation are logged as events. See Side conversation events.
This article shows an example of using the Zendesk side conversations API to send an email to a recipient and link it to a ticket.
What you need
To use Zendesk side conversations, you'll need a Zendesk account with a Zendesk Suite plan professional or above. You'll also need to be assigned the role of admin in the account.
To run this script, you'll need Python and the requests third-party library.
Creating email side conversations
The following script prompts for the subject and body of the conversation, the ticket number to associate with this conversation, and the recipient email addresses, which can be entered as a comma-separated list. Unlike side conversations in Admin Center, you cannot CC or BCC recipients with the API.
import os
import requests
import json
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
# Fetch environment variables
ZENDESK_USER_EMAIL = os.getenv('ZENDESK_USER_EMAIL')
ZENDESK_API_TOKEN = os.getenv('ZENDESK_API_TOKEN')
ZENDESK_SUBDOMAIN = os.getenv('ZENDESK_SUBDOMAIN')
# Set authentication
AUTH = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN
def create_side_conversation(subdomain: str, ticket_id: int, subject: str, body: str, recipient_emails: list) -> None:
# Create a side conversation in a Zendesk ticket
url = f'https://{subdomain}.zendesk.com/api/v2/tickets/{ticket_id}/side_conversations.json'
headers = {'Content-Type': 'application/json'}
# Construct the 'to' field with multiple recipients
to_recipients = [{"email": email} for email in recipient_emails]
payload = {
"message": {
"subject": subject,
"body": body,
"to": to_recipients
}
}
response = requests.post(url, headers=headers, json=payload, auth=AUTH)
if response.status_code == 201:
logging.info("Side conversation created successfully!")
logging.info("Response:\n%s", json.dumps(response.json(), indent=4))
else:
logging.error("Failed to create side conversation. Status Code: %s, Response: %s", response.status_code, response.text)
def main() -> None:
# Main function to prompt user input and create a side conversation.
subject = input("Enter the subject of the side conversation: ")
body = input("Enter the body of the side conversation: ")
recipient_emails = input("Enter the recipient email addresses (comma-separated): ")
recipient_emails = [email.strip() for email in recipient_emails.split(',')] # Split and strip whitespace
try:
ticket_id = int(input("Enter the ticket ID: "))
except ValueError:
logging.error("Error: Ticket ID must be an integer.")
return
create_side_conversation(ZENDESK_SUBDOMAIN, ticket_id, subject, body, recipient_emails)
if __name__ == "__main__":
main()
How it works
The script does the following:
-
Imports the required libraries.
os
for accessing environment variables.requests
for sending HTTP requests.json
for formatting JSON data.logging
for logging messages.
-
Retrieves the Zendesk API credentials (
ZENDESK_USER_EMAIL
andZENDESK_API_TOKEN
) and subdomain (ZENDESK_SUBDOMAIN
) from environment variables.Environment variables are stored outside the source code, reducing the risk of exposing sensitive information in version control systems. This isolation helps prevent credentials from being accidentally committed to public repositories.
ZENDESK_USER_EMAIL = os.getenv('ZENDESK_USER_EMAIL')
ZENDESK_API_TOKEN = os.getenv('ZENDESK_API_TOKEN')
ZENDESK_SUBDOMAIN = os.getenv('ZENDESK_SUBDOMAIN')
-
Sets up HTTP authentication using the Zendesk email and API token.
AUTH = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN
-
Defines a
create_side_conversation
function that sends an email to the designated recipients and associates that conversation with a particular ticket. See Side Conversations and Messages.def create_side_conversation(subdomain: str, ticket_id: int, subject: str, body: str, recipient_emails: list) -> None:
# This URL points to the specific ticket's side conversations endpoint
url = f'https://{subdomain}.zendesk.com/api/v2/tickets/{ticket_id}/side_conversations.json'
# Define the headers for the HTTP request, indicating that the content type is JSON
headers = {'Content-Type': 'application/json'}
# Construct the 'to' field with multiple recipients
to_recipients = [{"email": email} for email in recipient_emails]
# Create a payload dictionary, containing the message structure which includes subject, body, and recipient's email address
payload = {
"message": {
"subject": subject,
"body": body,
"to": to_recipients
}
}
# Send a POST request using the requests library
response = requests.post(url, headers=headers, json=payload, auth=AUTH)
# Check the response status code. If 201, the side conversation was created successfully
if response.status_code == 201:
logging.info("Side conversation created successfully!")
logging.info("Response:\n%s", json.dumps(response.json(), indent=4))
# If the request is not 201, the request failed. Log an error message that includes the status code and response
else:
logging.error("Failed to create side conversation. Status Code: %s, Response: %s", response.status_code, response.text)
-
Creates the main function that prompts the user for input and calls the
create_side_conversation
function.def main() -> None:
# Main function to prompt user input and create a side conversation
subject = input("Enter the subject of the side conversation: ")
body = input("Enter the body of the side conversation: ")
recipient_emails = input("Enter the recipient email addresses (comma-separated): ")
recipient_emails = [email.strip() for email in recipient_emails.split(',')] # Split and strip whitespace
# Prompt for the ticket number and log an error if the value entered is not an integer
try:
ticket_id = int(input("Enter the ticket ID: "))
except ValueError:
logging.error("Error: Ticket ID must be an integer.")
return
create_side_conversation(ZENDESK_SUBDOMAIN, ticket_id, subject, body, recipient_emails)
-
Adds a conditional statement to execute the
main
function when the script is run directly.if __name__ == "__main__":
main()
Example output
INFO:root:Side conversation created successfully!
INFO:root:Response:
{
"side_conversation": {
"url": "https://z3n8025.zendesk.com/api/v2/tickets/90/side_conversations/80d17404-e4b8-11ef-9f3b-397460190d64",
"id": "80d17404-e4b8-11ef-9f3b-397460190d64",
"ticket_id": 90,
"subject": "printer not working",
"preview_text": "Try unplugging the printer, wait 30 seconds, and then plug it back in",
"state": "open",
"participants": [
{
"user_id": 1509756491122,
"name": "Customer Care Bear",
"email": "[email protected]"
},
{
"user_id": 27011374436631,
"name": "Chris P Bacon",
"email": "[email protected]"
}
],
"created_at": "2025-02-06T18:30:55.809Z",
"updated_at": "2025-02-06T18:30:55.809Z",
"message_added_at": "2025-02-06T18:30:55.809Z",
"state_updated_at": "2025-02-06T18:30:55.809Z",
"external_ids": {}
},
"event": {
"id": "80d17404-e4b8-11ef-9f3b-397460190d64",
"side_conversation_id": "80d17404-e4b8-11ef-9f3b-397460190d64",
"actor": {
"user_id": 1509756491122,
"name": "Customer Care Bear",
"email": "[email protected]"
},
"type": "create",
"via": "api",
"created_at": "2025-02-06T18:30:55.809Z",
"message": {
"subject": "printer not working",
"preview_text": "Try unplugging the printer, wait 30 seconds, and then plug it back in",
"from": {
"user_id": 1509756491122,
"name": "Customer Care Bear",
"email": "[email protected]"
},
"to": [
{
"user_id": 27011374436631,
"name": "Chris P Bacon",
"email": "[email protected]"
}
],
"body": "Try unplugging the printer, wait 30 seconds, and then plug it back in",
"html_body": "<div class=\"zd-comment\">\n<p>Try unplugging the printer, wait 30 seconds, and then plug it back in</p>\n</div>",
"external_ids": {},
"attachments": []
},
"updates": {},
"ticket_id": 90
}
}
Troubleshooting
If the side conversation fails to create, check the returned error message. The logging statement will display the status code and response text. Common status codes are:
401 Unauthorized
: Indicates an issue with authentication.404 Not Found
: Indicates that the specified ticket does not exist.400 Bad Request
: Indicates that the request payload is invalid.