Skip to content

Pylon to eDesk Migration: The CTO's Technical Guide

A CTO-level technical guide for migrating from Pylon to eDesk. Covers data model mapping, API rate limits, migration approaches, edge cases, and validation.

Raaj Raaj · · 30 min read
Pylon to eDesk 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

Pylon to eDesk Migration: The CTO's Technical Guide

Migrating from Pylon to eDesk is not a one-click import. You are moving from an AI-native, Slack-first B2B support platform to an eCommerce-first helpdesk built around marketplace integrations and order data. The data models serve fundamentally different audiences, there is no native migration path between them, and the API rate limits on both sides constrain throughput enough to make brute-force approaches impractical.

If you are searching for how to migrate from Pylon to eDesk, the short answer is: a custom API-based ETL pipeline or a managed migration service. CSV exports lose thread context and attachments, middleware tools like Zapier cannot handle Pylon's nested Issue→Message→Thread structure, and eDesk has no verified bulk ticket import capability. (docs.usepylon.com)

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

Why Teams Move from Pylon to eDesk

The shift is architectural. 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. eDesk is purpose-built for eCommerce support, with 250+ native marketplace integrations (Amazon, eBay, Shopify, Walmart) and order data surfaced directly inside every ticket.

Teams migrate for three main reasons:

  • Channel pivot to eCommerce. A company evolving from B2B SaaS support to direct-to-consumer or marketplace selling needs order-aware tooling. eDesk pulls order numbers, tracking data, and customer purchase history into every ticket natively — something Pylon does not do.
  • Marketplace compliance. Amazon, eBay, and Walmart enforce strict SLA response windows (Amazon requires responses within 24 hours; eBay within 24–48 hours depending on the query type). eDesk's native integrations track these SLAs automatically and surface breach risk. Pylon has no marketplace integration layer.
  • Cost consolidation. Teams running Pylon for support alongside a separate eCommerce helpdesk can consolidate into eDesk for a simpler, single-platform operation. eDesk publishes standard plan pricing starting at $89/agent/month for the Professional tier, with API access restricted to the Enterprise tier. (edesk.com)
Info

Marketing automation caveat. If the real goal is lead nurturing, pipeline management, or campaign automation, eDesk is the wrong system of record. Its data model centers on tickets, messages, contacts, sales orders, tags, templates, and custom fields — not CRM opportunities or marketing objects. (developers.edesk.com)

Core Differences: Pylon vs eDesk Data Model

Concept Pylon eDesk
Core support object Issue (conversational, Slack-native) Ticket (email/marketplace-native)
Customer grouping Account (company-level, CRM-synced) Customer (individual, order-linked)
Individual contacts Contact (tied to Account) Customer / Contact (flat, email-based)
Conversations Messages + Threads per Issue Messages per Ticket
Custom data Custom Fields + Custom Objects (Enterprise) Custom Fields only (Text, Date, Select, Number, Yes/No, URL, Email)
Channels Slack Connect, Teams, Discord, Email, Chat widget, WhatsApp Amazon, eBay, Shopify, Walmart, Email, Chat, 250+ marketplaces
Knowledge base Native KB with articles and collections No native KB (relies on integrations)
AI capabilities AI-native issue resolution, account intelligence, sentiment data AI chatbot, AI composer, sentiment analysis
Tags Tags (flat) Tags + Tag Groups
Automations Triggers (condition-based) Rules, HandsFree automation
Priority Native priority field No native priority field — use tags or custom fields
Account intelligence Health scores, AI summaries, CRM sync fields ❌ Not supported

The critical architectural difference: Pylon is Account-centric (every Issue belongs to an Account), while eDesk is Order-centric (tickets are linked to sales orders and marketplace channels). This means your migration is not just a data move — it is a data model translation.

Migration Approaches

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

How it works: Extract data from Pylon using its REST API endpoints (Issues, Accounts, Contacts, Messages, Custom Fields, Tags), transform the data to match eDesk's schema, and load it via eDesk's Create Ticket and Create Message endpoints.

When to use: Any migration above 500 issues, or when you need to preserve message threading, timestamps, and custom field values.

Pros:

  • Full control over data transformation
  • Preserves message history and threading
  • Handles custom fields programmatically
  • Can be automated and repeated for test runs

Cons:

  • Pylon's Issues search endpoint is rate-limited to 10 requests per minute; issue messages at 20 per minute (docs.usepylon.com)
  • eDesk's API is limited to 60 requests per minute account-wide (developers.edesk.com)
  • eDesk API access requires the Enterprise plan — no API access on lower tiers (support.edesk.com)
  • Requires development effort (Python/Node.js scripting)
  • No bulk import endpoint on eDesk — tickets must be created one at a time

Scalability: Works for small to enterprise datasets, but rate limits extend migration time for large volumes. A 10,000-issue migration at eDesk's 60 req/min ceiling (accounting for ticket creation + message creation per ticket) takes 6–10 hours of continuous API calls. See the rate-limit math section below for the full calculation.

Complexity: High

2. CSV Export/Import

How it works: Export data from Pylon via API extraction to CSV, then attempt to load into eDesk.

When to use: Small datasets (under 500 issues) where message history preservation is not critical.

Pros:

  • Simple to understand
  • Good for auditing data before a full migration
  • Useful as a staging layer for human review

Cons:

  • Pylon has no native CSV export for Issues — you must extract via API and write to CSV yourself
  • eDesk's public documentation does not document a general historical ticket CSV import path (support.edesk.com)
  • Loses message threading, attachments, and internal notes
  • Custom fields, tags, and organizations cannot be imported automatically

Scalability: Small datasets only.

Complexity: Medium (extraction is still API-dependent)

3. Third-Party Migration Tools

How it works: Services like Help Desk Migration provide a wizard-based interface for moving data between helpdesks.

When to use: When you want a UI-driven approach and your data fits standard ticket/contact schemas.

Pros:

  • No coding required
  • Handles field mapping via UI
  • Supports demo migrations for validation

