Skip to content

eDesk to Pylon Migration: A Technical Guide

A technical guide to migrating from eDesk to Pylon. Covers API rate limits, data model mapping, ETL process, edge cases, and validation.

Rishabh Rishabh · · 31 min read
eDesk to Pylon Migration: A 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

eDesk to Pylon Migration: A Technical Guide

Migrating from eDesk to Pylon is not a button-press import. You are moving from an eCommerce-first helpdesk built around marketplace integrations, order context, and seller workflows to an AI-native, Slack-first B2B support platform built around account-level relationships and conversational channels. The data models serve fundamentally different audiences, there is no native migration path between them, and API rate limits on both sides make brute-force approaches impractical at scale.

If you are searching for how to migrate from eDesk to Pylon, the short answer is: a custom API-based ETL pipeline or a managed migration service. eDesk's CSV export is limited to 1,000 rows per download and excludes conversation history. (support.edesk.com) Middleware tools like Zapier cannot handle nested ticket-message-attachment relationships. And Pylon's Issues API is rate-limited to just 10 requests per minute, which constrains throughput regardless of how fast you extract. (docs.usepylon.com)

Last updated: July 2025. API rate limits, endpoint structures, and platform capabilities verified against published documentation as of this date.

Changelog:

  • July 2025: Initial publication with full endpoint verification against eDesk v1 API and Pylon REST API documentation.

Why Teams Move from eDesk to Pylon

The shift is architectural, not cosmetic.

eDesk is a purpose-built eCommerce helpdesk that unifies customer queries and order data from Amazon, eBay, Shopify, and dozens of marketplaces into a centralized smart inbox. Its core data model revolves around the Order — tickets are linked to sales orders, tracking information, and channel-specific workflows. The relationship chain is flat: Customer → Ticket → Messages → Sales Orders. (edesk.com)

Pylon is purpose-built for B2B support where conversations happen in shared Slack channels, Microsoft Teams, and Discord. Its core object is the Issue — a conversational thread tied to an Account, with messages, internal notes, custom fields, and CRM-synced metadata. The relationship chain is relational: Account → Contact → Issue → Messages. (usepylon.com)

Teams migrate for three reasons:

  • Business model shift. Companies that started in eCommerce and evolved into B2B SaaS or service businesses need account-centric support, not order-centric support. eDesk's data model does not map to recurring customer relationships.
  • Channel alignment. B2B customers live in Slack Connect and Teams. Pylon integrates natively with these channels. eDesk is built for email, marketplace messaging, and social — not real-time collaboration.
  • Cost and stack fit. eDesk's API access requires the Enterprise plan. Teams that no longer need marketplace integrations are paying for features they will not use. (edesk.com)

Core Data Model Differences: eDesk vs Pylon

This table defines the structural mismatch you will reconcile during migration:

Concept eDesk Pylon Migration Notes
Core record Ticket (tied to channel/order) Issue (tied to Account) 1:1 mapping possible, but context differs
Customer Customer record Contact (linked to Account) eDesk customers flatten to Pylon contacts
Company No formal company object Account Must be derived from customer email domains or tags
Conversations Messages within a ticket Messages within an Issue Thread structure differs
Order data Sales Orders, tracking links, order notes No equivalent Must be archived or stored in custom fields
Channels Amazon, eBay, Shopify, email, chat, social Slack, Teams, Discord, email, chat widget, WhatsApp Marketplace channels have no Pylon equivalent
Tags Tags and tag groups Tags Direct mapping
Custom fields Custom fields on tickets Custom fields on Issues, Contacts, Accounts Type matching required
Automations eDesk rules and templates Triggers, Macros Must be manually rebuilt
Knowledge base eDesk knowledge base Pylon Knowledge Base Separate migration required
Warning

eDesk Sales Orders, tracking links, and marketplace-specific metadata have no native equivalent in Pylon. If this data is business-critical, archive it separately or flatten it into Pylon custom fields before migration. There is no way to reconstruct marketplace order context inside Pylon. (developers.edesk.com)

Migration Approaches

There are five viable methods. Each has different trade-offs for fidelity, throughput, and engineering effort.

1. Native CSV Export → Manual Import

How it works: Export tickets from eDesk's Insights section as CSV files, transform them, and use Pylon's API to import.

When to use it: Proof-of-concept migrations under 1,000 records, or when you only need metadata without conversation history.

Pros:

  • No engineering required for extraction
  • Quick for small datasets

Cons:

  • eDesk's Search Download is Enterprise-only and capped at 1,000 rows. Ticket Download defaults to the first 500 tickets with no filters and produces JSON, not CSV. Report Extracts are snapshots and documentation indicates data goes back to July 2023. (support.edesk.com)
  • Exports contain ticket metadata only — no conversation threads, no attachments, no internal notes
  • Pylon has no bulk CSV import — you still need API calls to load data
  • Relationship data (customer → ticket → messages) is flattened

Scalability: Unsuitable beyond a small proof-of-concept.

2. API-Based Migration (eDesk REST API → Pylon REST API)

How it works: Build a custom pipeline that reads from eDesk's REST API and writes to Pylon's REST API. Extract tickets, messages, and customers; transform into Pylon's Account → Contact → Issue → Message hierarchy; load via Pylon endpoints.

Pylon documents a dedicated /import/issues endpoint for historical data that accepts threaded messages, timestamps, and internal notes in a single payload. This is the recommended path for backfilling closed tickets. Each message in the payload requires body_html, is_private (boolean), and exactly one of user_id (for agent messages) or contact_id (for customer messages). The endpoint returns the created Issue object with its Pylon id. (support.usepylon.com)

When to use it: Any production migration that requires conversation history, attachments, and relationship integrity.

Pros:

  • Full-fidelity extraction including messages, notes, and attachments
  • Relationship preservation (customer → ticket → messages)
  • Programmatic control over transformation logic
  • Handles custom fields, tags, and metadata

Cons:

  • eDesk API requires the Enterprise plan
  • Pylon's Issues API is rate-limited to 10 requests per minute for create operations
  • Building the pipeline takes 2–4 weeks of engineering time
  • Error handling, retry logic, and idempotency must be built from scratch
  • Imported issues do not trigger Pylon's AI topic generation (support.usepylon.com)

