Skip to content

Enchant to Desk365 Migration: A Technical Guide

Technical guide for migrating from Enchant to Desk365. Covers API constraints, object mapping, label-to-category conversion, rate limits, and dependency order.

Raaj Raaj · · 20 min read
Enchant to Desk365 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

Enchant to Desk365 Migration: A Technical Guide

Moving from Enchant to Desk365 means transitioning from a lightweight shared inbox to a structured, Microsoft 365-integrated helpdesk with custom fields, SLAs, company management, and ticket types. The data models differ enough that a direct lift-and-shift won't work — you need a transformation layer between the two systems.

As noted in our FuseDesk to Enchant migration guide, Enchant is built around the shared inbox philosophy: minimal, fast, and reliant on labels and folders rather than strict relational data models. Desk365 enforces strict relationships between Agents, Contacts, Groups, and Tickets, with custom fields and ticket types driving routing, SLAs, and reporting. You're moving from a flat data structure to a highly relational one. A naive API script that loops through Enchant tickets and pushes them to Desk365 will lose conversation history, break agent attribution, and orphan attachments.

Desk365 supports both CSV import (via /api/v3/tickets/import) and API-based ticket creation. The CSV importer handles basic ticket properties and contact imports but does not reconstruct full conversation threads with replies, notes, and attachments. For any migration that needs to preserve history, the API is the only viable path.

This guide covers the object mapping, dependency order, API constraints on both sides, error handling patterns, and the failure modes documented in real migrations. API details reflect Enchant API v1 and Desk365 API v3; verify against official documentation before running production migrations.

Why Teams Move from Enchant to Desk365

The most common technical drivers:

  • Microsoft 365 integration. Desk365 supports native Teams ticketing, Entra ID (Azure AD) agent sync, and a Power Automate connector. Enchant has no Microsoft 365 integrations.
  • Custom fields and structured data. Enchant has no custom fields on tickets — only flat labels for categorization and a single free-text summary field on customers. Desk365 supports custom ticket fields (dropdowns, text, checkbox, date, number, nested dropdowns), custom ticket types, and categories/subcategories with up to three levels of hierarchy.
  • Company-level management. Enchant has no Companies entity — customers are standalone records. Desk365 has a full Companies object with associated contacts, company-specific SLA policies, and department-level management.
  • SLA enforcement. Enchant has basic SLA support through rules. Desk365 has native SLA policies with configurable first response and resolution targets, business hours configuration, per-company SLA overrides, and breach escalation rules.

Data Model Comparison: Enchant vs. Desk365

Understanding the structural gap is the most important pre-work. Every mapping decision flows from it.

Enchant Object Desk365 Equivalent Notes
Inbox Group Enchant routes tickets to inboxes; Desk365 routes to groups. 1:1 mapping works for most setups.
User (agent) Agent Agents must exist in Desk365 before tickets reference them.
Customer Contact Enchant customers carry first_name, last_name, summary, and contacts. Desk365 contacts carry name, email, phone, title, company association, and custom fields.
Contact (email/phone/twitter) Contact email / phone Enchant supports multiple contact types per customer. Twitter handles have no direct Desk365 equivalent.
(no equivalent) Company Desk365 has a dedicated Companies entity. You'll need to decide whether to create companies from email domains or skip this entirely.
Label Category / Subcategory / Type / Custom Field Enchant uses labels for everything. Desk365 has structured fields. A mapping table is required.
Ticket Ticket Core migration object. State and field mapping required.
Message (reply in/out, note) Conversation (contact reply, agent reply, private/public note) Structural transformation needed.
Attachment Attachment Must be downloaded from Enchant and re-uploaded to Desk365 via multipart form.

Ticket State Mapping

Enchant tickets have five possible states. Desk365 has four default statuses plus custom statuses:

Enchant State Desk365 Status Notes
open Open Direct map
hold Pending Closest equivalent — both mean "waiting on something"
closed Closed Direct map
snoozed Pending Desk365 has no native snooze. Map to Pending, or create a custom "Snoozed" status. The snoozed_until timestamp will be lost unless stored in a custom field.
archived Closed Archived in Enchant is a terminal state. Map to Closed.

Priority Mapping