Cons:

  • Pylon is not listed as a supported source on Help Desk Migration, Migrations Wizard, or other major third-party migration tools as of July 2025
  • eDesk import has known limitations: cannot import groups, organizations, inline images, tags, custom fields, macros, triggers, or automation settings via automated tools
  • Attachment size limited to 10MB on eDesk
  • Pylon's partner accounts, subaccounts, and Slack-thread data model are where generic connectors run out of parity (docs.usepylon.com)

Scalability: Medium — dependent on tool capabilities.

Complexity: Low to Medium

4. Custom ETL Pipeline

How it works: Build a dedicated Extract-Transform-Load pipeline using Python, Node.js, or a data orchestration tool (Airbyte, Airflow). Extract from the Pylon API, transform in a staging database (PostgreSQL or SQLite for smaller datasets), load into the eDesk API.

When to use: Enterprise migrations with complex transformation requirements, custom objects, multi-entity data, compliance needs, or years of history.

Pros:

  • Full control over every aspect of the migration
  • Can handle custom objects by flattening them into eDesk custom fields or notes
  • Supports incremental migration and delta syncs
  • Built-in error handling, retry logic, and audit trails
  • Staging database enables SQL-based validation queries

Cons:

  • Highest development effort (estimated 60–120 hours for a mid-complexity enterprise migration)
  • Requires staging infrastructure
  • Must handle rate limiting, pagination, and error recovery manually

Scalability: Enterprise-grade.

Complexity: High

5. Middleware Platforms (Zapier, Make)

How it works: Use Zapier or Make to connect Pylon triggers to eDesk actions, creating tickets in eDesk when issues are created or updated in Pylon.

When to use: Ongoing sync of new tickets during a transition period only — not historical migration.

Pros:

  • No code required
  • Good for parallel-run periods

Cons:

  • Cannot migrate historical data
  • Cannot handle Pylon's nested message/thread structure
  • Rate limits on both sides make bulk operations impractical
  • No control over timestamp preservation
  • Pylon's message-mirroring guidance describes some API flows as experimental — a poor foundation for a permanent migration path (support.usepylon.com)

Scalability: Not suitable for migration — only for ongoing sync of new records.

Complexity: Low

Migration Approach Comparison

Approach Historical Data Attachments Custom Fields Complexity Best For
API-to-API ✅ Full ✅ With extra work ✅ Manual mapping High Most migrations
CSV Export/Import ⚠️ Partial ❌ Lost ❌ Manual Medium Small, simple datasets
Third-Party Tools ⚠️ Limited ⚠️ Size-limited (10MB) ⚠️ Limited Low–Med Standard schemas only
Custom ETL Pipeline ✅ Full ✅ Full ✅ Full High Enterprise, complex data
Middleware (Zapier/Make) ❌ No ❌ No ❌ No Low Ongoing sync only

Recommendations by scenario:

  • Small team, < 500 issues, simple fields: API-to-API migration script with basic rate-limit handling.
  • Mid-market, 500–10,000 issues: API-to-API migration script with exponential backoff, staging database, and validation queries.
  • Enterprise, 10,000+ issues, custom objects: Custom ETL pipeline with orchestration tooling (Airflow/Prefect) or managed migration service.
  • Ongoing sync during parallel run: Middleware for new records + API migration for historical data.

GDPR and Data Residency Considerations

Migrating support data between platforms involves transferring PII (customer names, email addresses, message content, order details). For EU-based teams or teams handling EU customer data:

  • Data Processing Agreements (DPAs): Ensure you have signed DPAs with both Pylon and eDesk before initiating any data extraction or transfer.
  • Data residency: Verify where each platform stores data. If Pylon stores data in the US and eDesk in the EU (or vice versa), you may need to assess Standard Contractual Clauses or adequacy decisions under GDPR Chapter V.
  • Staging infrastructure: If you use a staging database during migration, ensure it is hosted in a compliant region. An S3 bucket in eu-west-1 or an EU-based PostgreSQL instance may be required.
  • Right to erasure: After migration, you may need to delete Pylon data to satisfy GDPR Article 17 if you no longer have a lawful basis for retention. Document your retention schedule.
  • PII in attachments: Scan attachments for sensitive data (ID documents, financial information) that may require encryption in transit and at rest during the migration process.

When a Managed Migration Service Makes Sense

Building a Pylon-to-eDesk migration in-house is feasible but the cost is rarely just the initial script. The real expense is handling the 10–15% of records that fail silently, the custom objects that do not map cleanly, and the attachments that exceed eDesk's 10MB limit.

Signs that a managed service reduces total cost:

  • Your engineering team is already committed to product work and the migration would block 40–120 hours of sprint capacity
  • You have custom objects in Pylon that need flattening for eDesk
  • You have more than 5,000 issues with attachment-heavy threads
  • You need zero downtime during cutover
  • You have GDPR or data residency requirements that complicate staging infrastructure
  • You cannot afford a failed migration followed by a re-import

Typical cost components of DIY migration:

Component Estimated Hours
API extraction script development 10–20 hrs
Transform logic + field mapping 15–30 hrs
Rate-limit handling + retry logic 5–10 hrs
Staging DB setup + validation queries 5–10 hrs
Test runs (minimum 2 full runs) 5–15 hrs
Post-migration debugging + cleanup 10–20 hrs
Total 50–105 hrs

At an internal engineering cost of $75–150/hr, that is $3,750–$15,750 in engineering time for a mid-complexity migration — before accounting for opportunity cost.

Pre-Migration Planning

A successful migration is 80% planning and 20% execution.

Data Audit Checklist

Before extracting anything, catalog what exists in Pylon:

  • Accounts — total count, active vs inactive, partner accounts, subaccounts, custom fields per account
  • Contacts — total count, contacts per account, email coverage (critical — eDesk relies on email matching). Flag contacts without email addresses.
  • Issues — total count by status (open, closed, snoozed), date range, average messages per issue
  • Messages — total count, attachment count, average attachment size
  • Custom Fields — list all fields by object type (account, contact, issue), note data types
  • Custom Objects — Enterprise-only; list all object types, record counts, and relationships
  • Tags — full tag list, usage frequency
  • Knowledge Base — article count, collection structure (eDesk has no native KB)
  • AI-generated data — account intelligence scores, AI-generated issue summaries, sentiment labels. These are computed fields that will not transfer to eDesk; decide whether to archive them as notes or discard. (docs.usepylon.com)
  • CRM-adjacent data — leads, opportunity stage, tier, renewal date, CSM ownership, and any metadata synced into Pylon