Scalability: The only approach that scales to production workloads, but Pylon's 10 req/min ceiling means 50,000 tickets take approximately 3.5 days of uninterrupted API writes (50,000 ÷ 10 per min ÷ 60 min ÷ 24 hr ≈ 3.47 days).

3. Third-Party Migration Tools

Platforms like Help Desk Migration offer automated eDesk connectors. eDesk lists Help Desk Migration as a partner supporting 60+ platforms, and Pylon's migration materials reference Relokia. (edesk.com)

When to use it: Mid-size migrations where you want to avoid building a custom pipeline but need more than CSV.

Pros:

  • Pre-built connectors reduce development time
  • Built-in field mapping UI
  • Support for test migrations before committing

Cons:

  • Pylon is a newer platform — third-party tool support may be limited or incomplete. Run a proof-of-concept before committing.
  • Do not assume a direct eDesk → Pylon template handles sales-order data or custom cutover logic without validation
  • Limited control over edge cases and complex relationships
  • Per-record pricing can be significant at scale. Help Desk Migration's published pricing (as of 2025) uses tiered per-record rates — check their calculator for current costs with your dataset size.

Scalability: Good for mid-size datasets (5K–50K tickets). May hit limitations on complex data models.

4. Custom ETL Pipeline

How it works: Build a dedicated data pipeline using Python, Node.js, or a data engineering framework. Extract from eDesk into an intermediate data store (PostgreSQL, MongoDB, or flat files), transform to match Pylon's schema, then load via Pylon's API.

When to use it: Enterprise migrations with complex transformation requirements, multiple source systems, or regulatory constraints.

Pros:

  • Full control over every transformation
  • Intermediate staging allows auditing and validation before loading
  • Supports incremental and delta syncs
  • Audit trail and replayability

Cons:

  • Highest engineering investment (3–6 weeks)
  • Requires monitoring, logging, and alerting infrastructure
  • Maintenance burden if the migration is one-time

Scalability: Best option for enterprise datasets (100K+ tickets).

5. Middleware / Integration Platforms (Zapier, Make)

How it works: Connect eDesk and Pylon through a middleware platform using triggers and actions.

When to use it: Ongoing sync of new tickets only. Not suitable for historical migration.

Pros:

  • No-code setup
  • Good for forwarding new tickets post-migration

Cons:

  • Cannot migrate historical data in bulk
  • Cannot handle nested relationships (ticket → messages → attachments)
  • Limited error handling and retry logic
  • Running historical tickets through Zapier will trigger rate limits and cost thousands in task usage

Scalability: Only suitable for low-volume, forward-looking sync.

Migration Approach Comparison

Method Fidelity Scalability Engineering Effort Best For
CSV Export Low <1K records Low Proof-of-concept
API-Based High 5K–100K+ High Production migrations
Third-Party Tools Medium–High 5K–50K Medium Mid-size, standard schemas
Custom ETL Highest 100K+ Very High Enterprise, multi-source
Middleware (Zapier/Make) Low Ongoing sync only Low Post-migration forwarding

Recommendations by scenario:

  • Small business (<5K tickets, no dev team): Third-party migration tool with a test run first. Validate Pylon adapter coverage before committing.
  • Mid-market (5K–50K tickets, some engineering bandwidth): API-based migration with a staged approach. Extract to a local database first, transform, then load.
  • Enterprise (50K+ tickets, dedicated dev team): Custom ETL pipeline with intermediate staging, throttled loading, and full audit logging.
  • Ongoing sync (post-migration): Middleware platform for new ticket forwarding only.

When to Use a Managed Migration Service

When NOT to Build In-House

  • Your engineering team is allocated to product work and cannot absorb 3–6 weeks of migration development
  • You have less than 2 weeks until your eDesk contract ends
  • Your data includes complex relationships, custom fields, and attachments that require per-record validation
  • You have never built against Pylon's API and need to learn its rate limit behavior, pagination model, and error handling from scratch

Pylon's own documentation is clear that standard data migration covers historical closed tickets and knowledge base content, while process recreation, open-ticket cutover, views, SLAs, triggers, and training are separate workstreams. (support.usepylon.com)

Hidden Costs of DIY Migration

These are real costs teams underestimate — not reasons to avoid building internally, but factors to budget for:

  • Engineering time: A senior engineer at $150/hr for 4 weeks = $24,000+ in opportunity cost, not counting debugging and re-runs. This is based on US market rates; adjust for your team's location and seniority.
  • Data loss risk: Partial failures without resumability mean re-running the entire migration
  • Downtime exposure: Without a delta sync strategy, tickets created between extraction and go-live are lost
  • Validation overhead: Building record-count comparison, field-level validation, and sampling logic is its own project — typically 20–30% of total migration engineering time

About ClonePartner

ClonePartner has completed 1,500+ migrations across helpdesk platforms, including migrations into Pylon from Zendesk, Freshdesk, and other sources. Specific capabilities relevant to this migration path:

  • Relationship reconstruction: Deriving Pylon Accounts from eDesk customer data where no formal company object exists
  • Rate limit orchestration: Managing Pylon's 10 req/min Issues ceiling without timeout failures
  • Attachment handling: Downloading from eDesk, uploading to Pylon's attachments API, and linking to the correct Issues and Messages
  • Custom field mapping: Transforming eDesk's field types to Pylon's supported types
  • Zero-downtime execution: Delta sync strategies that capture tickets created during the migration window

Disclosure: ClonePartner is the publisher of this guide. If you have the engineering bandwidth and timeline, the API-based approach documented below works without external help.

Pre-Migration Planning

Data Audit Checklist

Before writing any migration code, audit your eDesk instance:

Object Audit Action
Tickets Count total, by status, by channel. Identify oldest records.
Messages/Replies Count messages per ticket. Flag tickets with 50+ messages (may hit Pylon payload size limits).
Customers Deduplicate by email. Identify customers with multiple channel identities.
Tags Export full tag list. Map to Pylon tags.
Custom Fields Document field names, types, and picklist values.
Attachments Estimate total file size. Identify file types. Note any files >25 MB.
Sales Orders Decide: archive separately or flatten into Pylon custom fields?
Templates Document all templates. These must be manually recreated as Pylon Macros.
Knowledge Base Export all articles with their HTML/markdown content. Plan a separate KB migration.
Automations/Rules Document all eDesk rules including trigger conditions and actions. Must be rebuilt as Pylon Triggers.