Enchant does not have a priority field on tickets. Desk365 uses numeric priority values: 1 (Low), 5 (Medium), 10 (High), 20 (Urgent). You'll need to either set a default priority for all migrated tickets or derive priority from Enchant labels (e.g., if you used a "VIP" or "Urgent" label in Enchant, map those to 20 in Desk365 via your label mapping table).

Source Mapping

Enchant ticket types map to Desk365 source values:

Enchant Type Desk365 Source Value Notes
email 1 (Email) Direct map
chat 13 (Web Widget) Closest match
web 12 (Web Form) Direct map
phone, call 7 (Phone/Other) Direct map
sms, whatsapp, fb_messenger, instagram_dm, twitter, twitter_dm 7 (Phone/Other) No direct Desk365 source for social/messaging channels
Warning

Social channel data loss risk. Enchant supports tickets from Twitter, Facebook Messenger, Instagram DM, WhatsApp, and SMS. Desk365's source values don't include these channels natively. If preserving the original channel is important, store the Enchant type value in a Desk365 custom field (e.g., cf_original_channel) before migration.

Pre-Migration Preparation

You cannot migrate data into Desk365 if the structural foundation doesn't exist. The following must be configured before running any migration scripts.

Configure Groups and Agents

Desk365 groups need to mirror your Enchant Inboxes. If Desk365 groups are mapped to Microsoft Teams channels, configure that alignment before migration begins. All agents must be invited and active in Desk365 before ticket import starts — Desk365 rejects ticket creation if the assigned agent ID doesn't exist.

Warning

Handling inactive agents: If you have historical tickets assigned to agents who have since left the company, Desk365 will return a 422 Unprocessable Entity error if you attempt to assign the ticket to a non-existent agent. Either create a "Migration Archive" dummy agent in Desk365 to hold these tickets, or map all inactive agents to a specific active administrator in your agent mapping table.

Design Custom Fields, Types, and Categories

Review your Enchant labels. If you use labels like Bug, Feature Request, or Billing, don't dump these into Desk365 as plain tags. Create Ticket Types, Categories, Subcategories, or dropdown Custom Fields in Desk365. Your migration script needs transformation logic that reads the Enchant label array and maps it to the corresponding Desk365 field IDs.

Disable Notifications and Automations

Desk365 has triggers and automations that fire when tickets are created or updated. Disable all outbound notifications, SLA timers, and webhooks in Desk365 before beginning the import. To disable: navigate to Settings → Automations → Notification Rules and deactivate all rules. Also disable Settings → SLA Policies and any active Webhooks under Settings → Integrations. Failing to do this will trigger thousands of email notifications to customers, informing them that their three-year-old tickets have been "created."

Pre-Migration Checklist

  • Audit Enchant data: total tickets, customers, attachments, labels, and any deleted/merged ticket records
  • Confirm Desk365 plan tier — Standard (100 calls/hour) vs. Plus/Premium (50 calls/minute) — and upgrade if needed for your data volume
  • Create all Groups in Desk365 (mapped from Enchant inboxes)
  • Create all Agents in Desk365 with matching email addresses
  • Design and create custom fields, ticket types, categories, and subcategories
  • Build the label → category/type/custom field mapping table
  • Set up SLA policies in Desk365
  • Disable all outbound notifications, automations, SLA timers, and webhooks
  • Initialize a local tracking database (SQLite recommended) with schema: enchant_id, desk365_id, status, migrated_at
  • Run a test migration with 50–100 tickets to validate mapping logic
  • Verify conversation threading, attachments, and contact associations in test data

Migration Dependency Order

Dependencies between objects dictate the order you create them in Desk365. Get this wrong and your ticket imports will fail with missing reference errors.

  1. Agents (Manual / Azure AD sync)
  2. Companies (if applicable — create from email domains)
  3. Contacts (from Enchant Customers)
  4. Tickets
  5. Messages (Replies and Notes, in ascending created_at order)
  6. Attachments

Extracting Data from Enchant

Enchant's REST API (v1) is your only extraction path. There is no bulk export feature.

Authentication

Enchant uses bearer token authentication. Obtain an access token by installing the API app from your Enchant account settings.

curl -H 'Authorization: Bearer YOUR_TOKEN' \
  https://site.enchant.com/api/v1/tickets

Rate Limits

Enchant's API is rate limited to 100 credits per minute per account, across all endpoints, users, and tokens. There is also a burst limiter of 6 requests per second. Embedding related resources costs an additional credit per embed type — fetching a ticket with embedded messages costs 2 credits instead of 1.