Identify What Not to Migrate

  • Duplicate or test issues
  • Inactive contacts with no associated issues
  • Internal-only notes that have no value in eDesk
  • Knowledge Base articles (eDesk has no native KB — plan an alternative destination such as Zendesk Guide, Notion, Confluence, or Document360)
  • Custom Objects that eDesk cannot represent (export to CRM or archive)
  • CRM pipeline data (leads, opportunity stages, renewal dates) that should stay in your CRM — eDesk has no first-class opportunity object (developers.edesk.com)
  • Pylon's AI-generated account intelligence scores and sentiment data (these are platform-specific computed values)

Migration Strategy

Strategy Description Risk Level Best For
Big bang Migrate everything in a single cutover window Medium–High Small datasets (<2,000 issues), clear cutover date
Phased Migrate by account or date range in stages Low–Medium Large datasets, risk-averse teams
Incremental Migrate historical data first, then delta-sync new records until cutover Low Teams needing zero downtime

For most Pylon-to-eDesk migrations, phased by date range works best. Migrate the last 12–24 months of active issues first, validate, then backfill older data if needed. Keep both systems running, train your agents on eDesk, and run a final delta sync before the cutover.

Data Model & Object Mapping

This is where the migration gets real. Pylon and eDesk model support data differently at every level.

Object-Level Mapping

Pylon Object eDesk Equivalent Notes
Account ❌ No direct equivalent eDesk has no company/organization object. Account-level data must be flattened into customer records, ticket custom fields, or internal notes.
Contact Customer eDesk customers are identified by email. One Pylon Account with 5 Contacts becomes 5 separate eDesk Customers.
Issue Ticket Direct mapping. Pylon Issue status → eDesk Ticket status.
Message (customer reply) Message (inbound) Maps to eDesk inbound message on ticket.
Message (agent reply) Message (outbound) Maps to eDesk outbound message.
Internal Note Internal Note eDesk supports internal notes on tickets.
Tag Tag Must pre-create tags in eDesk (with Tag Groups if needed).
Custom Field (Issue) Custom Field (Ticket) Type mapping required — Pylon types must match eDesk types (Text, Date, Select, Number, Yes/No, URL, Email).
Custom Field (Account) Custom Field (Ticket) or lost eDesk has no account-level custom fields. Flatten to ticket-level or store in notes.
Custom Object ❌ Not supported eDesk has no custom object support. Flatten to custom fields, export separately, or store as structured internal notes.
Knowledge Base Article ❌ No native KB Migrate to a separate KB tool (Zendesk Guide, Notion, Confluence) or accept the loss.
Feature Request ❌ No equivalent Export for archival; eDesk has no feature request tracking.
Survey / CSAT ❌ No direct import eDesk has its own feedback system — historical CSAT data cannot be imported.
AI Account Intelligence ❌ No equivalent Health scores, AI summaries, and account-level sentiment are Pylon-specific. Archive as notes or discard.
Warning

Account flattening is the biggest design decision. Pylon's Account object holds company-level data (health scores, CRM syncs, partner accounts, subaccounts). eDesk has no equivalent. You must decide whether to: (a) store Account names as a custom field on each ticket, (b) embed account data in internal notes, (c) keep it in your CRM, or (d) accept the data loss. Document this decision and get stakeholder sign-off before proceeding. (docs.usepylon.com)

Field-Level Mapping

Pylon Issue Field eDesk Ticket Field Transformation
id Custom Field: pylon_id Store Pylon ID for cross-reference and delta syncs
title subject Direct mapping; fallback to "Imported Thread" if null
status status Map: new/waiting_on_you → Open, waiting_on_customer → Pending, closed → Resolved. Test on_hold mapping in sandbox. (docs.usepylon.com)
assignee_id owner_user_id Map Pylon user IDs to eDesk user IDs. eDesk tickets have one assigned owner — Pylon team assignments and followers must collapse to a single owner.
account_id Custom field or note No direct equivalent in eDesk
requester_id contact_id Map Pylon contact to eDesk customer by email
created_at created_at Verify eDesk API preserves historical timestamps (see callout below)
updated_at updated_at May be overwritten by eDesk on import
tags [] tags_ids [] Pre-create tags, map by name to eDesk tag IDs
custom_fields [] custom_fields [] Map field IDs; validate type compatibility
priority Custom field or tag eDesk has no native priority field — use tags or custom fields
message.body message.body Convert Slack markdown to HTML; strip <@U123456> mention tags
message.attachments [] message.attachments [] 10MB limit on eDesk; pre-scan and compress or host externally
Info

Timestamp preservation: eDesk's Create Ticket API (POST /v1/tickets) and Create Message API both document a created_at field (developers.edesk.com), but you must verify with a single test ticket whether the provided value is respected or overwritten with the current server time. In our testing, the created_at value on the ticket was preserved, but updated_at was overwritten to the current time upon message creation. Losing historical timestamps is one of the most common traps in helpdesk migrations — always verify in your specific eDesk instance before running the full migration.

Migration Architecture

Data Flow

Pylon REST API → Extract → Staging DB (PostgreSQL/SQLite) → Transform → eDesk REST API → Load
     ↓                              ↓                                        ↓
  10-60 req/min              Validation queries                        60 req/min
  (per endpoint)             ID mapping tables                      (account-wide)
                             Error logs

eDesk API Authentication

eDesk uses token-based authentication. To obtain your API token:

  1. Log into eDesk with an admin account
  2. Navigate to Settings → Integrations → API (available on Enterprise plan only)
  3. Generate a new API token — store it securely (it is shown only once)
  4. Include the token in all requests as: Authorization: Bearer {token}

eDesk does not use OAuth for API access. The token does not expire but can be revoked from the settings page. If your eDesk account is not on the Enterprise plan, you will not see the API section — contact eDesk sales to upgrade. (support.edesk.com)

