Skip to content

Gladly to Unthread Migration: The CTO's Technical Guide

Technical guide for CTOs migrating from Gladly to Unthread. Covers API rate limits, data model mapping, conversation-splitting logic, and validation.

Raaj Raaj · · 16 min read
Gladly to Unthread Migration: The CTO's Technical Guide
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,500+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Migrating from Gladly to Unthread is a schema translation project, not a simple export/import. Gladly centers support history on a customer profile and a continuous conversation timeline. Unthread tracks discrete conversations in Slack with statuses, assignees, ticket types, and SLA clocks. The engineering task: extract Gladly history at scale, split lifelong timelines into logical tickets, remap customers and agents to Slack identities, migrate attachments, and load everything into Unthread without breaking chronology. (help.gladly.com)

Teams usually make this move when standardizing on Slack-native support, want an API-first operating model, or no longer need Gladly's broader B2C channel mix. If voice, SMS, and deep consumer-profile routing are still core to your service model, this is not a natural 1:1 switch. (help.gladly.com)

This guide covers the data model clash, API constraints, migration approaches, field-level mapping, and the validation steps needed to plan a Gladly-to-Unthread cutover. For background on Gladly's architecture, see The Ultimate Guide to Mastering Gladly.

The Core Data Model Clash: Lifelong Conversations vs. Slack Threads

The fundamental challenge of this migration is architectural, not cosmetic.

Gladly is built around a person-centric model. Every customer has a single profile, and every interaction — email, chat, voice, SMS, social — is threaded into one continuous, lifelong conversation timeline. There are no discrete ticket numbers. An agent sees the entire customer history as a single scrollable stream, regardless of channel or topic. Gladly explicitly positions itself as putting people, not tickets, at the center of the support experience. (help.gladly.com)

Unthread operates on the opposite principle. It is a Slack-native ticketing system where each Slack thread, DM, or channel message becomes a discrete, trackable ticket. Each ticket has a defined lifecycle: open, in progress, resolved. Unthread groups top-level Slack messages into conversations based on a configurable time interval (defaulting to one hour), and threads are always tracked as individual conversations. (docs.unthread.io)

This means a single Gladly customer with 18 months of interactions across email, chat, and phone has one conversation object in Gladly. In Unthread, that same history needs to become many individual tickets, each mapped to a Slack thread with the correct timestamps, assignee, and resolution state.

Warning

Do not map one Gladly customer profile to one Unthread ticket. A customer with three years of history will generate a single, massive ticket containing hundreds of unrelated messages. This breaks SLA tracking, overwhelms the Slack interface, and makes the data operationally useless. (help.gladly.com)

Why This Breaks Naive Migration

  • No 1:1 mapping exists. You must define splitting logic — by topic change, time gap, channel switch, or a combination.
  • Customer identity must be resolved. Gladly merges profiles across channels into a single record. Unthread ties conversations to Slack users or customer records. If your customers are not Slack users (common when moving from B2C on Gladly to B2B on Unthread), you need to represent historical identities as external user profiles or Unthread Customer objects.
  • Attachments are channel-bound. Gladly stores attachments across email, chat, and social. Unthread is constrained by Slack's limits: 10 files per message, 20MB maximum per file.

Conversation Splitting Logic