Response headers provide real-time rate limit state:

Rate-Limit-Limit: 100
Rate-Limit-Remaining: 98
Rate-Limit-Used: 2
Rate-Limit-Reset: 20

For a 10,000-ticket migration, with each ticket requiring at minimum a list call plus an embed call for messages, plan for the extraction phase alone to take 4–8 hours under normal conditions.

Info

Rate limiting strategy: Implement exponential backoff when receiving 429 responses. Read the Retry-After header if provided; otherwise back off exponentially (2s, 4s, 8s, 16s) on successive 429s. Fixed sleep timers are fragile — rate limit reset windows vary based on account activity.

Enchant API Error Codes

HTTP Status Meaning Action
200 OK Success Continue
400 Bad Request Malformed request syntax Log and skip; review payload
401 Unauthorized Invalid or expired token Refresh token; abort migration
403 Forbidden Insufficient permissions Check API app scope
404 Not Found Resource deleted or merged Log as skipped; continue
429 Too Many Requests Rate limit exceeded Exponential backoff; retry
500 Internal Server Error Enchant-side error Retry up to 3 times; then log and skip

Extracting Customers

Paginate through all customers. Default page size is 10; max is 100. For accounts with more than 10,000 customers, use since_created_at cursor-style pagination instead of page offset to avoid skipping records:

# Page-based (works up to ~10,000 records)
GET /api/v1/customers?per_page=100&sort=created_at&page=1
 
# Cursor-based (use for large datasets)
GET /api/v1/customers?per_page=100&sort=created_at&since_created_at=2024-01-15T12:00:00Z

Each customer includes embedded contacts (email, twitter, phone). Store the full response — you'll need contact values to look up or create Desk365 contacts, and the mapping of Enchant customer_id → primary email is needed for ticket creation.

Extracting Tickets with Messages

Use the embed parameter to pull messages inline with each ticket:

GET /api/v1/tickets/TICKET_ID?embed=messages

For bulk extraction, first list all ticket IDs with minimal fields, then fetch each ticket individually with embed=messages. This two-pass approach allows checkpointing:

# Pass 1: Get all ticket IDs and metadata (2 credits per page of 100)
GET /api/v1/tickets?per_page=100&sort=created_at&since_created_at=2020-01-01T00:00:00Z
 
# Pass 2: Fetch full ticket with conversation (2 credits per ticket)
GET /api/v1/tickets/TICKET_ID?embed=messages

For accounts with more than 10,000 tickets, use since_created_at pagination. The page parameter stops working reliably beyond 10,000 records due to Enchant's cursor limitations.

Tip

Checkpoint your extraction. Record each successfully fetched ticket ID in your local tracking database immediately after download. If rate limits or network issues interrupt the run, resume from the last checkpoint rather than restarting from zero.

Handling Deleted and Merged Tickets

Enchant's API returns 404 for deleted or merged tickets. Your script must handle these gracefully: log the ticket ID as skipped with reason "deleted_or_merged", increment a skip counter, and continue. Do not treat 404 as a fatal error during bulk extraction.

Extracting Attachments

Attachments are nested inside message objects. Each attachment has an id, name, size, and type. Enchant stores attachments on AWS S3 and the API provides temporary signed URLs. These URLs expire — download attachments immediately after fetching the ticket, not in a separate deferred pass.

{
  "id": 12345,
  "name": "screenshot.png",
  "size": 204800,
  "type": "image/png",
  "url": "https://s3.amazonaws.com/enchant-attachments/..."
}

Store attachments locally with their original filenames and MIME types. The signed URL will be invalid by the time you attempt to re-upload to Desk365 if you defer this step.

Loading Data into Desk365

Desk365 offers two import paths: CSV import and the REST API v3.

CSV Import: Contacts and Companies Only

Desk365's CSV import works reliably for bulk-loading contacts and companies. Download the sample CSV template from the Desk365 portal, populate it, and upload via Settings → Import. For tickets, CSV import creates basic ticket records but does not support importing conversation threads, attachments, or private notes. Use CSV only if migrating ticket metadata without history.

API Import: Required for Full-Fidelity Migration

The Desk365 API v3 is the only path that supports tickets with full conversation history.