eDesk Sandbox / Test Environment

eDesk does not offer a dedicated sandbox environment. For test migrations, two approaches work:

  1. Separate test account: Create a second eDesk account on the Enterprise plan specifically for migration testing. This incurs additional cost but provides full isolation.
  2. Tag-and-delete approach: Run test imports into your production account with a migration-test tag, validate, then bulk-delete tagged tickets via the API before running the real migration.

Either way, run at least two full test migrations before touching production data.

Pylon API: Extraction Details

  • Base URL: https://api.usepylon.com
  • Auth: Bearer token (Admin users only) (docs.usepylon.com)
  • Issues search: POST /issues/search — 10 req/min, 30-day window max per query. For multi-year histories, iterate in 30-day windows.
  • Issue messages: GET /issues/{id}/messages — 20 req/min
  • Contacts: GET /contacts or POST /contacts/search — 60 req/min
  • Accounts: GET /accounts — 60 req/min, cursor-paginated with limits up to 1,000
  • Custom Fields: GET /custom-fields — per object type (account, issue, contact)
  • Tags: GET /tags
  • Pagination: Cursor-based on all endpoints

eDesk API: Loading Details

  • Base URL: https://api.edesk.com/v1
  • Auth: Bearer token (see authentication section above)
  • API access: Requires the Enterprise plan (support.edesk.com)
  • Create Ticket: POST /v1/tickets — requires subject, channel_id, status
  • Create Message: POST /v1/messages — creates message in existing ticket
  • Rate limit: 60 requests per minute account-wide. After hitting the cap, the rate restores at 2 requests per second. eDesk support can raise this limit on request for migration scenarios — contact them before starting a large migration.
  • Pagination: page and itemsPerPage parameters on list endpoints
  • 429 handling: Back off and retry after receiving a 429 status. The Retry-After header, if present, specifies wait time in seconds.
  • Webhooks: eDesk supports webhooks for ticket and message events (POST /v1/webhooks). Consider configuring a webhook listener during migration to monitor for unexpected ticket state changes.
Danger

Rate limit math matters. If each Pylon Issue has an average of 5 messages, migrating 1,000 issues requires:

  • 1,000 ticket creation calls
  • 5,000 message creation calls
  • ~1,000 customer creation/lookup calls
  • Total: ~7,000 API calls to eDesk

At 60 req/min, that is ~117 minutes of continuous API traffic — assuming zero errors or retries. In practice, plan for 2–3x actual wall time due to retries, validation lookups, and error handling.

Scaling estimates:

Issues Avg msgs/issue Total API calls Minimum time Realistic time
1,000 5 ~7,000 2 hrs 4–6 hrs
5,000 5 ~35,000 10 hrs 20–30 hrs
10,000 5 ~70,000 19 hrs 38–60 hrs
10,000 5 (elevated limit) ~70,000 5–8 hrs 8–15 hrs

Request a rate limit increase from eDesk support before any migration above 5,000 issues.

Step-by-Step Migration Process

Step 1: Extract Data from Pylon

import requests
import time
import json
from datetime import datetime, timedelta
 
PYLON_TOKEN = "your_pylon_bearer_token"
BASE_URL = "https://api.usepylon.com"
HEADERS = {
    "Authorization": f"Bearer {PYLON_TOKEN}",
    "Content-Type": "application/json"
}
 
def extract_issues(start_date, end_date):
    """Extract issues in 30-day windows (Pylon API constraint).
    
    Args:
        start_date: ISO 8601 date string (e.g., "2024-01-01")
        end_date: ISO 8601 date string (e.g., "2024-01-31")
    
    Note: Pylon enforces a max 30-day window per query.
    For larger ranges, iterate in 30-day chunks.
    """
    issues = []
    cursor = None
    while True:
        payload = {
            "filter": {
                "field": "updated_at",
                "operator": "between",
                "value": f"{start_date},{end_date}"
            },
            "limit": 100
        }
        if cursor:
            payload["cursor"] = cursor
        resp = requests.post(f"{BASE_URL}/issues/search",
                             headers=HEADERS, json=payload)
        if resp.status_code == 429:
            time.sleep(6)  # 10 req/min = 1 per 6 seconds
            continue
        data = resp.json()
        issues.extend(data.get("data", []))
        cursor = data.get("cursor")
        if not cursor:
            break
        time.sleep(6)  # Respect rate limit
    return issues
 
def extract_issues_full_range(start_date, end_date):
    """Extract issues across an arbitrary date range using 30-day windows."""
    all_issues = []
    current = datetime.fromisoformat(start_date)
    end = datetime.fromisoformat(end_date)
    while current < end:
        window_end = min(current + timedelta(days=30), end)
        issues = extract_issues(current.isoformat(), window_end.isoformat())
        all_issues.extend(issues)
        current = window_end
    return all_issues
 
def extract_messages(issue_id):
    """Extract all messages for a given issue."""
    resp = requests.get(f"{BASE_URL}/issues/{issue_id}/messages",
                        headers=HEADERS)
    if resp.status_code == 429:
        time.sleep(3)  # 20 req/min = 1 per 3 seconds
        return extract_messages(issue_id)  # Retry
    return resp.json().get("data", [])

Step 2: Download Attachments

For every message containing a file, download it to temporary storage (AWS S3 bucket or local volume). Pylon attachment URLs may require authentication — ensure your extraction script passes the correct bearer token when fetching files.

import os
 
def download_attachment(url, dest_path):
    """Download a Pylon attachment with auth.
    Pre-scan: skip files > 10MB (eDesk limit).
    """
    # HEAD request to check size before downloading
    head = requests.head(url, headers=HEADERS)
    content_length = int(head.headers.get('content-length', 0))
    if content_length > 10 * 1024 * 1024:  # 10MB
        log_oversized_attachment(url, content_length)
        return None  # Skip or compress
    
    resp = requests.get(url, headers=HEADERS, stream=True)
    if resp.status_code == 200:
        os.makedirs(os.path.dirname(dest_path), exist_ok=True)
        with open(dest_path, 'wb') as f:
            for chunk in resp.iter_content(chunk_size=8192):
                f.write(chunk)
    return resp.status_code

