How to Automate Incoming Helpdesk Emails with the Cleanbox API
If you run a support address through Cleanbox — whether as an alias or a relay address — you are sitting on structured data that can power your entire helpdesk workflow. Every incoming email has metadata, spam scores, contact categories, and attachments available through the API.
In this guide, we will build a Python script that polls your support alias for new messages, categorizes them by sender, extracts attachments, and creates tickets in your helpdesk system. The same pattern works for any webhook-less integration: CRM, project management, accounting, or custom dashboards.
What we are building
The workflow:
- Poll
GET /v1/messagesevery 5 minutes for new delivered messages on the support alias - For each new message, fetch the full details and spam report
- Check the contact's category and state to determine priority
- Extract attachments if present
- Create a ticket in your helpdesk (we will use a generic HTTP POST, adaptable to Zendesk, Freshdesk, Linear, etc.)
Prerequisites
- A Cleanbox account with API access enabled (setup guide)
- Python 3.8+ with the
requestslibrary - Your API key and the UUID of the support alias
Step 1: Set up the API client
First, create a simple wrapper for the Cleanbox API:
import requests
import time
from datetime import datetime, timezone
API_BASE = "https://api.cleanbox.app/v1"
API_KEY = "your_api_key_here"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
def api_get(path, params=None):
"""Make a GET request to the Cleanbox API."""
r = requests.get(f"{API_BASE}{path}", headers=HEADERS, params=params)
r.raise_for_status()
return r.json()
def api_post(url, data):
"""Make a POST request to an external service."""
r = requests.post(url, json=data)
r.raise_for_status()
return r.json()
Note: we are not wrapping the entire API — just the calls we need. For a complete reference, see the API documentation.
Step 2: Fetch new messages
The messages endpoint supports filtering by address UUID and status. We want delivered messages on our support alias:
SUPPORT_ALIAS_UUID = "your-alias-uuid-here"
def get_new_messages(since_timestamp):
"""Fetch delivered messages on the support alias."""
messages = []
page = 1
while True:
data = api_get("/messages", {
"address": SUPPORT_ALIAS_UUID,
"status": "delivered",
"page": page,
"per_page": 100
})
for msg in data["data"]:
# Only process messages newer than our checkpoint
received = datetime.fromisoformat(msg["received_at"])
if received > since_timestamp:
messages.append(msg)
if page >= data["total_pages"]:
break
page += 1
return messages
The API returns messages sorted by date (newest first). We paginate through all pages and filter by our checkpoint timestamp. In production, you would store this timestamp in a file or database between runs.
Step 3: Enrich with contact data
Each message includes a contact UUID. We can fetch the contact to get their category and state:
def get_contact(contact_uuid):
"""Fetch contact details including category and state."""
data = api_get(f"/contacts/{contact_uuid}")
return data["contact"]
def determine_priority(contact):
"""Map contact state and category to ticket priority."""
# Prioritized contacts = urgent
if contact["state"] == "prioritized":
return "urgent"
# Whitelisted contacts = high (trusted, probably a client)
if contact["state"] == "whitelisted":
return "high"
# Finance and Business Services categories = high
category = contact.get("category", {})
if category and category.get("name") in ["Finance", "Business Services"]:
return "high"
return "normal"
This is where Cleanbox's automatic contact categorization pays off. A message from a Finance-category sender automatically gets higher priority without any manual classification.
Step 4: Extract attachments
If the message has attachments, we can download them via the API:
import base64
def get_attachments(message_uuid):
"""Download message attachments as files."""
data = api_get(f"/messages/{message_uuid}/attachments")
files = []
for att in data.get("attachments", []):
files.append({
"filename": att["filename"],
"content_type": att["content_type"],
"size": att["size"],
"content": base64.b64decode(att["content"])
})
return files
The attachments endpoint returns base64-encoded content. We decode it to raw bytes, ready to attach to a helpdesk ticket or save to disk.
Alternatively, use GET /v1/messages/{uuid}/attachment/{index} to download a single attachment as a binary file (index starts at 1).
Step 5: Check the spam report
For support aliases, you might want to flag messages with suspicious spam signals even if they passed the threshold:
def get_spam_flags(message_uuid):
"""Check spam report for concerning signals."""
data = api_get(f"/messages/{message_uuid}/spamreport")
flags = []
for rule in data.get("rules", []):
if rule["rule"] in ["FORGED_SENDER", "SPOOF_DISPLAY_NAME", "PHISHING"]:
flags.append(rule["rule"])
return flags
If a message has FORGED_SENDER or PHISHING symbols, you might want to tag the ticket as suspicious even though it passed delivery.
Step 6: Create the ticket
Now we combine everything into a ticket creation function. This example uses a generic HTTP POST — adapt the payload to your helpdesk's API:
HELPDESK_API = "https://your-helpdesk.com/api/tickets"
HELPDESK_TOKEN = "your_helpdesk_api_key"
def create_ticket(message, contact, priority, attachments, spam_flags):
"""Create a ticket in your helpdesk system."""
ticket = {
"subject": message["subject"],
"from_email": message["from_address"],
"from_name": message["from_name"],
"priority": priority,
"category": contact.get("category", {}).get("name", "Uncategorized"),
"tags": spam_flags,
"received_at": message["received_at"],
"cleanbox_message_uuid": message["uuid"]
}
# Add attachments if your helpdesk supports them
if attachments:
ticket["attachments"] = [
{"filename": a["filename"], "size": a["size"]}
for a in attachments
]
print(f"Creating ticket: {ticket['subject']} [{priority}]")
# Uncomment to actually create:
# requests.post(HELPDESK_API,
# headers={"Authorization": f"Bearer {HELPDESK_TOKEN}"},
# json=ticket)
return ticket
Step 7: The main loop
Tie it all together with a polling loop:
import os
from datetime import timedelta
CHECKPOINT_FILE = "last_check.txt"
def load_checkpoint():
"""Load the last check timestamp."""
if os.path.exists(CHECKPOINT_FILE):
with open(CHECKPOINT_FILE) as f:
return datetime.fromisoformat(f.read().strip())
# Default: check last 24 hours on first run
return datetime.now(timezone.utc) - timedelta(hours=24)
def save_checkpoint(ts):
"""Save the current timestamp."""
with open(CHECKPOINT_FILE, "w") as f:
f.write(ts.isoformat())
def run():
since = load_checkpoint()
print(f"Checking for messages since {since.isoformat()}")
messages = get_new_messages(since)
print(f"Found {len(messages)} new messages")
for msg in messages:
# Get contact info
contact = get_contact(msg.get("contact_uuid", ""))
if msg.get("contact_uuid") else {}
# Determine priority
priority = determine_priority(contact)
if contact else "normal"
# Get attachments
attachments = get_attachments(msg["uuid"])
if msg.get("has_attachments") else []
# Check spam signals
spam_flags = get_spam_flags(msg["uuid"])
# Create ticket
create_ticket(msg, contact, priority, attachments, spam_flags)
# Update checkpoint
save_checkpoint(datetime.now(timezone.utc))
print("Done")
if __name__ == "__main__":
run()
Running it
For a simple setup, run the script via cron every 5 minutes:
*/5 * * * * cd /path/to/script && python3 helpdesk_sync.py >> /var/log/helpdesk_sync.log 2>&1
For a more robust setup, use a process manager (systemd, PM2) with a sleep loop, or trigger it via a webhook from your monitoring system.
What you can extend
- Auto-reply: For known categories (e.g., "Billing" contacts), send an automatic acknowledgment email
- Quarantine review: Use
GET /v1/quarantineto also check quarantined messages that might be legitimate support requests caught by aggressive filtering - Contact auto-whitelist: When a ticket is resolved, use
PUT /v1/contacts/{uuid}to set the contact to whitelisted so future emails from them skip spam checks - SLA monitoring: Track message timestamps vs. ticket creation time to measure response time
The Cleanbox API turns your email into structured data. What you build with that data is up to you.