Base URL: https://<yoursubdomain>.desk365.io/apis/v3/

Authentication: API Key in the Authorization header.

curl -H 'Authorization: YOUR_API_KEY' \
  https://yoursubdomain.desk365.io/apis/v3/tickets

Desk365 API Rate Limits

This is where many migrations stall. Desk365's rate limits are plan-specific:

Plan Monthly Price Rate Limit
Standard $12/agent/month 100 API calls per hour
Plus $20/agent/month 50 API calls per minute
Premium $32/agent/month 50 API calls per minute
Danger

The Standard plan limit of 100 calls per hour is prohibitive for any meaningful migration. At that rate, loading 10,000 tickets would take over 100 hours of API time, excluding conversations and attachments. Upgrade to Plus or Premium before starting migration, and downgrade after completion. At 50 calls/minute (Plus/Premium), migrating 10,000 tickets with conversations still takes 12–20 hours.

Desk365 API Error Codes and Retry Behavior

HTTP Status Meaning Retryable? Action
200 OK / 201 Created Success Continue
400 Bad Request Invalid field value or missing required field No Log payload; fix mapping; skip
401 Unauthorized Invalid API key No Abort; verify key
404 Not Found Referenced resource (agent, group, contact) doesn't exist No Fix dependency; retry after creating missing resource
422 Unprocessable Entity Business logic violation (e.g., invalid agent assignment, duplicate contact email) No Log and handle per case
429 Too Many Requests Rate limit exceeded Yes Exponential backoff with jitter
500 / 503 Desk365-side error Yes Retry up to 3 times with backoff

A 422 response on ticket creation most commonly means: the assigned agent doesn't exist, the group name doesn't match exactly, or a required custom field is missing. Log the full response body — Desk365 returns a structured error object identifying which field failed.

Contact Deduplication in Desk365

Desk365 uses email address as the primary deduplication key for contacts. If two Enchant customers share the same email address (which can happen if agents manually created duplicate customer records), the second POST /contacts call will return a 422 with a duplicate email error rather than merging the records. Handle this by:

  1. Before migration, deduplicate Enchant customers by email address
  2. Decide which record is authoritative (typically the one with more tickets)
  3. Map all tickets from the duplicate customer to the primary customer's email

If Desk365 returns a 422 on contact creation due to duplicate email, treat it as a successful lookup — fetch the existing contact by email and use its ID for ticket association.

Migrating Contacts

Create Desk365 contacts from Enchant customers via the API or CSV import. The contact's email is the primary identifier in Desk365 — it links contacts to tickets.

Map Enchant fields as follows:

Enchant Customer Field Desk365 Contact Field Notes
first_name First name Direct map
last_name Last name Direct map
contacts [].value (type: email) Email Primary deduplication key
contacts [].value (type: phone) Phone Direct map
summary Notes or custom field No native Notes field; use a custom text field
contacts [].value (type: twitter) Custom field cf_twitter_handle No native Desk365 equivalent

Store the mapping of Enchant Customer ID → Desk365 Contact Email in your local tracking database. You'll need this for every ticket creation call.

If you want to group contacts under companies, extract unique email domains from Enchant customers and create Desk365 Company records first. The API endpoint for company creation is POST /apis/v3/companies.

Migrating Tickets

This is the most complex phase. You must fetch tickets from Enchant, transform the payload, and push them to Desk365 while preserving conversation history.

Core Ticket Field Mapping

Enchant Field Desk365 Field Transformation
id custom_fields.cf_enchant_ticket_number Store as string in a custom field for cross-reference
subject subject Direct string map
state status Map using state table above
customer_id contact_email Lookup from your customer→email mapping table
user_id agent_id Lookup from your agent mapping table
type source Map using source table above
labels custom_fields, category, type Apply label mapping table
created_at custom_fields.cf_original_created_at See timestamp note below

A complete migration payload example:

{
  "subject": "Original Enchant ticket subject",
  "contact_email": "customer@example.com",
  "description": "<p>Original first message body</p>",
  "status": "Closed",
  "priority": 5,
  "group": "Support",
  "type": "Question",
  "source": "1",
  "custom_fields": {
    "cf_enchant_ticket_number": "2209",
    "cf_original_channel": "email",
    "cf_original_created_at": "2021-03-14T09:22:11Z"
  }
}