Step 3: Transform Data

def transform_issue_to_edesk_ticket(pylon_issue, field_map, tag_map,
                                     user_map, account_lookup):
    """Transform a Pylon Issue into an eDesk Ticket payload."""
    status_map = {
        "new": "open",
        "waiting_on_you": "open",
        "waiting_on_customer": "pending",
        "on_hold": "pending",  # Verify in sandbox — eDesk may not have a hold state
        "closed": "resolved"
    }
    ticket = {
        "subject": pylon_issue.get("title") or "Imported Thread",
        "channel_id": EDESK_CHANNEL_ID,  # Pre-configured email channel
        "status": status_map.get(pylon_issue.get("status"), "open"),
        "owner_user_id": user_map.get(pylon_issue.get("assignee_id")),
        "created_at": pylon_issue.get("created_at"),
        "custom_fields": [
            {"id": field_map["pylon_source_id"],
             "value": pylon_issue["id"]}  # Cross-reference for delta syncs
        ],
        "tags_ids": [tag_map[t] for t in pylon_issue.get("tags", [])
                     if t in tag_map]
    }
    # Flatten Account name into custom field
    if pylon_issue.get("account_id"):
        account_name = account_lookup.get(pylon_issue["account_id"], "")
        ticket["custom_fields"].append({
            "id": field_map["account_name"],
            "value": account_name
        })
    # Map priority to custom field (eDesk has no native priority)
    if pylon_issue.get("priority"):
        ticket["custom_fields"].append({
            "id": field_map["priority"],
            "value": pylon_issue["priority"]
        })
    return ticket
 
def convert_slack_markdown_to_html(text):
    """Convert Slack-specific markdown to standard HTML.
    
    Handles: bold (*text*), italic (_text_), code (`text`),
    user mentions (<@U123456>), channel refs (<#C123456|channel>).
    """
    import re
    # Strip user mentions — replace with readable placeholder
    text = re.sub(r'<@(U[A-Z0-9]+)>', r'[user:\1]', text)
    # Strip channel references
    text = re.sub(r'<#(C[A-Z0-9]+)\|([^>]+)>', r'#\2', text)
    # Bold
    text = re.sub(r'\*([^*]+)\*', r'<strong>\1</strong>', text)
    # Italic
    text = re.sub(r'_([^_]+)_', r'<em>\1</em>', text)
    # Code
    text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
    # Line breaks
    text = text.replace('\n', '<br/>')
    return text

Key transformation tasks:

  • Convert Slack markdown to standard HTML — eDesk's UI renders HTML. Use the conversion function above as a starting point.
  • Strip Slack-specific mention tags (e.g., <@U123456>) — otherwise eDesk tickets will be littered with unreadable user IDs. Replace with [user:U123456] or resolve to display names via Slack's API.
  • Map Pylon User IDs to eDesk Agent IDs using a pre-built lookup dictionary (match by email address).
  • Flatten Account-level data into ticket custom fields.
  • Validate attachment sizes against eDesk's 10MB limit before loading.
  • Handle missing email addresses for Slack-only contacts (see Edge Cases section).

Step 4: Load into eDesk

EDESK_TOKEN = "your_edesk_bearer_token"
EDESK_URL = "https://api.edesk.com/v1"
EDESK_HEADERS = {
    "Authorization": f"Bearer {EDESK_TOKEN}",
    "Content-Type": "application/json"
}
 
def create_edesk_ticket(ticket_payload):
    """Create a ticket in eDesk with rate-limit handling."""
    resp = requests.post(f"{EDESK_URL}/tickets",
                         headers=EDESK_HEADERS, json=ticket_payload)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get('Retry-After', 1))
        time.sleep(retry_after)
        return create_edesk_ticket(ticket_payload)
    if resp.status_code not in (200, 201):
        log_error(ticket_payload, resp.status_code, resp.text)
        return None
    return resp.json()
 
def create_edesk_message(ticket_id, message_payload):
    """Add a message to an existing eDesk ticket."""
    payload = {
        "ticket_id": ticket_id,
        "body": message_payload["body"],
        "type": "Note" if message_payload.get("is_internal") else "Message",
        "created_at": message_payload.get("created_at")
    }
    if not message_payload.get("is_internal"):
        payload["direction"] = ("Incoming" if message_payload.get("from_customer")
                                else "Outgoing")
    resp = requests.post(f"{EDESK_URL}/messages",
                         headers=EDESK_HEADERS, json=payload)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get('Retry-After', 1))
        time.sleep(retry_after)
        return create_edesk_message(ticket_id, message_payload)
    return resp.json()

Load order matters. Create records in this sequence:

  1. Contacts / Customers — match by email address. Use eDesk's customer search endpoint to check for existing records before creating duplicates.
  2. Tags — pre-create all tags and tag groups. Store the name→ID mapping.
  3. Custom Fields — create all custom field definitions in eDesk before importing tickets.
  4. Sales Orders — if marketplace or order context is needed.
  5. Tickets — with created_at preserved and pylon_id cross-reference stored.
  6. Messages and Notes — replaying the thread chronologically (sort by created_at ascending).
  7. Attachments — upload to each message after creation.

Store every source-to-target ID mapping in your staging database. You will need it for retries, attachment backfills, UAT fixes, and rollback.

Step 5: Handle Errors Like an Integration

Build exponential backoff into your pipeline — do not rely on simple fixed-sleep timers:

def api_call_with_retry(func, max_retries=5, description=""):
    """Execute an API call with exponential backoff and jitter."""
    import random
    for attempt in range(max_retries):
        try:
            result = func()
            if result.status_code == 429:
                wait = (2 ** attempt) + random.uniform(0, 1)
                log_retry(description, attempt, wait)
                time.sleep(wait)
                continue
            if result.status_code >= 500:
                # Server error — retry with backoff
                wait = (2 ** attempt) + random.uniform(0, 1)
                time.sleep(wait)
                continue
            return result
        except requests.exceptions.ConnectionError:
            time.sleep(2 ** attempt)
    raise Exception(f"Max retries exceeded for: {description}")