eDesk's documented API objects include tickets, messages, contacts, sales orders, order notes, tags, users, channels, templates, ticket fields, and order fields. The API uses offset-based pagination with page and per_page parameters (max per_page varies by endpoint, typically 100). If you have CRM data (accounts, leads, opportunities) in an upstream system, migrate those from the system of record — eDesk's API surface is support and order oriented, not a CRM pipeline. (developers.edesk.com)

Identify Unused Data

Most eDesk accounts accumulate dead weight:

  • Closed tickets older than 24 months with no business value
  • Duplicate customer records created by marketplace channel splits
  • Test tickets and internal test data
  • Orphaned attachments from deleted tickets

Every record you exclude reduces migration time and Pylon API calls. At 10 Issues per minute, cutting 10,000 unnecessary tickets saves approximately 16.7 hours of load time (10,000 ÷ 10 per min ÷ 60 min).

Migration Strategy

Strategy Best For Risk Level
Big bang Small datasets (<5K), tight timelines High — no rollback window
Phased Mid-size, multiple channels Medium — can validate per phase
Incremental Enterprise, zero-downtime requirement Low — continuous validation

For most eDesk-to-Pylon migrations, a phased approach works best: migrate historical closed tickets first, run a delta sync, then cut over active operations. Pylon's own migration guidance positions historical backfill as milestone 3, not the step that makes go-live possible — process recreation comes first. (support.usepylon.com)

Delta sync for the cutover window: eDesk supports webhooks for ticket events (create, update, close). Configure an eDesk webhook to capture tickets created or updated during the migration window. Buffer these events and replay them against Pylon after the primary migration completes. This eliminates the gap between extraction and go-live. If webhooks are not available on your eDesk plan, poll the eDesk tickets endpoint filtered by updated_at > extraction_start_timestamp as a fallback.

For the full decision framework, see our Help Desk Data Migration Checklist.

Data Model & Object Mapping

This is the section that determines whether your migration succeeds or fails.

Object-Level Mapping

eDesk Object Pylon Object Mapping Notes
Customer Contact Map by email address. Deduplicate first.
Customer (company domain) Account eDesk has no company object — derive Accounts from customer email domains or tags.
Ticket Issue 1:1 mapping. Map status, priority, tags, custom fields.
Message (in ticket) Message (in Issue) Map as reply or internal note based on message type.
Note / Order note Private message or Account Activity Use is_private=true for internal notes.
Tag Tag Direct mapping. Create tags in Pylon first.
Custom Field Custom Field Type-match required.
Sales Order No equivalent Archive or flatten to Issue custom fields.
Template Macro Manual recreation required.
Knowledge Base Article KB Article Separate migration via KB API.

Status Mapping

eDesk and Pylon use different status taxonomies. eDesk tickets use Open, Pending, Closed, Archived, and Spam. (developers.edesk.com) Pylon Issues use new, waiting_on_you, waiting_on_customer, on_hold, and closed.

There is no vendor-provided one-to-one mapping. You must make design decisions:

eDesk Status Recommended Pylon State Notes
Open waiting_on_you Active, needs agent action
Pending waiting_on_customer Awaiting customer response
Closed closed Direct map
Archived closed Preserve original status in a custom field
Spam Exclude from migration Or store in custom field for audit
Info

Preserve the original eDesk status in a custom field if you need audit accuracy. The Pylon state set is smaller and semantically different.

Field-Level Mapping

eDesk Ticket Field Pylon Issue Field Transformation
ticket_id Custom field: edesk_ticket_id Cast to string. Critical for deduplication and delta syncs.
subject title Direct map. Default to "(no subject)" if empty.
body body_html Convert to HTML if plain text. Wrap in <p> tags.
status state Map per status table above.
priority priority Map values.
channel Custom field: source_channel Store as metadata. Marketplace channels have no Pylon equivalent.
assigned_agent assignee_id Lookup Pylon user ID by agent email.
tags tags Array of tag slugs.
created_at created_at ISO 8601. Ensure UTC normalization.
customer_email requester_email Direct map.
customer_name requester_name Direct map.
Custom fields custom_fields Slug-based mapping with type conversion.
sales_order.order_id Custom field: order_id Flatten. No native order object in Pylon.
messages [].body Message body_html Convert to HTML.
messages [].attachments attachment_urls Re-upload to Pylon.
Info

Pylon custom fields support these types: text, number, decimal, boolean, date, datetime, user, url, select, and multiselect. If eDesk has a custom field type that does not map cleanly (e.g., rich text), convert to text and accept the formatting loss. Match option sets for select fields before loading data — Pylon will reject values not in the defined option list. (docs.usepylon.com)

Handling the Order Discrepancy

eDesk relies heavily on Order data. Pylon does not have an Order object. You have three options:

  1. Custom fields on Issues: Create fields for order_id, marketplace, SKU, and tracking_number on the Pylon Issue. Best for fast agent lookup.
  2. Internal notes: Append the eDesk order details as an internal note on the first message of the Pylon Issue. Lower ongoing maintenance, but harder to search.
  3. External system of record: Keep order data in your eCommerce or ERP system and link by customer email or order ID. Best for data integrity if the order data is complex or frequently updated.

For most teams, option 1 gives agents the fastest lookup. Option 3 is better if order records contain nested line items, shipment updates, or refund histories that exceed what custom fields can represent.

Handling Relationships and Dependencies

The load order matters. Pylon Issues reference Accounts and Contacts by ID, so you must create parent records first:

  1. Accounts — Derive from customer email domains. Create via POST /accounts. Use external_id (e.g., the domain string) for idempotency.
  2. Contacts — Create and link to Accounts via account_id. Create via POST /contacts. Use external_id (e.g., eDesk customer ID) for idempotency.
  3. Tags — Create all tags before loading Issues. Pylon tags are referenced by slug in Issue payloads.
  4. Custom Fields — Create field definitions before loading Issues. Note the field slug returned — you will reference it in Issue payloads.
  5. Issues — Create with account_id, requester_id, tags, custom_fields. Use /import/issues for historical data.
  6. Messages — If using /import/issues, messages are included in the Issue payload. If creating Issues via POST /issues, add messages separately.
  7. Attachments — Upload via POST /attachments, then reference the returned URL in Messages.
Warning

Every Issue must be associated with an Account or Contact. If an eDesk ticket lacks a valid customer email, the Pylon API will reject the payload. Build fallback logic to assign orphaned tickets to a default "Unknown Customer" Account and Contact record.