Timestamp Preservation

Desk365's API v3 sets created_on to the time the API call is made, not the original timestamp from the source system. As of the time of writing, there is no API parameter to override created_on during ticket or conversation creation. To preserve original timestamps:

  1. Store created_at from Enchant in a custom date field (cf_original_created_at) on the ticket
  2. Prepend the original timestamp to each conversation entry body: [Original timestamp: 2021-03-14 09:22 UTC]
  3. For compliance or audit requirements, export Enchant ticket history to CSV as an archival record before account closure

This is not ideal, but it is the current constraint of the Desk365 API. Verify with Desk365 support before migration whether a created_on override parameter has been added to the API.

Preserving Conversation History

Desk365 expects the initial customer message as the ticket description. In Enchant, use the first inbound message (direction: in) from the messages array as the description during ticket creation.

For the rest of the thread, iterate chronologically through the remaining Enchant messages in ascending created_at order and POST each to the Desk365 conversations endpoint (POST /apis/v3/tickets/{ticket_id}/conversations).

Enchant Message Desk365 Conversation Type API Value
type: reply, direction: in Contact reply "contact"
type: reply, direction: out Agent reply "agent"
type: note Private note "note"
Danger

Private note mapping is critical. If you accidentally map Enchant internal notes (type: note) to agent replies instead of private notes, customers will see internal team discussions in their support portal. Test this mapping explicitly on your 50-ticket pilot before running the full migration.

HTML vs. Plain Text

Enchant messages have a htmlized boolean field. When htmlized: true, the body field contains HTML. Desk365 conversation entries support both HTML (body) and plain text (body_text). Always check the htmlized flag:

if message["htmlized"]:
    payload["body"] = message["body"]
else:
    payload["body_text"] = message["body"]

Passing HTML content to body_text will expose raw markup to customers. Passing plain text to body will double-encode characters.

Idempotency and Crash Recovery

Neither Enchant nor Desk365 provides idempotent ticket creation — there is no idempotency_key parameter. If your script crashes mid-migration, a naive retry will create duplicate tickets. Prevent this with a local tracking database:

CREATE TABLE migration_log (
  enchant_ticket_id INTEGER PRIMARY KEY,
  desk365_ticket_id INTEGER,
  status TEXT,  -- 'pending', 'created', 'conversations_done', 'complete', 'failed'
  error_message TEXT,
  migrated_at TIMESTAMP
);

Before creating each ticket in Desk365, check whether enchant_ticket_id already exists in migration_log with status = 'created'. If it does, skip ticket creation and proceed to conversation migration using the stored desk365_ticket_id. This makes every phase of the migration resumable from the last checkpoint.

Attaching Files to Desk365: Code Example

Attachments require a separate multipart/form-data upload to Desk365 after the conversation entry is created. Here is a working example in Python:

import requests
 
def upload_attachment_to_desk365(ticket_id, conversation_id, file_path, 
                                   file_name, mime_type, api_key, subdomain):
    url = f"https://{subdomain}.desk365.io/apis/v3/tickets/{ticket_id}/conversations/{conversation_id}/attachments"
    
    with open(file_path, 'rb') as f:
        files = {'file': (file_name, f, mime_type)}
        headers = {'Authorization': api_key}
        response = requests.post(url, headers=headers, files=files)
    
    if response.status_code == 201:
        return response.json()
    elif response.status_code == 413:
        # File too large — log and append note to conversation
        raise FileTooLargeError(f"{file_name} exceeds Desk365 size limit")
    else:
        response.raise_for_status()
Danger

File size limits. Desk365 enforces attachment size limits (typically 15–25MB depending on plan; verify in your account settings). The API returns 413 Request Entity Too Large for oversized files. Your script must check attachment ["size"] before downloading from Enchant. If the file exceeds the limit, skip the upload and append a text note to the message body: [Attachment stripped during migration: filename.ext (32.4MB) — exceeded size limit].

Label-to-Category Mapping Strategy

This is the most design-intensive part of the migration. Enchant uses flat labels for everything — categorization, status tracking, priority, routing, and custom tags. Desk365 has structured fields: Category, Subcategory, Type, Priority, and custom fields.