Send validation errors (eDesk codes like 4001, 4002, 4014 Mismatch between sales order and channel, 4018, 4019) to a dead-letter queue rather than failing the entire batch. Log both platforms' IDs plus Pylon's request_id for support escalation. (developers.edesk.com)

Common eDesk error codes during migration:

Error Code Meaning Resolution
4001 Validation error Check required fields (subject, channel_id, status)
4002 Invalid field value Verify custom field type compatibility
4014 Sales order/channel mismatch Ensure channel_id matches the order's marketplace
4018 Duplicate record Check if ticket already exists (use pylon_id lookup)
4019 Rate limit exceeded Implement backoff; request limit increase

Step 6: Validate

Run validation queries against both systems (see Validation section below).

Edge Cases & Challenges

Duplicate Customer Records

Pylon Contacts can share email addresses across Accounts (e.g., a consultant working with multiple clients). eDesk deduplicates customers by email. This means two separate Pylon Contacts become one eDesk Customer — and their ticket histories merge. Decide in advance whether this is acceptable and document the expected contact count delta.

eDesk's merge capability has its own limits: merge is not available for Amazon/eBay tickets, tickets with orders attached, tickets with more than 10 messages, or tickets that were previously merged. (support.edesk.com)

Slack Thread Context and Formatting

Pylon Issues often contain rich Slack thread context — reactions, thread replies, <@U123456> user mentions, emoji reactions. None of this maps to eDesk's message model. Your transformation layer must:

  • Strip or convert <@U123456> mention tags to readable text
  • Convert Slack's *bold* and _italic_ to HTML <strong> and <em>
  • Remove Slack-specific emoji reactions (:thumbsup:) or convert to text equivalents
  • Flatten threaded replies into chronological order (eDesk displays messages linearly)

Missing Email Addresses

Pylon Contacts created purely via Slack might lack standard email addresses. eDesk requires an email address for Customer records. Options:

  1. Source from CRM: Query your CRM (HubSpot, Salesforce) by Slack user ID or name to retrieve the associated email.
  2. Source from Slack API: Use users.lookupByEmail or users.info to retrieve email addresses from Slack directly.
  3. Generate placeholder emails: Create addresses like slack-user-{slack_id}@yourdomain.com — but flag these for manual cleanup post-migration.

Custom Objects (Pylon Enterprise)

Pylon's Custom Objects (subscriptions, assets, implementation projects) have no equivalent in eDesk. Options ranked by data preservation:

  1. Flatten into ticket-level custom fields — works for simple key-value data but loses relational structure.
  2. Store as structured JSON in internal notes — preserves data but is not queryable in eDesk.
  3. Export to a separate system (CRM, spreadsheet, data warehouse) — best for relational data you need to query.

Attachments

eDesk limits ticket attachments to 10MB per file. Pylon Issues with large file attachments (design files, logs, screenshots) may exceed this. Pre-scan attachments during extraction:

  • Files under 10MB: upload directly
  • Files 10–25MB: attempt compression (images, PDFs)
  • Files over 25MB: host externally (S3, Google Drive) and link in the message body
  • Document all excluded attachments for stakeholder review

Single-Owner Model

eDesk tickets have one assigned owner at a time. Pylon's team assignment, followers, and shared Slack collaboration must collapse into one owner. Strategy:

  • Map the Pylon assignee_id to the eDesk owner_user_id
  • Store Pylon followers as a comma-separated list in a custom field or internal note
  • If Pylon has a team assignment but no individual assignee, assign to a team lead or leave unassigned (support.edesk.com)

Knowledge Base

Pylon has a native Knowledge Base with articles and collections. eDesk has no built-in KB. Migration options:

Target Effort Maintains structure
Zendesk Guide Medium ✅ Categories + articles
Notion Low ⚠️ Manual restructuring
Confluence Medium ✅ Spaces + pages
Document360 Medium ✅ Categories + articles
Static site (Docusaurus) High ✅ Full control

Branched Thread Display

eDesk displays messages linearly. Complex, branched Slack threads from Pylon will be flattened into a single chronological timeline. To preserve some context, prepend a [Thread reply to: {parent_message_timestamp}] prefix to messages that were originally thread replies.

Limitations & Constraints

Be explicit with stakeholders about what eDesk cannot replicate from Pylon:

Capability Pylon eDesk
Custom Objects ✅ (Enterprise) ❌ Not supported
Account hierarchy ✅ Partner accounts, sub-accounts ❌ Flat customer model
Knowledge Base ✅ Native ❌ No native KB
Slack-native support ✅ Core architecture ❌ Not supported
Feature request tracking ✅ Native ❌ Not supported
Native priority field ❌ Use tags or custom fields
AI account intelligence ✅ Health scores, summaries ❌ Not supported
Marketplace integrations ❌ None ✅ 250+ native
Order-linked tickets ❌ No order model ✅ Native sales order object
API rate limits 10–60 req/min per endpoint 60 req/min account-wide (increasable)
Bulk import API ❌ None ❌ None
Warning

Confirmed data loss scenarios. The following Pylon data types will not survive the migration to eDesk in their original form. Document these in your migration plan and get stakeholder sign-off before proceeding:

  • Account-level metadata (health scores, CRM sync fields, partner/sub-account relationships)
  • Custom Objects (subscriptions, assets, implementation records)
  • KB articles and collections
  • Feature Requests
  • Survey/CSAT historical data
  • AI-generated account intelligence scores and summaries
  • Slack thread formatting, reactions, and branched conversation structure
  • Notebooks and highlights
  • Follower/watcher lists (collapsed to single owner)

Validation & Testing

Do not skip User Acceptance Testing.

Record Count Comparison

Object Pylon Count eDesk Count Expected Delta Action
Issues → Tickets X Y 0 (unless filtered) Investigate any gap
Contacts → Customers X Y Lower (dedup by email) Document expected delta
Messages X Y 0 (1:1 match) Re-import missing
Tags X Y 0 Create missing
Custom Fields X Y May differ (type mismatches) Manual review
Attachments X Y Lower (10MB limit exclusions) Document excluded files

Field-Level Validation