Handling Multi-Language Content

eDesk serves international eCommerce sellers. If your tickets contain non-English content (German, French, Japanese, etc.):

  • Ensure all text fields are UTF-8 encoded before sending to Pylon's API
  • HTML entities in eDesk message bodies (e.g., &auml; for ä) should be preserved or converted to their Unicode equivalents
  • Right-to-left (RTL) languages may require additional HTML direction attributes (dir="rtl") in body_html for correct rendering in Pylon's UI

Migration Architecture

Data Flow

eDesk REST API          →    Staging DB/Files    →    Transform Layer    →    Pylon REST API
(60 req/min extraction)       (local PostgreSQL       (field mapping,          (10 req/min for Issues,
                               or JSON files)          deduplication,            60 req/min for Accounts,
                                                       relationship             Contacts, Tags)
                                                       linking)

eDesk API Details

  • Base URL: https://api.edesk.com/v1/ (v2 also available for some endpoints)
  • Authentication: Bearer token generated in Settings → API Tokens
  • Rate limit: 60 requests per minute per client (developers.edesk.com)
  • Pagination: Offset-based with page and per_page parameters. Max per_page is typically 100. Response includes total and last_page fields for iteration control.
  • Token expiry: 90 days by default (configurable)
  • Plan requirement: Enterprise plan required for API access
  • Key endpoints: GET /tickets, GET /tickets/{id}/messages, GET /customers, GET /sales-orders, GET /tags, GET /ticket-fields
  • Webhooks: Available for ticket create/update/close events. Useful for delta sync during cutover.

Pylon API Details

  • Base URL: https://api.usepylon.com/
  • Authentication: Bearer token (Admin users only can create tokens)
  • Rate limits (per endpoint):
    • GET /issues, POST /issues, /import/issues: 10 requests per minute
    • PATCH /issues/{id}: 20 requests per minute
    • GET /accounts, POST /accounts: 60 requests per minute
    • GET /contacts, POST /contacts: 60 requests per minute
    • POST /attachments: 60 requests per minute
    • GET /knowledge-bases: 60 requests per minute
  • Pagination: Cursor-based. Response includes a cursor field; pass it as a query parameter in subsequent requests.
  • Error format: Standard HTTP status codes. 429 for rate limit exceeded (include Retry-After header in seconds). 422 for validation errors (response body includes field-level error details). 401 for invalid or expired tokens.
  • Idempotency: Use external_id on Accounts and Contacts to enable safe re-runs without duplicates (docs.usepylon.com). Issues created via /import/issues do not currently support external_id — use the edesk_ticket_id custom field for deduplication logic in your pipeline.
  • Sandbox environments: Contact Pylon support to provision a test environment. Alternatively, create a separate Pylon workspace for test migrations — this is the most common approach for validating before production.
Danger

Pylon's 10 requests/minute ceiling on Issues is the primary throughput bottleneck. This rate limit is per-workspace, not per-token — creating multiple API tokens does not increase throughput. Every issue creation and every issue list query consumes from this budget. At this rate, loading 10,000 issues takes approximately 16.7 hours. Loading 50,000 issues takes roughly 3.5 days of continuous writes. Plan your migration timeline around this constraint.

Handling Large Datasets

  • Extraction (eDesk): Batch extraction by date range using created_at_from and created_at_to query parameters. Paginate through results and store raw JSON payloads in a local PostgreSQL table or JSONL files rather than holding them in memory.
  • Loading (Pylon): The rate limit is per-workspace, not per-token. Parallelization of Pylon writes is not possible within a single workspace.
  • Batching strategy: Extract all eDesk data first into a staging layer. Transform offline. Load into Pylon as a separate, throttled operation. This decouples extraction speed from loading constraints.

Step-by-Step Migration Process

Step 1: Extract Data from eDesk

Use the eDesk API to extract all required objects. Start with customers, then tickets with their messages. Order of operations matters — you need customer IDs to resolve foreign keys during transformation.

import requests
import time
import json
 
EDESK_BASE = "https://api.edesk.com/v1"
EDESK_TOKEN = "your_edesk_token"
 
def edesk_get(endpoint, params=None):
    """Make a GET request to eDesk API with automatic retry on rate limit."""
    headers = {"Authorization": f"Bearer {EDESK_TOKEN}"}
    resp = requests.get(f"{EDESK_BASE}/{endpoint}", headers=headers, params=params)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 2))
        time.sleep(retry_after)
        return edesk_get(endpoint, params)
    resp.raise_for_status()
    return resp.json()
 
def extract_all_tickets():
    """Extract all tickets with offset-based pagination."""
    tickets = []
    page = 1
    while True:
        data = edesk_get("tickets", {"page": page, "per_page": 100})
        if not data.get("data"):
            break
        tickets.extend(data["data"])
        if page >= data.get("last_page", page):
            break
        page += 1
    return tickets
 
def extract_messages(ticket_id):
    """Extract all messages for a specific ticket."""
    return edesk_get(f"tickets/{ticket_id}/messages")
 
def extract_all_customers():
    """Extract all customers with pagination."""
    customers = []
    page = 1
    while True:
        data = edesk_get("customers", {"page": page, "per_page": 100})
        if not data.get("data"):
            break
        customers.extend(data["data"])
        if page >= data.get("last_page", page):
            break
        page += 1
    return customers

Step 2: Transform Data

Map eDesk's flat customer model into Pylon's Account → Contact hierarchy. Derive Accounts from customer email domains, filtering out freemail providers.

FREEMAIL_DOMAINS = {
    "gmail.com", "yahoo.com", "hotmail.com", "outlook.com", "aol.com",
    "icloud.com", "mail.com", "protonmail.com", "zoho.com", "yandex.com"
}
 
MARKETPLACE_DOMAINS = {
    "marketplace.amazon.com", "members.ebay.com"
}
 
def derive_account(customer):
    """Derive a Pylon Account from an eDesk customer's email domain.
    Returns None for freemail and marketplace-anonymized addresses."""
    email = customer.get("email", "")
    if not email or "@" not in email:
        return None
    domain = email.split("@")[-1].lower()
    if domain in FREEMAIL_DOMAINS or domain in MARKETPLACE_DOMAINS:
        return None  # No company account for freemail or anonymized users
    return {
        "name": domain,
        "domains": [domain],
        "external_id": f"edesk-domain-{domain}"
    }
 