Mapping Process

  1. Export all unique labels from your Enchant ticket history using GET /api/v1/labels
  2. Classify each label into one of: Category, Subcategory, Type, Priority, Custom Field, or Discard
  3. Create the corresponding structures in Desk365 before running migration
  4. Encode the mapping in a configuration file your migration script reads at runtime

Example mapping table:

Enchant Label Desk365 Field Desk365 Value Desk365 API Field
Billing Category Billing category
Refund Subcategory Refund sub_category
Bug Type Problem type
VIP Custom field VIP custom_fields.cf_tier
Urgent Priority 20 priority
Internal Discard

Tickets with multiple labels may map to multiple Desk365 fields simultaneously — that is expected. A ticket with labels ["Bug", "Billing", "VIP"] would set type: "Problem", category: "Billing", and custom_fields.cf_tier: "VIP" in a single ticket creation payload.

Handling Inline Images

Inline images (images embedded directly in email bodies) are stored as HTML <img> tags pointing to Enchant-hosted URLs. When you close your Enchant account, those URLs return 404, leaving broken images throughout your Desk365 ticket history.

To prevent this, your script must rewrite inline image URLs:

  1. Parse the HTML body of every message using an HTML parser (BeautifulSoup in Python, Cheerio in Node.js)
  2. Find all <img src="..."> tags where src points to an Enchant or S3 domain
  3. Download the image from the signed URL (immediately — it may expire)
  4. Upload the image as an attachment to the Desk365 conversation entry
  5. Replace the original src value in the HTML with the new Desk365 attachment URL
from bs4 import BeautifulSoup
import re
 
ENCHANT_IMG_PATTERN = re.compile(r'enchant\.com|s3\.amazonaws\.com/enchant')
 
def rewrite_inline_images(html_body, ticket_id, conversation_id, api_key, subdomain):
    soup = BeautifulSoup(html_body, 'html.parser')
    for img in soup.find_all('img'):
        src = img.get('src', '')
        if ENCHANT_IMG_PATTERN.search(src):
            # Download from Enchant, re-upload to Desk365, update src
            image_data = download_from_url(src)
            new_url = upload_inline_image(image_data, ticket_id, 
                                          conversation_id, api_key, subdomain)
            img['src'] = new_url
    return str(soup)

This step adds significant processing time — budget an additional 30–50% on top of your base migration timeline for accounts with high image volume.

Executing the Delta Migration

Migrations are rarely one-time script executions. Because your team needs to continue working during transition, execute in three phases.

Phase 1: Historical Sync

Migrate all closed tickets older than a defined freeze date (e.g., everything closed more than 30 days ago). This moves the bulk of data without affecting daily operations. Depending on Desk365 API rate limits, this phase can take several days.

Phase 2: Active Sync

Migrate remaining open tickets and recent history. Your team continues working in Enchant throughout this phase. New tickets created in Enchant during Phase 2 will be picked up in the delta sync.

Phase 3: Cutover and Delta Sync

On cutover day:

  1. Update DNS MX records and email forwarding rules to route incoming email to Desk365
  2. Instruct agents to stop working in Enchant and switch to Desk365
  3. Run a final delta sync — query Enchant for any tickets or messages with updated_at or created_at after your Phase 2 snapshot timestamp, and push them to the corresponding Desk365 tickets (using your migration_log table to match Enchant IDs to Desk365 IDs)

This approach guarantees no data loss during the DNS propagation window (typically 24–48 hours for TTL expiry).

Validating the Migration

Never assume a 201 Created response means the data is correctly structured in Desk365.

Count validation:

  • Total non-spam, non-trash Enchant tickets = total Desk365 tickets
  • Count by status: Enchant closed count should match Desk365 Closed count
  • Contacts: Enchant customer count ≥ Desk365 contact count (may be lower if deduplication removed duplicates)

Content validation:

  • Spot-check 20–30 tickets across open, closed, and pending states
  • Verify conversation order is chronological
  • Confirm private notes are not visible in the customer portal
  • Test that attachments are downloadable
  • Render complex HTML tickets and verify tables, bullet points, and inline images display correctly

Systematic issues: If you find a mapping error that affected more than a handful of tickets (e.g., all notes were created as agent replies), it is faster to wipe the Desk365 environment and rerun the corrected script than to patch data via the API. This is why the test pilot on 50–100 tickets is mandatory, not optional.

Edge Cases and Failure Modes