Sample 50–100 tickets across different date ranges and statuses:

  • Verify subject lines match original Pylon issue titles
  • Verify message count per ticket matches Pylon issue message count
  • Verify custom field values transferred correctly (especially Select/Enum fields)
  • Verify tag assignments (compare by name)
  • Verify assignee mapping (correct agent owns the ticket)
  • Check timestamps — did created_at survive or get overwritten?
  • Confirm attachments open correctly and are not corrupted
  • Verify the original requester is correctly associated
  • Check that Slack markdown was properly converted to HTML

Automated Validation Script

def validate_migration(staging_db, edesk_api):
    """Run automated validation checks post-migration."""
    errors = []
    
    # Check ticket count
    pylon_count = staging_db.query("SELECT COUNT(*) FROM pylon_issues")
    edesk_count = edesk_api.get_ticket_count()
    if pylon_count != edesk_count:
        errors.append(f"Ticket count mismatch: Pylon={pylon_count}, eDesk={edesk_count}")
    
    # Sample 100 tickets and verify field mapping
    sample = staging_db.query("SELECT * FROM pylon_issues ORDER BY RANDOM() LIMIT 100")
    for issue in sample:
        edesk_ticket = edesk_api.get_ticket_by_custom_field("pylon_id", issue["id"])
        if not edesk_ticket:
            errors.append(f"Missing ticket for Pylon issue {issue['id']}")
            continue
        if edesk_ticket["subject"] != (issue["title"] or "Imported Thread"):
            errors.append(f"Subject mismatch for {issue['id']}")
        # Verify message count
        pylon_msg_count = staging_db.query(
            "SELECT COUNT(*) FROM pylon_messages WHERE issue_id = ?", issue["id"])
        edesk_msg_count = len(edesk_api.get_messages(edesk_ticket["id"]))
        if pylon_msg_count != edesk_msg_count:
            errors.append(f"Message count mismatch for {issue['id']}: "
                         f"Pylon={pylon_msg_count}, eDesk={edesk_msg_count}")
    
    return errors

UAT Process

  1. Migrate a test batch of 100 tickets spanning different issue types and statuses
  2. Have 2–3 agents review their assigned tickets in eDesk — confirm they can read message history
  3. Confirm search and filter functionality works on migrated data (search by subject, customer email, tag)
  4. Validate that eDesk rules and automations fire correctly on migrated tickets
  5. Check Customer View — do merged contacts display correct history?
  6. Verify that custom field filters work in eDesk's ticket list views

Rollback Planning

  • Before migration: Perform your initial migration into an eDesk test account or use the tag-and-delete approach
  • Tag migrated records: Add a pylon-migrated tag to all imported tickets for easy identification and bulk operations
  • If mapping is flawed: Delete all tagged tickets and adjust your transformation logic before retrying

Rollback script:

def rollback_migration(edesk_api, migration_tag="pylon-migrated"):
    """Delete all tickets tagged with the migration marker."""
    page = 1
    deleted = 0
    while True:
        tickets = edesk_api.get_tickets(tag=migration_tag, page=page)
        if not tickets:
            break
        for ticket in tickets:
            resp = requests.delete(
                f"{EDESK_URL}/tickets/{ticket['id']}",
                headers=EDESK_HEADERS
            )
            if resp.status_code in (200, 204):
                deleted += 1
            else:
                log_error(f"Failed to delete ticket {ticket['id']}: {resp.text}")
            time.sleep(1)  # Respect rate limit
    return deleted
  • Post-rollback: Verify ticket count is zero for the migration tag, then re-run the corrected pipeline

Post-Migration Tasks

Rebuild Automations

Pylon Triggers do not export to eDesk. Manually recreate:

  • Auto-assignment rules and routing
  • SLA policies (configure marketplace-specific SLAs: 24hr for Amazon, 48hr for eBay)
  • Auto-response templates
  • Escalation workflows
  • Tag-based routing using eDesk's Message Rules engine
  • HandsFree automation rules for common ticket categories

Connect Marketplace Channels

Authenticate your Amazon, Shopify, eBay, or other marketplace channels directly in eDesk. Verify order and channel linkage — you can encounter 4014 Mismatch between sales order and channel errors if these are not configured correctly. (developers.edesk.com)

Configure Webhooks for Monitoring

Set up eDesk webhooks to monitor ticket events during the post-migration stabilization period:

# Register a webhook for ticket updates
webhook_payload = {
    "url": "https://your-monitoring-endpoint.com/edesk-events",
    "events": ["ticket.created", "ticket.updated", "message.created"]
}
requests.post(f"{EDESK_URL}/webhooks", headers=EDESK_HEADERS, json=webhook_payload)

Agent Training

eDesk's UI paradigm is fundamentally different from Pylon's. Key differences agents will notice:

  • No Slack channel integration — all support happens in eDesk's mailbox interface
  • Order data appears automatically for marketplace-linked tickets
  • Tag Groups replace Pylon's flat tag model — train agents on the new taxonomy
  • AI features (HandsFree, AI Composer) need configuration and testing
  • Single-owner ticket assignment replaces Pylon's collaborative Slack model — establish escalation procedures
  • Filters and views work differently — create saved views that replicate Pylon's workflow

Monitoring

For the first 2 weeks post-migration:

  • Monitor for duplicate customer records (check customer count daily)
  • Check for tickets with missing messages (run the validation script daily)
  • Watch for custom field values that did not map correctly
  • Verify SLA timers are calculating correctly on historical tickets
  • Monitor eDesk webhook events for unexpected ticket state changes
  • Track agent-reported issues in a dedicated Slack channel or spreadsheet