STATUS_MAP = {
    "open": "waiting_on_you",
    "pending": "waiting_on_customer",
    "closed": "closed",
    "archived": "closed",
}
 
def map_ticket_to_issue(ticket, account_id, contact_id, agent_id_map):
    """Transform an eDesk ticket into a Pylon /import/issues payload.
 
    The /import/issues endpoint accepts the full issue with messages
    in a single request. Each message requires body_html, is_private,
    and exactly one of user_id or contact_id.
    """
    messages = []
    for msg in ticket.get("messages", []):
        message_payload = {
            "body_html": msg.get("body", ""),
            "is_private": msg.get("is_internal", False),
        }
        if msg.get("is_agent"):
            pylon_user_id = agent_id_map.get(msg.get("agent_id"))
            if pylon_user_id:
                message_payload["user_id"] = pylon_user_id
            else:
                message_payload["user_id"] = agent_id_map.get("default")
        else:
            message_payload["contact_id"] = contact_id
        messages.append(message_payload)
 
    return {
        "title": ticket.get("subject") or "(no subject)",
        "body_html": ticket.get("body", "") or "<p>(no body)</p>",
        "account_id": account_id,
        "requester_id": contact_id,
        "state": STATUS_MAP.get(ticket.get("status", "").lower(), "waiting_on_you"),
        "tags": [t["name"] for t in ticket.get("tags", [])],
        "created_at": ticket.get("created_at"),  # ISO 8601 UTC
        "custom_fields": [
            {"slug": "edesk-ticket-id", "value": str(ticket["id"])},
            {"slug": "edesk-channel", "value": ticket.get("channel", "")},
            {"slug": "edesk-original-status", "value": ticket.get("status", "")},
            {"slug": "order-id", "value": ticket.get("order_id", "")},
        ],
        "messages": messages,
    }

Clean HTML formatting where necessary — Pylon renders markdown in its Slack integration. Convert timestamps to ISO 8601 with UTC normalization. Strip Base64-encoded inline images from body_html (see edge cases section).

Step 3: Load into Pylon

Respect rate limits. Create parent records first, then Issues.

For historical ticket backfill, use Pylon's /import/issues endpoint. This accepts the full issue payload including threaded messages in a single request. Accounts, contacts, and users must exist before import. (support.usepylon.com)

import time
import json
import sqlite3
 
PYLON_BASE = "https://api.usepylon.com"
PYLON_TOKEN = "your_pylon_token"
 
# Checkpoint database for resumability
checkpoint_db = sqlite3.connect("migration_checkpoint.db")
checkpoint_db.execute("""
    CREATE TABLE IF NOT EXISTS migrated_issues (
        edesk_ticket_id TEXT PRIMARY KEY,
        pylon_issue_id TEXT,
        created_at TEXT
    )
""")
 
def pylon_post(endpoint, payload):
    """POST to Pylon API with automatic retry on 429."""
    headers = {
        "Authorization": f"Bearer {PYLON_TOKEN}",
        "Content-Type": "application/json"
    }
    resp = requests.post(f"{PYLON_BASE}/{endpoint}", headers=headers, json=payload)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 60))
        print(f"Rate limited. Waiting {retry_after}s. Request ID: {resp.headers.get('x-request-id', 'N/A')}")
        time.sleep(retry_after)
        return pylon_post(endpoint, payload)
    if resp.status_code == 422:
        error_body = resp.json()
        print(f"Validation error: {json.dumps(error_body, indent=2)}")
        raise ValueError(f"Pylon validation error for {endpoint}: {error_body}")
    resp.raise_for_status()
    return resp.json()
 
def is_already_migrated(edesk_ticket_id):
    """Check checkpoint DB to enable resumable migrations."""
    row = checkpoint_db.execute(
        "SELECT pylon_issue_id FROM migrated_issues WHERE edesk_ticket_id = ?",
        (str(edesk_ticket_id),)
    ).fetchone()
    return row is not None
 
def load_issues(issues):
    """Load issues into Pylon via /import/issues, respecting 10 req/min limit.
    Checkpoints each successful create for resumability."""
    for i, (edesk_id, payload) in enumerate(issues):
        if is_already_migrated(edesk_id):
            print(f"Skipping already-migrated ticket {edesk_id}")
            continue
        result = pylon_post("import/issues", payload)
        pylon_id = result["data"]["id"]
        checkpoint_db.execute(
            "INSERT INTO migrated_issues VALUES (?, ?, datetime('now'))",
            (str(edesk_id), pylon_id)
        )
        checkpoint_db.commit()
        print(f"Created issue {pylon_id} from eDesk #{edesk_id} ({i+1}/{len(issues)})")
        time.sleep(6)  # 10 req/min = 1 request per 6 seconds

Step 4: Migrate Attachments

Attachments require a separate process. Download the binary file from eDesk's CDN URL, upload to Pylon via POST /attachments, then reference the returned URL when creating the Message. This triples the API call count per attachment (download from eDesk, upload to Pylon, associate with message).

def migrate_attachment(edesk_attachment_url, pylon_issue_id):
    """Download from eDesk CDN and re-upload to Pylon."""
    # Step 1: Download binary from eDesk
    file_resp = requests.get(edesk_attachment_url)
    file_resp.raise_for_status()
 
    # Step 2: Upload to Pylon
    headers = {"Authorization": f"Bearer {PYLON_TOKEN}"}
    files = {"file": ("attachment", file_resp.content)}
    upload_resp = requests.post(
        f"{PYLON_BASE}/attachments",
        headers=headers,
        files=files
    )
    if upload_resp.status_code == 429:
        time.sleep(int(upload_resp.headers.get("Retry-After", 60)))
        return migrate_attachment(edesk_attachment_url, pylon_issue_id)
    upload_resp.raise_for_status()
    return upload_resp.json()["data"]["url"]

Important: If eDesk CDN URLs are not stable long-term, add an intermediate object-store step (S3, GCS) during extraction so you are not depending on eDesk infrastructure after contract cancellation. Download all attachments to your own storage during the extraction phase, then upload to Pylon from there. (docs.usepylon.com)

Step 5: Migrate Knowledge Base