Multi-contact customers. Enchant customers can have multiple email addresses. Desk365 contacts support multiple emails, but ticket creation requires a single contact_email as the primary key. Decide which email is primary before migration — typically the first email contact in the Enchant contacts array, or the one with the most tickets.

Spam and trash tickets. Enchant tickets have spam and trash boolean flags. These are filtered from the default ticket list API response but can be fetched explicitly. Most teams skip migrating these. Document your decision either way.

Social handles as contacts. Enchant contact type twitter has no Desk365 equivalent on the contact record. Store in a custom contact field (cf_twitter_handle) or accept the data loss. Log the count of Twitter contacts for your migration report.

Tickets exceeding Enchant pagination limits. The Enchant page parameter stops reliably paginating beyond 10,000 records. Switch to since_created_at cursor pagination before you hit this boundary. Set sort=created_at&since_created_at=<last_seen_timestamp> and advance the cursor with each page response.

Desk365 bulk creation endpoint. Desk365 API v3 does not expose a batch ticket creation endpoint — all ticket creation is serial, one ticket per API call. Plan your rate limit math accordingly: at 50 calls/minute on Plus/Premium, you have a ceiling of 3,000 tickets/hour before accounting for conversation and attachment calls.

Enchant canned responses. These are not accessible via the Enchant API and cannot be migrated programmatically. Export manually from the Enchant UI and recreate in Desk365. Use the migration as an opportunity to audit and remove outdated templates.

Realistic Timeline Estimates

Data Volume Extraction (Enchant) Transformation Loading (Desk365 Plus/Premium) Total
1,000 tickets 1–2 hours 1–2 hours 2–4 hours 1 day
10,000 tickets 8–12 hours 4–6 hours 12–20 hours 2–3 days
50,000 tickets 2–3 days 1 day 3–5 days 1–2 weeks

These estimates assume Plus or Premium plan (50 calls/minute) and include conversation and attachment loading. On the Standard plan (100 calls/hour), multiply the Desk365 loading time by approximately 30×. Accounts with high attachment volume or heavy inline image usage add 30–50% to loading time.

What Not to Migrate

Just as we advise when migrating from Enchant to Intercom, some objects are better rebuilt from scratch in Desk365:

  • Canned responses. Not accessible via Enchant's API. Recreate manually in Desk365 under Settings → Canned Responses. Use this as an opportunity to retire outdated templates.
  • Rules and automations. Enchant's rule engine and Desk365's automation engine have different trigger models and condition structures. Rebuild from scratch based on current workflow requirements.
  • Satisfaction ratings. Enchant and Desk365 use different CSAT survey frameworks — ratings are not structurally portable. Export historical satisfaction data from Enchant to CSV for archival before account closure.
  • Knowledge base articles. If you use Enchant's knowledge base, migrate these separately. The Desk365 API v3 exposes endpoints for KB categories (/kb/categories), folders (/kb/folders), and articles (/kb/articles) — these can be scripted, but are outside the scope of the ticket migration.

For further reading on related migrations, see our guides on Enchant to Deskpro and how to export data from Desk365.

Frequently Asked Questions

Can I migrate Enchant tickets to Desk365 using CSV import?
Desk365's CSV import handles basic ticket properties and contacts, but it cannot import full conversation threads, notes, or attachments. For a complete migration with history preserved, you need the Desk365 API (v3).
What are the Desk365 API rate limits for migration?
Desk365 rate limits are plan-specific: Standard plan allows 100 API calls per hour, Plus and Premium plans allow 50 API calls per minute. The Standard plan limit is too restrictive for most migrations — upgrade to Plus or Premium during the migration window.
How do Enchant labels map to Desk365 fields?
Enchant uses flat labels for all categorization. In Desk365, you map labels to structured fields: Category, Subcategory, Type, Priority, or custom fields. Build a mapping table before migration and create the corresponding Desk365 structures first.
What happens to tickets assigned to agents who no longer work here?
Desk365 requires a valid agent ID for assignment. You must either create a dummy 'Migration Archive' agent profile in Desk365 to hold these historical tickets or map them to an active administrator's account.
Does Desk365 preserve original ticket timestamps during migration?
By default, the Desk365 API sets created_on to the time of the API call, not the original Enchant timestamp. Check with Desk365 support about overriding timestamps, or store the original timestamp in a custom field or message body.

More from our Blog