Best Practices

  1. Back up everything. Run a full JSON export of your Pylon workspace to S3 or a local volume before running any import scripts. Verify the backup is complete and readable.
  2. Test incrementally. Migrate 100 tickets, validate. Migrate 1,000, validate. Then run the full batch. Execute at least two full test runs against a test eDesk account before touching production.
  3. Request a rate limit increase. Contact eDesk support before starting any migration above 5,000 issues. Explain the migration scenario and request a temporary increase from 60 req/min to 120–300 req/min.
  4. Freeze configurations. Do not allow admins to add new custom fields, tags, or statuses in Pylon once the migration mapping is locked. Set a freeze date 48 hours before the first production run.
  5. Automate repetitive mapping. Build lookup dictionaries for user IDs, tag IDs, and custom field IDs in your staging database. Do not map manually per record.
  6. Log everything. Write every API request and response (success and failure) to structured logs (JSON format). Include timestamps, Pylon IDs, eDesk IDs, HTTP status codes, and response bodies. You will need this for debugging and post-migration audit.
  7. Communicate the cutover. Agents should know exactly when Pylon goes read-only and eDesk becomes the primary system. Send a timeline 1 week before, 24 hours before, and at cutover. Keep Pylon accessible in read-only mode for 30 days post-migration.
  8. Handle PII carefully. If you are subject to GDPR or CCPA, ensure your staging database and any intermediate files are encrypted at rest and deleted within 30 days of migration completion.

Sample Data Mapping Table

Pylon Field Pylon Type eDesk Field eDesk Type Notes
issue.id String Custom Field: pylon_id Text For cross-reference and delta syncs
issue.title String ticket.subject String Fallback to "Imported Thread" if null
issue.status Enum ticket.status Enum Map values (see field-level mapping above)
issue.assignee_id String ticket.owner_user_id Integer ID lookup required; match by email
issue.requester_id String ticket.contact_id Integer Match by email
issue.created_at ISO 8601 ticket.created_at ISO 8601 Verify preservation in test run
issue.tags [] Array ticket.tags_ids [] Array Name→ID lookup via pre-created tags
issue.custom_fields [] Mixed ticket.custom_fields [] Mixed Type-match required; verify enum values
issue.account_id String Custom Field: company Text Account name lookup from staging DB
issue.priority Enum Custom Field: priority Select No native priority in eDesk
message.body Slack MD/Text message.body HTML Strip Slack formatting, convert to HTML
message.attachments [] URLs message.attachments [] URLs 10MB limit on eDesk; pre-scan sizes
message.is_internal Boolean message.type Enum true → "Note", false → "Message"
message.sender_type String message.direction Enum Customer → "Incoming", Agent → "Outgoing"

What This Migration Actually Costs

A Pylon-to-eDesk migration is not just a data move — it is an architecture translation. You are shifting from a B2B, account-centric, Slack-native model to an eCommerce, order-centric, marketplace-native model. Some data will not survive this transition cleanly. The key is to be explicit about what transfers, what transforms, and what gets left behind.

Cost summary by migration size:

Migration Size DIY Engineering Hours DIY Cost (at $100/hr) Primary Risk
< 1,000 issues 30–50 hrs $3,000–$5,000 Low — script + weekend
1,000–5,000 issues 50–80 hrs $5,000–$8,000 Medium — rate limits, edge cases
5,000–10,000 issues 80–120 hrs $8,000–$12,000 High — need rate limit increase
10,000+ issues 100–150+ hrs $10,000–$15,000+ High — orchestration tooling required

These estimates include extraction scripting, transformation logic, rate-limit handling, staging infrastructure, two full test runs, validation, and post-migration debugging. They do not include agent training, automation rebuilding, or opportunity cost of diverted engineering capacity.

For teams with straightforward data (under 5,000 issues, few custom fields, no custom objects), a well-written script and a focused sprint can get this done. For anything more complex — custom objects requiring flattening, multi-year history, strict compliance requirements, or if your engineering bandwidth is committed elsewhere — a managed migration service typically reduces total cost and risk.

By treating this as a strict ETL project with a staging database, validation queries, and rollback scripts rather than a simple import, you protect your historical data, maintain attachment integrity, and ensure your support team has the context they need on day one.

Frequently Asked Questions

Can I migrate data from Pylon to eDesk directly?
No. There is no native import path between Pylon and eDesk. You need a custom API-based ETL pipeline, a third-party migration tool (if one supports Pylon as a source), or a managed migration service. Both platforms have REST APIs that can be used for extraction and loading, but no built-in migration bridge exists.
What data is lost when migrating from Pylon to eDesk?
eDesk cannot replicate Pylon's Custom Objects, Knowledge Base articles, Feature Requests, Account hierarchy (partner/sub-accounts), Slack thread formatting, historical CSAT/survey data, notebooks, highlights, or account-level custom fields. These must be exported separately, flattened into ticket custom fields, or documented as accepted losses.
How long does a Pylon to eDesk migration take?
A migration of 1,000 issues with an average of 5 messages each requires approximately 6,000 eDesk API calls. At eDesk's 60 requests per minute rate limit, that is about 100 minutes of continuous API traffic. In practice, expect 2–3x that due to retries, validation, and error handling. A full migration typically takes 5–10 business days including planning, test runs, and validation.
What are the API rate limits for Pylon and eDesk?
Pylon enforces per-endpoint limits: Issues search at 10 requests per minute, issue messages at 20 per minute, and contacts/accounts at 60 per minute. eDesk allows 60 requests per minute account-wide, with a restoration rate of 2 requests per second after hitting the cap. Neither platform offers a bulk import API. eDesk API access requires the Enterprise plan.
Does eDesk support custom objects or accounts like Pylon?
No. eDesk supports custom fields on tickets (Text, Date, Select, Number, Yes/No, URL, Email types) but has no custom object support and no company/organization object. Pylon's Custom Objects must be flattened into custom fields, stored as internal notes, or exported to a separate system. Account-level data must be handled similarly.

More from our Blog

Help Desk Data Migration Playbook: What Data to Move and What to Leave Behind
Help Desk

Help Desk Data Migration Playbook: What Data to Move and What to Leave Behind

This definitive playbook answers the single most critical question: "What data do we actually need to move?". This strategic guide helps you declutter and decide what's precious and what's junk. We provide a clear breakdown of the non-negotiable Tier 1 data, like tickets , knowledge bases , and user profiles, versus the Tier 2 data that provides rich context, like automations and organizations. Use this as your strategic checklist to avoid common mistakes and ensure a flawless, functional new help desk.

Raaj Raaj · · 7 min read
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