eDesk's knowledge base articles must be migrated separately from ticket data. The process:

  1. Extract from eDesk: Use the eDesk API to pull KB articles including title, body (HTML), category, and published status.
  2. Transform: Convert eDesk's article HTML to match Pylon's expected format. Update any internal links that reference eDesk URLs.
  3. Load into Pylon: Use Pylon's Knowledge Base API (GET /knowledge-bases to find your KB ID, then create articles via the appropriate endpoint). Rate limit: 60 req/min.

If your KB contains fewer than 50 articles, manual recreation through Pylon's UI may be faster than building an API pipeline.

Step 6: Validate

After loading, run validation checks (covered in the Validation section below).

Edge Cases & Challenges

Duplicate Customer Records

eDesk frequently creates separate customer records for the same person across different marketplace channels. A customer who emails from a personal address and buys via Amazon may have two or three eDesk profiles. Before loading into Pylon, deduplicate by email address and merge into a single Contact. Use external_id on Pylon contacts to prevent re-creating duplicates on subsequent runs. (docs.usepylon.com)

Deduplication strategy: Group eDesk customer records by normalized email (lowercase, strip whitespace). For records sharing the same email, merge names (prefer the most recent), combine tag sets, and consolidate ticket references. For marketplace-anonymized emails (abc123@marketplace.amazon.com), treat each as a separate contact — there is no way to link them to real identities.

Missing or Inconsistent Data

  • eDesk tickets from marketplace channels may lack a subject line. Pylon Issues require a title. Default to "(no subject)" or derive from the first 100 characters of the first message body.
  • Customer emails from marketplace messaging (e.g., Amazon buyer messages) use anonymized addresses like abc123@marketplace.amazon.com. These cannot be matched to real contacts and will not produce useful Account derivations. Assign them to a catch-all "Marketplace Customers" Account.
  • Some eDesk tickets may have null or empty created_at timestamps. Default to the earliest message timestamp in the thread, or the current time as a last resort.

Multi-Channel Threading

eDesk flattens emails, Amazon messages, and live chats into a single ticket feed. Pylon expects clearer threading. If a customer switched channels mid-conversation in eDesk, insert an internal note in Pylon to denote where a channel switch occurred in the historical thread:

# Example: Insert a channel-switch note
if previous_msg_channel != current_msg_channel:
    messages.insert(i, {
        "body_html": f"<p><em>[Channel switched from {previous_msg_channel} to {current_msg_channel}]</em></p>",
        "is_private": True,
        "user_id": system_user_id
    })

Inline Images and Base64 Encoding

eDesk sometimes embeds images as Base64 strings in the HTML body (e.g., <img src="data:image/png;base64,iVBOR..."/>). Pylon's API may reject payloads that exceed size limits due to large Base64 strings. During transformation:

  1. Scan body_html for Base64 image patterns using regex: data:image/[^;]+;base64, [A-Za-z0-9+/=]+
  2. Extract each image, decode to binary, and upload as a standard attachment via POST /attachments
  3. Replace the Base64 src attribute with the returned Pylon attachment URL

API Failures and Retries

  • eDesk: Returns 429 when rate-limited. The Retry-After header specifies wait time in seconds. Implement exponential backoff starting at 2 seconds, capped at 60 seconds.
  • Pylon: Returns 429 when rate limit is exceeded. Use the Retry-After header if provided, otherwise wait 60 seconds. Log the Pylon x-request-id from response headers for debugging with Pylon support. Returns 422 for validation errors — the response body contains an errors object with field-level details.
  • Idempotency: Store eDesk ticket IDs as Pylon custom fields (edesk-ticket-id) and query before creating to prevent duplicates on re-runs. Use a resilient queue (BullMQ in Node.js, Celery in Python) with exponential backoff for production migrations — do not use simple for loops without checkpointing.

Sample Pylon error responses:

// 429 Rate Limit
{"error": "Rate limit exceeded", "retry_after": 60}
 
// 422 Validation Error
{"errors": {"title": ["can't be blank"], "account_id": ["must reference a valid account"]}}

Status Mapping Edge Cases

eDesk has statuses like Archived and Spam that have no direct Pylon equivalent. Map Archived to closed and either exclude Spam tickets entirely or store the original eDesk status in a custom field for audit purposes. (developers.edesk.com)

If your eDesk instance uses custom statuses beyond the defaults, document each one and map it to the closest Pylon state. Store the original in the edesk-original-status custom field.

Limitations & Constraints

Pylon Limitations vs eDesk

Limitation Impact
No Sales Order object eCommerce order context is lost unless flattened to custom fields
No marketplace channel support Amazon, eBay, Shopify channel data has no target
Issues API: 10 req/min Migration throughput is severely constrained (16.7 hrs per 10K issues)
No bulk import endpoint for Issues Each /import/issues call creates one issue with its messages
Custom Objects require Enterprise plan Cannot store arbitrary eDesk data structures without Enterprise
No external_id on Issues Deduplication must use custom fields, not native idempotency
No bulk delete API for Issues Rollback of failed migrations requires manual cleanup or Pylon support

Data That Will Not Transfer

  • Marketplace SLAs: eDesk's Amazon/eBay SLA timers do not translate to Pylon. Pylon uses standard B2B SLA policies based on priority and account tier.
  • Agent performance metrics: eDesk's CSAT data and response-time metrics tied to agents have no direct import path into Pylon's analytics.
  • Live open tickets: Pylon's standard migration path is for historical closed tickets. Open-ticket continuity requires a separate cutover workflow where agents manually re-engage in Pylon. (support.usepylon.com)
  • Channel-specific metadata: Seller SKU, tracking number, and marketplace-specific fields must be stored as custom fields or discarded.
  • eDesk Smart Inbox rules and AI classifications: These are eDesk-proprietary and must be rebuilt using Pylon's trigger and automation system.

Potential Data Loss Scenarios

Scenario Mitigation
Tickets created between extraction start and go-live Delta sync via eDesk webhooks or polled re-extraction
Inline images referencing eDesk's CDN (URLs expire after contract cancellation) Download all attachment URLs during extraction phase
Marketplace-anonymized emails that cannot resolve to real contacts Assign to catch-all Account; flag in custom field
eDesk custom field values that exceed Pylon's text field length limits Truncate with ellipsis; store full value in an internal note
Agent accounts that do not exist in Pylon Map to a default "Migrated Agent" user; note original agent in custom field

Validation & Testing

Do not assume a 200 OK response means the data is correct.

Record Count Comparison