Your transformation layer needs deterministic rules to split Gladly timelines into discrete tickets:

  • Time-gap splitting: If more than N hours elapse between consecutive messages, start a new ticket. Common thresholds range from 2 hours (closer to Unthread's own default grouping behavior) to 7 days (for low-volume accounts). Pick what matches your operating model.
  • Topic-based splitting: If Gladly topics change within a conversation, create a new ticket per topic.
  • Channel-based splitting: Each channel switch (email → chat → phone) becomes a separate ticket.
  • Reopened issues: If an issue reopens after a long dormant period, create a new conversation with a backlink to the source.

Keep splitting rules in version control so QA can replay the same segmentation logic. Expect 3–5 iterations with your support ops team before the heuristic is production-ready.

Migration Approaches: What Actually Works

Four viable paths, each with different trade-offs in engineering effort, data fidelity, and risk. The hard constraints are vendor-documented: Gladly's default API limit is 10 requests per second, large exports are .jsonl and expire after 14 days, Unthread list endpoints use cursor-based pagination with a 100-per-page cap, and Slack imports must respect 10 attachments and 20MB per message. (developer.gladly.com)

1. Export API + Custom ETL Script

How it works: Use Gladly's Export API to generate .jsonl files containing conversation items, customers, agents, and topics for a specified date range. Parse the output, apply transformation logic to split timelines into discrete tickets, then push to Unthread's REST API.

When to use it: Medium-to-large datasets where you need full control over the transformation logic.

Pros:

  • Full control over splitting logic and field mapping
  • Handles large datasets without hitting per-request rate limits
  • Can be version-controlled and tested incrementally

Cons:

  • Requires significant engineering time (2–4 weeks for a production-quality pipeline)
  • You own all error handling, retry logic, and data validation
  • Gladly's conversation timeline API returns at most 1,000 items per conversation — the Gladly-Limited-Data response header flags truncated results, but the API does not paginate further
Info

Gladly's Export API returns .jsonl format (one JSON object per line). Export files are available for 14 days after generation. One-time exports covering periods greater than six months may require coordination with Gladly Support and could incur a service fee. (developer.gladly.com)

Complexity: High

2. REST API Per-Record Extraction

How it works: Use Gladly's standard REST endpoints to fetch customers, conversations, and timeline items one by one. Transform in-memory, then POST to Unthread.

When to use it: Small datasets (under 5,000 conversations) or test migration runs to validate your mapping logic before committing to a full pipeline.

Pros:

  • Simpler to implement than the bulk export path
  • Good for validation and dry runs

Cons:

  • Gladly enforces a 10 requests per second rate limit across all HTTP methods. At that rate, extracting 50,000 conversation items takes over 80 minutes of pure API time — before transformation or loading.
  • The conversations list endpoint returns at most 100 conversations per customer and is not paginated
  • Not viable for enterprise-scale datasets

Complexity: Medium

3. iPaaS / Middleware (Zapier, Make)

How it works: Configure workflows that map Gladly events or scheduled exports to Unthread API actions. Unthread has native Zapier and Make integrations with webhook support. (docs.unthread.io)

When to use it: Ongoing lightweight sync of new conversations during a transition period. Not for historical migration.

Pros:

  • No code required for basic field mapping
  • Quick to set up for low-volume forward sync

Cons:

  • Cannot handle the conversation-splitting transformation — iPaaS tools map fields, they do not restructure data models
  • No support for batch operations or large historical datasets
  • Limited error handling: Unthread retries failed webhooks up to 4 times; Gladly retries webhooks up to 4 times over an hour. Ordering drift during retries is a real risk. (docs.unthread.io)

In a public Nango case study, Unthread described moving away from Merge's unified API model because the generic abstraction missed needed data and still left system-specific behavior to handle. That failure mode applies to any overly generic migration adapter for this project. (nango.dev)

Complexity: Low

4. Managed Migration Service

How it works: A dedicated migration team handles extraction, transformation, loading, and validation. Custom scripts are built specifically for the Gladly-to-Unthread data model transformation.

When to use it: When you need high data fidelity, cannot afford downtime during cutover, or lack internal engineering bandwidth for a multi-week project.

Pros:

  • Handles the conversation-splitting problem with tested, production-grade logic
  • Includes validation, rollback planning, and delta migration capability
  • Your engineering team stays on product work

Cons:

  • External cost
  • Requires sharing API credentials with the migration partner

Complexity: Low (for your team)

Approach Comparison

Approach Best For Scale Historical Data Ongoing Sync Complexity Main Risk
Export API + Custom ETL Medium–large datasets, full control Medium–Enterprise High Engineering time, edge cases
REST API Per-Record Small datasets, test runs Small Medium Rate limits, truncated data
iPaaS (Zapier/Make) Forward-only sync Small ongoing Low Ordering drift, no splitting
Managed Service Any size, high fidelity Any ✅ (delta) Low (for you) External cost

Handling Gladly API Limits and Export Formats

Gladly's rate limiting is strict and uniform. Every HTTP method — GET, POST, PUT, PATCH, DELETE — is capped at 10 requests per second at the organization level. You can monitor consumption via the Ratelimit-Limit-Second and Ratelimit-Remaining-Second response headers. Exceeding the limit returns HTTP 429. (developer.gladly.com)

For any migration involving more than a few thousand records, the Export API is the only practical extraction method:

  • Export jobs are date-range scoped. You specify a start and end time, and Gladly generates files covering all communications delivered in that window.
  • Output format is .jsonl — one JSON object per line. File types include customers, conversation_items, agents, and topics.
  • Files expire after 14 days. Download them immediately or automate the pull on job completion.
  • Conversation items with invalid attributes may be silently excluded — undelivered emails or auto-replies generated by Gladly rules can be absent from exports.
  • The conversation timeline API caps at 1,000 items per conversation. If a customer has a longer history, the Gladly-Limited-Data response header will be true, but the API will not paginate further. For high-volume customers, the Export API is the only path to complete data.
Warning

Do not load a multi-gigabyte Gladly .jsonl export into memory at once. Use a streaming parser or stage the data in an intermediary database (PostgreSQL, for example) before running transformation logic. This avoids out-of-memory exceptions and gives you a queryable layer for debugging.

Parsing .jsonl for Transformation

Each line in a Gladly .jsonl export contains a self-contained JSON object with fields like id, conversationId, customerId, initiator, timestamp, and content. Your transformation layer must:

  1. Group items by conversationId
  2. Sort by timestamp within each group (use source id as a tiebreaker for timestamp collisions)
  3. Apply splitting logic to create discrete Unthread tickets
  4. Map initiator.type (CUSTOMER or AGENT) to the correct Unthread user or customer record

Mapping Gladly Data to Unthread's Schema

Unthread's API follows RESTful conventions with JSON request/response bodies. Authentication uses an X-Api-Key header tied to a service account. List endpoints use cursor-based pagination — responses include a cursors object with next and previous strings. You must parse this object and pass the next cursor to subsequent requests; offset pagination will silently miss records. (docs.unthread.io)

Core Object Mapping

Gladly Source Unthread Target Transformation Notes
Customer Profile (id, name, emails, phones) Account + Customer Map externalCustomerId to account.externalCrmMetadata.id. Gladly allows multiple emails/phones; choose a primary identifier. In B2B Slack Connect setups, map to an Account with associated email domains and Slack channels.
Conversation (lifelong timeline) Multiple Conversations (Tickets) Split using time-gap, topic, or channel heuristics. One Gladly conversation → N Unthread tickets.
Conversation Item (content, timestamp, initiator) Message / Reply Convert to Slack-compatible markdown. Preserve chronological order. Use onBehalfOf for authorship attribution.
Agent (id, name, email) User (Workspace Member) Match by email to a Slack workspace member. Provision agents in Unthread before migration. Fall back to a placeholder assignee if no match exists.
Topic / Tag Tag Direct mapping. Flatten Gladly's hierarchical topics. Preserve original topic IDs in metadata for analytics continuity.
Conversation Status Status (open, in_progress, on_hold, closed) Map through a deterministic status table. Preserve original Gladly status in metadata.
Attachment (binary + metadata) Attachment (multipart upload) Download from Gladly, re-upload to Unthread. Slack enforces 10 files per message, 20MB per file.
Voicemail / Phone Transcript Note or archived text Unthread has no native voice channel. Convert to text-based notes or omit from scope.

Key Transformation Rules

Customer identity in Unthread's model: Unthread distinguishes between Accounts (external customer organizations), Customers (channel-level support settings), and Users (Slack workspace members). A Gladly person may need to map to an Unthread Account plus a Customer or Slack channel, plus an external user representation — not just one record. If the historical customer does not exist in your Slack workspace, create them as an Unthread Customer object so the historical context is preserved without failing the API request. (docs.unthread.io)

Message formatting: Gladly stores message content in its own rich-text format. Unthread expects Slack-compatible markdown. HTML tags, inline images, and email signatures need to be stripped or converted during transformation.

Conversation type: Unthread conversations have a type field: slack, email, or triage. If no shared Slack channel exists for a migrated issue, decide per segment whether the target should be slack, email, or triage — not once for the entire migration. (docs.unthread.io)

Step-by-Step Migration Process

Use a straightforward pipeline: extract → stage → transform → load → validate. Authenticate to Gladly with token-based Basic auth and to Unthread with an X-Api-Key service-account header.

Phase 1: Pre-Migration Audit

  • Export a full inventory of Gladly customers, conversations, agents, topics, and tags
  • Count total conversation items — this determines whether you use the REST API or Export API
  • Identify merged customer profiles (Gladly returns 301 for merged records and 404 for update attempts on merged IDs)
  • Catalog all attachment types and sizes; flag anything exceeding Slack's 20MB limit
  • Define the conversation-splitting heuristic with your support ops team
  • Identify unused tags, inactive agents, and dead inboxes — exclude them from scope
  • Set up the Unthread workspace, connect Slack, and configure service accounts for API access

Phase 2: Extract from Gladly

For datasets larger than a few thousand conversations, use the Export API:

# Trigger Gladly Export Job
import requests
 
GLADLY_BASE = "https://yourorg.gladly.com"
HEADERS = {"Authorization": "Basic <base64(email:token)>"}
 
# Create export job
job = requests.post(f"{GLADLY_BASE}/api/v1/export/jobs", headers=HEADERS, json={
    "startAt": "2024-01-01T00:00:00.000Z",
    "endAt": "2025-12-31T23:59:59.000Z"
})
job_id = job.json()["id"]
 
# Poll until COMPLETED, then download .jsonl files:
# customers.jsonl, conversation_items.jsonl, agents.jsonl, topics.jsonl

Supplement with REST calls for attachments, recordings, and transcripts where the export does not include them. Download everything to a secure, encrypted staging environment. Back up immediately — export files disappear from the API after 14 days.

Phase 3: Transform and Split Timelines

Parse the .jsonl records. Group messages by conversationId. Sort chronologically. Apply the splitting heuristic:

from collections import defaultdict
from datetime import datetime, timedelta
 
TIME_GAP = timedelta(hours=168)  # 7-day threshold; adjust to match your model
 
def split_into_tickets(conversation_items):
    grouped = defaultdict(list)
    for item in conversation_items:
        grouped[item["conversationId"]].append(item)
    
    tickets = []
    for conv_id, items in grouped.items():
        items.sort(key=lambda x: x["timestamp"])
        current_ticket = [items[0]]
        for i in range(1, len(items)):
            prev_time = datetime.fromisoformat(items[i-1]["timestamp"].rstrip("Z"))
            curr_time = datetime.fromisoformat(items[i]["timestamp"].rstrip("Z"))
            if curr_time - prev_time > TIME_GAP:
                tickets.append(current_ticket)
                current_ticket = [items[i]]
            else:
                current_ticket.append(items[i])
        tickets.append(current_ticket)
    return tickets

Convert message content to Slack-compatible markdown. Build customer and agent crosswalks. Tag every staged record with immutable source IDs for traceability.

Phase 4: Load Setup Objects, Then Conversations

Create Unthread accounts, customers, tags, ticket types, and user mappings before any conversation load. Then create conversations and replay messages in chronological order:

UNTHREAD_BASE = "https://api.unthread.io/api"
UT_HEADERS = {"X-Api-Key": "your-service-account-key", "Content-Type": "application/json"}
 
def create_ticket(ticket_items, customer_map):
    first_msg = ticket_items[0]
    conv = requests.post(f"{UNTHREAD_BASE}/conversations", headers=UT_HEADERS, json={
        "type": "email",  # or slack/triage, per source channel
        "markdown": first_msg["content"]["content"],
        "status": "open",
        "onBehalfOf": {
            "email": customer_map[first_msg["customerId"]]["email"],
            "name": customer_map[first_msg["customerId"]]["name"]
        },
        "metadata": {
            "sourceConversationId": first_msg["conversationId"],
            "sourceCustomerId": first_msg["customerId"]
        }
    })
    conv_id = conv.json()["id"]
    
    for item in ticket_items[1:]:
        requests.post(f"{UNTHREAD_BASE}/conversations/{conv_id}/messages",
                      headers=UT_HEADERS, json={
            "markdown": item["content"]["content"],
            "authorType": "agent" if item["initiator"]["type"] == "AGENT" else "customer"
        })
    
    # Patch final status after message replay
    requests.patch(f"{UNTHREAD_BASE}/conversations/{conv_id}",
                   headers=UT_HEADERS, json={"status": "closed"})

Preserve source IDs in metadata on every created object. This makes debugging and reconciliation possible after the load completes.

Phase 5: Migrate Attachments

Gladly exports provide URLs for attachments, but these are authenticated and fetched through redirect endpoints. Your script must:

  1. Download the binary file from the Gladly URL
  2. POST it to Unthread's attachment endpoint as a multipart form upload
  3. Link the resulting Unthread attachment ID to the specific thread reply
  4. Handle Slack's 10-file-per-message limit by splitting over-limit payloads across multiple messages if necessary

Inline images pasted directly into Gladly chats are often stored as base64-encoded strings or authenticated URLs embedded in the HTML message body. If your script does not parse the message content to extract and re-host these images, Unthread will display broken image links.

Phase 6: Validate

Do not cut over without systematic validation:

Check Method Pass Criteria
Record count Compare Gladly export totals vs. Unthread API counts ≤ 0.1% variance
Chronological order Sample 100 tickets, verify message timestamps ascending 100% pass
Customer attribution Cross-reference 50 customers across both systems All messages linked to correct customer
Attachment integrity Download 50 random Unthread attachments, compare checksums to source 100% match
Tag mapping Verify all Gladly topics appear as Unthread tags Complete coverage
Merged profiles Confirm merged Gladly profiles resolved to correct Unthread customer No duplicates

Run at least two test migrations. The first will expose mapping gaps. The second validates your fixes. Automate the validation — write comparison scripts that produce a pass/fail report after each load run rather than relying on manual spot-checks.

Edge Cases That Break Migrations

Migrations fail on the edges, not the happy path. For recovery tactics when things go wrong, read Helpdesk Migration Failed? The Engineer's Rescue Guide.

Merged customer profiles. Gladly returns a 301 redirect when you request a customer ID that was merged into another. Your extraction logic must follow these redirects and deduplicate, or you will create duplicate customers in Unthread. Merged profiles can also cause conversation history to appear out of order — your sorting layer must handle this explicitly.

Truncated timelines. The conversation timeline API returns at most 1,000 items per conversation. High-volume customers (enterprise accounts with years of chat history) will have incomplete data unless you use the Export API. The Gladly-Limited-Data header flags this, but the API does not paginate further.

Inline images in email threads. Gladly stores inline images as part of the message content — sometimes as base64 strings, sometimes as authenticated URLs. These must be extracted, re-hosted, and referenced as Slack-compatible URLs. Slack does not reliably render arbitrary external image URLs in all contexts.

Voicemail and phone transcripts. Gladly stores voicemail content and call metadata on separate API endpoints. Unthread has no native voice channel. Convert these to text-based notes or explicitly exclude them from scope.

Missing export items. Gladly's documentation notes that rare conversation items with invalid attributes can be silently excluded from exports. Undelivered emails and auto-replies generated by rules are common culprits. Flag gaps during reconciliation. (developer.gladly.com)

Rate-limit cascading. At 10 requests/second on Gladly's side, a poorly designed extraction script will spend more time waiting than working. Implement exponential backoff with jitter and monitor the Ratelimit-Remaining-Second header to throttle proactively. On the Unthread side, the public documentation does not specify a general API rate limit as of this writing — keep worker concurrency conservative and implement retry logic with backoff for any 429 responses.

Post-Migration: What to Rebuild in Unthread

Once data is loaded, the migration is not done. Unthread's operational model is fundamentally different from Gladly's, and several components cannot be migrated — they must be rebuilt:

  • Routing rules. Gladly uses inbox-based routing with skills matching. Unthread routes via Slack channels, assignments, and automation rules. These must be reconfigured from scratch.
  • SLA policies. Gladly SLAs are tied to conversations. Unthread SLAs are tied to tickets with distinct response and resolution targets. Rebuild these in Unthread's dashboard.
  • Knowledge base. If you were using Gladly's Answers, migrate that content separately. Unthread can sync documentation from web-based sources or native integrations.
  • Automations and triggers. Unthread supports custom TypeScript automations, webhook triggers, and Zapier/Make integrations. Rebuild your Gladly rules as Unthread automations.
  • Agent training. Your team is moving from a dedicated agent desktop to a Slack-native interface. The workflow muscle memory is completely different. Plan at least one week of parallel operation before cutting over.

Keep Gladly active in read-only mode for at least 30 days post-migration. Do not delete Gladly data until validation is complete and your team has confirmed no missing records.

Keep workspace setup on a separate track from historical data movement. Migration is not implementation — mixing the two usually hides data issues until cutover week.

Why In-House Migrations Fail at the Data Layer

The technical surface area of this migration is deceptive. The API calls are straightforward. The data model transformation is not.

Teams that build in-house typically underestimate three costs:

  1. Splitting logic iteration. The first version of your conversation-splitting heuristic will produce bad tickets — too granular, too broad, or inconsistent across channels. Expect multiple rounds of review with support ops before the logic is production-ready.
  2. Attachment re-hosting. Gladly attachment URLs are authenticated. You must download every file, re-upload to a location Unthread can access, and respect Slack's file constraints. For a 50,000-conversation migration, attachment handling alone can take days.
  3. Validation engineering. Building comparison scripts, sampling frameworks, and rollback procedures is a separate workstream that often takes as long as the migration itself.

The real cost is not the API integration — it is the 4–6 weeks of engineering time diverted from product work. Because migrations are one-off projects, the scripts rarely get the rigorous error handling of production systems. This leads to silent failures: missing attachments, broken threads, and corrupted timelines that surface weeks after cutover.

When to Use a Managed Migration Service

Build in-house if you have a dedicated engineer who can commit 4–6 weeks, a dataset under 10,000 conversations, and a straightforward splitting heuristic.

Use a managed service if:

  • Your dataset exceeds 50,000 conversation items
  • You have merged customer profiles that need deduplication
  • You need zero downtime during cutover
  • Your engineering team cannot absorb a multi-week project without slipping product commitments
  • You need delta migrations to keep both systems in sync during a phased rollout
  • You have significant attachment or voice history

ClonePartner has handled complex helpdesk migrations — including the kind of timeline-to-ticket transformation this migration requires — across 1,500+ projects. For Gladly to Unthread transitions, we provide:

  • Automated timeline splitting based on customizable time-gap and topic heuristics
  • Rate limit handling using optimized export ingestion for Gladly and conservative cursor-based loading for Unthread
  • Point-in-time delta migrations — your team continues working in Gladly while we migrate historical data. On cutover day, we sync only the final delta, ensuring no disruption to support operations. See Zero Downtime Guaranteed for how this works.
  • Complete attachment and inline image migration — extracted, re-hosted, and linked to the correct Unthread thread replies

Your engineering team stays on product work. We deliver a correctly structured Unthread database.

Frequently Asked Questions

Can you directly export data from Gladly to Unthread?
No. There is no native export-to-Unthread path. You must extract data from Gladly using its Export API (.jsonl files) or REST API, transform the data to split lifelong conversations into discrete tickets, and then load into Unthread via its REST API.
What is Gladly's API rate limit?
Gladly enforces a default rate limit of 10 requests per second across all HTTP methods (GET, POST, PUT, PATCH, DELETE). Exceeding this returns HTTP 429. Monitor the Ratelimit-Remaining-Second response header to throttle proactively. For bulk extraction, use the Export API instead of per-record REST calls.
How do you map Gladly conversations to Unthread tickets?
Gladly uses a single lifelong conversation per customer. You must apply a splitting heuristic — typically based on time gaps between messages, topic changes, or channel switches — to convert one Gladly conversation into multiple discrete Unthread tickets. Expect several iterations before the splitting logic is production-ready.
What format does the Gladly Export API use?
Gladly's Export API returns .jsonl (JSON Lines) files where each line is a self-contained JSON object. File types include customers, conversation_items, agents, and topics. Files are available for download for 14 days after generation.
How long does a Gladly to Unthread migration take?
A DIY API-based migration typically takes 4–6 weeks of engineering time including script development, splitting logic iteration, and validation. A managed migration service like ClonePartner can complete the same migration in days using pre-built extraction and transformation pipelines.

More from our Blog

Gladly Migration Checklist
Checklist/Gladly

Gladly Migration Checklist

Moving to Gladly? Use our migration checklist to transition from a ticket-based system to a people-centered model. Learn how to map customer timelines and API data safely.

Tejas Mondeeri Tejas Mondeeri · · 7 min read