Object eDesk Count Pylon Count Status
Customers → Contacts ✓ / ✗
Derived Accounts ✓ / ✗
Tickets → Issues ✓ / ✗
Messages ✓ / ✗
Attachments ✓ / ✗
Tags ✓ / ✗

Count discrepancies are expected and acceptable in specific cases: duplicate customers merged into fewer contacts, spam tickets excluded, and orphaned records dropped. Document every expected discrepancy.

Field-Level Validation

For a random sample of 50–100 records, compare:

  • Ticket subject → Issue title
  • Ticket status → Issue state (verify mapping logic)
  • Message count per ticket → Message count per Issue
  • Attachment count per message
  • Customer email → Contact email
  • Tag assignments
  • Custom field values (especially edesk-ticket-id for traceability)

Sampling Strategy

Use a structured sample instead of random selection alone:

  • 10 tickets with 1 message each (baseline)
  • 10 tickets with 10+ messages (threading validation)
  • 10 tickets with attachments (binary transfer validation)
  • 10 tickets with custom fields (type mapping validation)
  • 10 tickets from each channel (email, marketplace, chat)
  • 5 tickets with Archived or Spam status (edge case validation)

UAT Process

  1. Run a test migration with 5–10% of data into a separate Pylon workspace (contact Pylon support to provision, or create a test workspace)
  2. Have 2–3 support agents verify 20 tickets each against the eDesk originals, using a structured checklist (subject, status, message count, attachments, custom fields, tags)
  3. Test Pylon workflows (triggers, SLAs, assignments) with migrated data to confirm automation rules fire correctly
  4. Validate that links between Accounts → Contacts → Issues resolve correctly in the Pylon UI
  5. Verify that Slack/Teams channel integrations display migrated Issues properly

Rollback Planning

Pylon does not offer a bulk delete API for Issues. If validation fails:

  • For small datasets (<500 Issues): Delete individually via the Pylon UI or DELETE /issues/{id} API calls (if available — verify with Pylon support)
  • For large datasets: Contact Pylon support for bulk cleanup assistance, or provision a fresh Pylon workspace and re-run the migration
  • Best practice: Always run test migrations into a separate workspace before touching production. The cost of a second workspace is negligible compared to recovering from a bad production load.

For a full post-migration QA workflow, see our Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration.

Post-Migration Tasks

Rebuild Automations

eDesk's rules and templates do not transfer. You must manually recreate:

  • eDesk templates → Pylon Macros (recreate text, variables, and any conditional logic)
  • eDesk auto-assignment rules → Pylon Triggers (define matching conditions and assignment actions)
  • eDesk SLA rules → Pylon SLA policies (configure response and resolution time targets by priority/account tier)
  • eDesk auto-responders → Pylon AI or Trigger-based responses

Document each eDesk automation before decommissioning. Create a mapping spreadsheet: eDesk rule name, trigger condition, action, and corresponding Pylon trigger configuration.

Delta Sync

Run a final sync to capture tickets created or updated in eDesk during the migration window. Two approaches:

  1. Webhook-based: If you configured eDesk webhooks before migration, replay buffered events through your transform-and-load pipeline.
  2. Poll-based: Query eDesk API for tickets with updated_at greater than your extraction start timestamp. Transform and load only net-new records (check against your checkpoint database to avoid duplicates).

Keep eDesk in read-only mode (remove agent access) until signoff.

DNS and Routing Cutover

  • Update support email forwarding rules (MX records or forwarding rules) to point to Pylon instead of eDesk
  • If you use shared Slack channels, connect them to Pylon before go-live
  • Update any CNAME records for your help center domain if migrating the knowledge base
  • Disable eDesk email ingestion to prevent duplicate ticket creation during the transition

CRM Sync Caveat

If you enable CRM attribute syncing (e.g., Salesforce, HubSpot) after migration, test on a subset of 10–20 Accounts first. Pylon documents that CRM-to-Pylon sync can overwrite or clear existing Pylon values when the CRM field is blank. This can wipe out custom field data you just migrated. (support.usepylon.com)

User Training

The UX paradigm shift is significant. Agents accustomed to eDesk's order-centric inbox will need training on:

  • Pylon's Account-centric navigation (finding Issues by company, not by order number)
  • Slack/Teams-based conversation flows (responding in-channel vs. in-app)
  • Issue states vs. eDesk ticket statuses (fewer states, different semantics)
  • Custom field usage for order data lookups (where order context now lives)
  • Using the edesk-ticket-id custom field to cross-reference historical tickets

Plan for 1–2 hours of hands-on training per agent, plus a reference guide covering the status mapping and custom field conventions.

Monitor for Inconsistencies

For the first 2 weeks post-migration:

  • Spot-check 10 random Issues daily against eDesk originals
  • Monitor for missing attachments (CDN URL expiry from eDesk)
  • Verify that Pylon triggers fire correctly on migrated data
  • Watch for duplicate Contacts created by new incoming messages from customers who already exist
  • Check that CRM sync has not overwritten migrated custom field values
  • Monitor Pylon's error logs for any delayed-processing failures

Best Practices

  1. Never migrate directly from production to production. Extract to a secure intermediary database first. This protects against network timeouts and allows re-running transformations without re-extracting.
  2. Run test migrations. Push 5–10% of data into a separate Pylon workspace to validate mapping logic before touching production.
  3. Validate incrementally. Check after each phase (Accounts, Contacts, Issues, Messages) — do not wait until the end.
  4. Store eDesk IDs. Save the original edesk_ticket_id in a Pylon custom field. If a customer replies to an old eDesk email thread months later, your routing rules can use that ID to find the correct Pylon Issue.
  5. Use external_id on Accounts and Contacts. Set deterministic external IDs so re-runs are idempotent and do not create duplicates. (docs.usepylon.com)
  6. Plan for the gap. Tickets created in eDesk between extraction and go-live need a delta sync strategy using webhooks or polled re-extraction.
  7. Archive what Pylon cannot hold. Export Sales Orders, tracking data, and marketplace metadata to a separate data store (S3, BigQuery, or your ERP) before cancelling eDesk.
  8. Automate repetitive transforms. Field mapping, deduplication, and status conversion should be scripted — not manual.
  9. Checkpoint every write. Write each created Pylon ID to a local mapping store (SQLite or JSON) as you go. If the migration fails at record 5,000 of 10,000, you must resume from 5,001 — not restart from zero.
  10. Download all attachments during extraction. Do not depend on eDesk CDN URLs remaining accessible after your contract ends. Store them in your own object storage before loading into Pylon.

Automation Script Outline

For engineering teams building this internally, here is a high-level Python structure for an end-to-end API-based migration:

# edesk_to_pylon_migration.py
# Production code requires error handling, logging, checkpointing,
# and idempotency checks beyond what is shown here.
 
import logging
import sqlite3
from dataclasses import dataclass
from datetime import datetime
 
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("migration")
 
@dataclass
class MigrationConfig:
    edesk_token: str
    edesk_base_url: str = "https://api.edesk.com/v1"
    pylon_token: str = ""
    pylon_base_url: str = "https://api.usepylon.com"
    pylon_issues_rate_limit: float = 6.0  # seconds between requests (10 req/min)
    pylon_general_rate_limit: float = 1.0  # seconds between requests (60 req/min)
    checkpoint_db_path: str = "migration_checkpoint.db"
 
def main(config: MigrationConfig):
    checkpoint_db = sqlite3.connect(config.checkpoint_db_path)
    init_checkpoint_tables(checkpoint_db)
 
    # Phase 1: Extract from eDesk
    logger.info("Phase 1: Extracting from eDesk...")
    customers = extract_edesk_customers(config)
    tickets = extract_edesk_tickets_with_messages(config)
    logger.info(f"Extracted {len(customers)} customers, {len(tickets)} tickets")
 
    # Phase 2: Transform
    logger.info("Phase 2: Transforming data...")
    accounts = derive_accounts_from_customers(customers)
    contacts = transform_customers_to_contacts(customers)
    issues = transform_tickets_to_issues(tickets)
    logger.info(f"Derived {len(accounts)} accounts, {len(contacts)} contacts, {len(issues)} issues")
 
    # Phase 3: Load into Pylon (order matters)
    logger.info("Phase 3: Loading into Pylon...")
    account_id_map = load_accounts(config, accounts, checkpoint_db)     # 60 req/min
    contact_id_map = load_contacts(config, contacts, checkpoint_db)     # 60 req/min
    create_tags(config, extract_unique_tags(tickets))                   # 60 req/min
    create_custom_fields(config)                                        # One-time setup
    issue_id_map = load_issues(config, issues, checkpoint_db)           # 10 req/min ← bottleneck
    load_attachments(config, tickets, issue_id_map, checkpoint_db)      # 60 req/min
 
    # Phase 4: Validate
    logger.info("Phase 4: Validating...")
    validate_record_counts(config, len(tickets), len(customers))
    validate_sample_records(config, tickets[:50], issue_id_map)
 
    logger.info(f"Migration complete at {datetime.utcnow().isoformat()}")
 
if __name__ == "__main__":
    config = MigrationConfig(
        edesk_token="your_token",
        pylon_token="your_token"
    )
    main(config)
Tip

Checkpoint everything. Write each created Pylon ID to a local mapping file (SQLite is recommended over JSON for concurrent access safety) as you go. If the migration fails at record 5,000 of 10,000, you need to resume from 5,001 — not restart from zero. The code samples above demonstrate this pattern with the migration_checkpoint.db SQLite database.

Making the Decision

This migration is technically straightforward in concept but operationally demanding in execution. The data model gap — eDesk's eCommerce focus vs. Pylon's B2B focus — means you will make structural decisions about what data to keep, what to archive, and what to discard. The rate limit on Pylon's Issues API (10 req/min) is the single biggest factor in your migration timeline.

Timeline estimates by dataset size:

Dataset Size Estimated Load Time (Issues only) Total Migration (including planning, testing, validation)
1,000 tickets ~1.7 hours 1 week
5,000 tickets ~8.3 hours 1–2 weeks
10,000 tickets ~16.7 hours 2–3 weeks
50,000 tickets ~3.5 days 4–6 weeks
100,000 tickets ~6.9 days 6–8 weeks

For teams with fewer than 5,000 tickets and available engineering bandwidth, a self-built API migration is achievable in 1–2 weeks. For anything larger, the math on engineering time, rate limit management, and validation overhead usually favors a managed service or third-party tool.

For most teams, the right sequence is: rebuild support processes in Pylon first, cut over open work with a planned live handoff, and backfill closed history second. If you are planning a live cutover window, Zero-Downtime Help Desk Data Migration is the right companion read.

Frequently Asked Questions

Can I migrate from eDesk to Pylon using CSV export?
Not for production use. eDesk's Search Download is Enterprise-only and capped at 1,000 rows. It excludes conversation history, attachments, and internal notes. You need the eDesk API (Enterprise plan required) to extract full ticket data, then load via Pylon's REST API or historical import endpoint.
What are the Pylon API rate limits for migration?
Pylon's Issues endpoints (GET and POST) are limited to 10 requests per minute. Account, Contact, and most other endpoints allow 60 requests per minute. Issue updates allow 20 per minute. This means loading 10,000 issues takes approximately 16.7 hours of continuous API writes.
How long does an eDesk to Pylon migration take?
It depends on volume. Loading into Pylon is the bottleneck at 10 Issues/min. A 5,000-ticket migration takes roughly 8+ hours of load time. A 50,000-ticket migration takes 3–4 days. Add 1–2 weeks for pipeline development and validation if building in-house.
What eDesk data is lost when migrating to Pylon?
Pylon has no native equivalent for eDesk Sales Orders, order tracking links, marketplace channel metadata, or order notes. This data must be archived separately or flattened into Pylon custom fields. Marketplace-anonymized email addresses also cannot be resolved to real contacts.
Can open eDesk tickets stay live after moving to Pylon?
Not through standard historical backfill. Pylon's documented migration path covers closed tickets and knowledge base content. Live open-ticket continuity requires a separate cutover or handoff workflow.

More from our Blog

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration
Help Desk

Post-Migration QA: 20 Tests to Run After Your Help Desk Data Migration

Ensure your help desk migration is a success with this comprehensive 20-point post-migration QA checklist. This expert guide details the 20 essential tests needed to validate your data integrity, system functionality, user-friendliness, and performance . Learn exactly how to check everything from ticket data, attachments, and knowledge base articles to critical workflows, automations, and integrations before you go live. This process is your final line of defense against lost tickets, broken workflows, and unhappy customers.

Raaj Raaj · · 8 min read