Skip to content

Desk365 to Enchant Migration: The Complete Technical Guide

Technical guide to migrating from Desk365 to Enchant via API. Covers object mapping, custom field handling, rate limits, attachments, and common failure modes.

Abdul Abdul · · 25 min read
Desk365 to Enchant Migration: The Complete 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

Desk365 to Enchant Migration: The Complete Technical Guide

Moving from Desk365 to Enchant is not a lift-and-shift. You are moving from a structured, Microsoft 365-integrated helpdesk — with custom fields, SLAs, companies, and ticket types — into a lightweight shared inbox built around simplicity and speed. The direction of this move, from a highly relational data model to a deliberately flat one, creates specific challenges around data loss, field compression, and identity mapping.

Enchant does not have native custom fields, company objects, ticket types, SLA policies, or categories. If those structures carry operational meaning in your Desk365 instance, you need a concrete plan for how to preserve, flatten, or discard that information before you write any migration code.

This guide covers the object mapping between both platforms, API extraction and loading, dependency order, rate limit constraints, attachment handling, and the edge cases that break naive migrations. API details reflect Desk365 API v3 and the Enchant REST API as of the guide's last review; verify against official documentation before running production migrations, as endpoint behavior and rate limits are subject to change.

Scope note: This guide addresses migrations requiring full conversation history, agent attribution, and ongoing editability of migrated tickets. If you only need read-only historical reference, see the Managed Import vs. API decision matrix section.

Why Teams Move from Desk365 to Enchant

The most common drivers:

  • Simplicity over structure. Teams that don't use Desk365's SLAs, custom ticket types, or company-level routing find the overhead unnecessary. Enchant's shared inbox model removes that friction.
  • Multi-brand support. Enchant supports multiple inboxes, knowledge bases, and messengers per account, each with independent branding — useful for teams managing several customer-facing brands from one platform.
  • Channel consolidation. Enchant natively supports email, live chat, phone (via Twilio), SMS, WhatsApp, Facebook Messenger, Instagram DM, and Twitter DM in a single inbox view.
  • Cost. For smaller teams, Enchant's per-user pricing can be significantly lower than Desk365's Plus or Premium tiers, especially when Microsoft 365 integration isn't needed.

It is not a universal win. If your current operation depends on Desk365 companies, department-level visibility, Watchers, Share To, or heavily customized intake forms, expect redesign work. Those concepts do not map 1:1 into Enchant. (help.desk365.io)

The Data Model Shift

Desk365 is a structured helpdesk. Every ticket has a mandatory association with a Contact and can be linked to a Company, Group, Agent, Category, Sub-Category, Type, Priority, Status, SLA, and any number of custom fields (prefixed cf_ in the API). Desk365 enforces relational integrity — tickets require valid contacts, groups map to teams, and companies act as organizational containers. (help.desk365.io)

Enchant is inbox-centric. Its core objects are Tickets, Messages, Customers, Contacts, Users (agents), Inboxes, and Labels. A ticket always belongs to an inbox. Customers carry a first_name, last_name, summary field, and one or more contacts (email, phone, or Twitter). There are no custom fields on tickets, no company object, no ticket types, and no SLA management. Categorization is handled entirely through labels. (dev.enchant.com)

The table below maps the core objects:

Desk365 Object Enchant Equivalent Notes
Agent User Map by email. Create users manually in Enchant before migration.
Contact Customer + Contact Enchant splits identity (Customer) from channel (Contact).
Company None Flatten to customer summary or a label. No company object exists.
Group Inbox One Desk365 group → one Enchant inbox. Not always 1:1 — Enchant inboxes also carry channels, permissions, and language settings.
Ticket Ticket Only email type can be created via API.
Reply / Note Message Enchant messages are typed as reply (with direction: in or out) or note.
Attachment Attachment Must be base64-encoded and uploaded before linking to a message.
Category / Sub-Category Label Create labels in Enchant, map category names to label IDs.
Ticket Type Label Desk365 Type is a business classification (Incident, Problem, Request). Enchant type is the channel (email, chat, phone). Do not confuse them.
Custom Field (cf_*) None Append to first message body, store in customer summary, or discard.
SLA Policy None Not migrated. Enchant does not support SLAs.
Priority Label No direct priority field. Use labels (e.g., Priority: High).
Status State Map to open, hold, or closed. See status mapping below.
Watchers Workflow redesign Enchant's closest concept is following a ticket, which is not equivalent. Treat as a process decision.
Share To Workflow redesign No direct Enchant equivalent. Plan separately.
Danger

Do not map Desk365 Type to Enchant type. Desk365 uses Type as a business classification (Question, Incident, Problem, Request). Enchant uses type as the channel (email, chat, phone, SMS, WhatsApp). If you copy values straight across, your target data is wrong on day one. Use Enchant labels for the business classification instead. (help.desk365.io)

Define Your Migration Scope

Before writing code, decide what moves and what doesn't.

Migrate via API:

  • Customers and contacts
  • Tickets with full conversation history (replies, notes, attachments)
  • Labels (mapped from Desk365 categories, types, and priorities)

Create manually in Enchant:

  • Inboxes (mapped from Desk365 groups)
  • Users (agents) — Enchant does not expose a user creation endpoint in its public API
  • Labels
  • Canned responses / macros
  • Knowledge base articles (as detailed in our Zendesk to Enchant migration guide, Enchant lacks a bulk import API for Help Center content)
  • Automation rules (Enchant uses a different paradigm called Rules — see Automation Mapping below)

Cannot be migrated:

  • SLA policies (no Enchant equivalent)
  • Custom fields as structured, filterable fields (must be flattened or discarded)
  • Company objects (flatten to customer summary or labels)
  • Ticket forms and form configurations
  • Time entries
  • Surveys and satisfaction ratings
  • Watchers and Share To (require workflow redesign)
  • Original creation and modification timestamps (Enchant's API does not expose writable created_at fields — see Timestamp Limitation)
Tip

Should you migrate all tickets? If your Desk365 instance has years of closed tickets, consider whether your team actually needs them in Enchant. Migrating only open, pending, and recently closed tickets (last 12–24 months) can cut migration time dramatically—a strategy we also recommend when migrating from Freshdesk to Enchant. Archive the rest as a CSV export or database dump for compliance reference.

Managed Import vs. API Decision Matrix

Enchant offers two paths for bringing in historical data: a managed import feature and the REST API. They are not interchangeable.

Criterion Managed Import API Migration
Tickets editable after import No — read-only Yes
Appears in live folders No Yes
Included in reports No Yes
Can be run multiple times No — one-time only Yes
Attachment history May be trimmed for large imports Complete if migrated correctly
Suitable ticket volume Low, historical-only datasets Any volume requiring editability
Engineering effort Low High

Use the managed import only for historical reference that does not need to be editable or reportable. If your agents need to continue working tickets after cutover — responding to customers, reassigning, or closing — those tickets must be recreated through the API.

The decision threshold in practice: if more than 5% of the tickets you plan to import are in an open or hold state at migration time, use the API path for the entire dataset. Mixing methods creates split inventory that is operationally confusing.

(help.enchant.com)

API Access and Constraints

Desk365 API v3

Desk365's v3 API is your primary extraction path. The base URL follows the pattern:

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

Authentication uses a Bearer token generated from Settings → Integrations → API in the Desk365 admin portal:

Authorization: Bearer YOUR_DESK365_API_KEY

Key constraints:

  • Pagination: You can retrieve 30, 50, or 100 tickets per API call using the ticket_count parameter. The maximum throughput is 10,000 tickets per hour.
  • Rate limits vary by plan: Standard plan allows 100 API calls per hour. Plus and Premium plans allow 50 API calls per minute. If you extract through the Microsoft connector instead of raw HTTP, Microsoft Learn documents a different throttle of 100 calls per 60 seconds per connection — that difference changes how aggressive your extraction can be. (help.desk365.io)
  • Descriptions not included by default: The include_description parameter must be set to include ticket descriptions in list responses. The conversations endpoint does not include the ticket description — you must pull it from the ticket record separately.
  • Custom fields: All custom fields are returned with the cf_ prefix (e.g., cf_department, cf_location).
Tip

Freeze Desk365 field dictionaries before export. Desk365 lets admins rename statuses and type choices, and those changes flow through existing tickets, activities, and automations. Late edits can invalidate your mapping sheet. (help.desk365.io)

Enchant REST API

Enchant's API base URL:

https://site.enchant.com/api/v1

Replace site with your Enchant helpdesk identifier. Authentication uses Bearer tokens:

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

Key constraints:

  • Rate limit: 100 credits per minute across the entire account, all endpoints, users, and tokens. Embedded resources and count queries consume additional credits, so validation jobs that repeatedly call embed=customer,labels,messages will throttle faster than flat reads. (dev.enchant.com)
  • Burst limit: 6 requests per second maximum.
  • Pagination: 0–100 results per page, 10 by default. For datasets exceeding 10,000 records, iterate using since_created_at instead of page numbers.
  • Ticket creation: Only tickets of type: "email" can be created via the API. Desk365 tickets originally created through Teams, web forms, or other channels will be recreated as email-type records.
  • Attachment upload: Attachments must be base64-encoded and uploaded individually via POST /api/v1/attachments before being linked to a message by attachment_ids.
  • Customer deduplication: When creating a ticket, you can pass a customer object instead of customer_id. Enchant will look up existing customers by contact value and create a new one if no match is found.
  • No API endpoints for inboxes, users, or labels. These must be created manually in the Enchant admin UI before your migration script runs.

Timestamp Limitation

Enchant's published API documentation does not expose writable created_at fields on tickets or messages. In testing, all migrated records carry the migration execution timestamp rather than the original Desk365 timestamp. This is the most operationally significant data loss in the migration — agents lose the ability to sort or filter tickets by original submission date.

Two mitigations:

  1. Body annotation (always do this): Append the original Desk365 creation date to the first message body as a visible HTML block (e.g., <p><em>Original submission date: 2022-11-14 09:32 UTC</em></p>). This preserves the information in a human-readable form regardless of what the API allows.
  2. Confirmed API behavior: Contact Enchant support before committing to an API-only path to confirm whether private or partner API access exposes writable timestamp fields. Document the response. If writable timestamps are available under any access tier, this is worth negotiating before migration begins.

If preserving original timestamps is a hard compliance requirement, treat this as a blocker until confirmed. Do not assume the body annotation alone satisfies audit requirements.

Warning

Timestamp limitation: Enchant's published API docs do not document writable created_at fields on tickets or messages. All migrated records may carry the migration execution timestamp instead of the original Desk365 timestamp. Confirm with Enchant support before committing to an API-only path. As a fallback, append the original date to the message body as a visible annotation. (dev.enchant.com)

Migration Dependency Order

Enchant enforces referential dependencies during creation. You cannot create a ticket without knowing the Inbox ID and Customer ID. You cannot create a message without a valid User ID. Load data in this order:

  1. Inboxes — Create manually in Enchant admin, mapped from Desk365 Groups.
  2. Users (Agents) — Create manually. Match by email address. Build an agent-to-user ID mapping table.
  3. Labels — Create manually. One label per Desk365 category, type, priority level, and any company names you want to preserve.
  4. Customers — Create via POST /api/v1/customers. Include all contacts (emails, phone numbers) in the initial payload.
  5. Tickets — Create via POST /api/v1/tickets with the initial message.
  6. Additional Messages — For replies and notes beyond the initial message, use POST /api/v1/tickets/{id}/messages.
  7. Attachments — Upload via POST /api/v1/attachments first, then reference attachment_ids in the message creation payload.

Maintain a local key-value store (Redis, SQLite, or a JSON file) during the migration to map legacy Desk365 IDs to new Enchant IDs.

Handling Inactive Agents

If a Desk365 agent is inactive or has left the company, Enchant still requires a valid user_id to attribute their historical messages. Two options:

  1. Pay for legacy seats in Enchant just to hold the data (cost-prohibitive at scale).
  2. Create a single "Legacy Agent" user in Enchant. Map all inactive Desk365 agents to this ID, and prepend their real name to the body of their historical messages (e.g., [Original Author: Jane Doe]).

The second approach is preferred in almost every case. The first option is practical only if fewer than 3–4 former agents have significant open ticket histories that active agents need to review in context.

Multi-Group Ticket Handling

Desk365 allows tickets to move across groups during their lifecycle — via manual reassignment, automation rules, or CC/forwarding patterns. Enchant's inbox model means a ticket exists in exactly one inbox at any given time. Moving a ticket to a different Enchant inbox automatically unassigns it from its current agent.

For tickets with a group-movement history, apply this rule: migrate the ticket to its final group at time of migration, not its origin group. The movement history is not reproducible in Enchant's inbox model. If the audit trail of group changes matters, include a structured note on the ticket listing each group transition with timestamps before closing the source record.

Field-Level Mapping and Transformation

Status Mapping

Desk365 uses configurable statuses. The defaults are Open, Pending, Resolved, and Closed. Enchant's writable ticket states are open, hold, and closed.

Desk365 Status Enchant State
Open open
Pending hold
Waiting on Customer / Waiting for Vendor hold
Resolved closed
Closed closed
Custom statuses Map to open, hold, or closed based on intent
Warning

Enchant does not distinguish between "Resolved" and "Closed." If that distinction matters for your reporting, preserve the original Desk365 status as a label (e.g., status:resolved) before migration.

Build an explicit mapping configuration before writing your script:

{
  "status_map": {
    "Open": "open",
    "Pending": "hold",
    "Waiting for Vendor Reply": "hold",
    "Resolved": "closed",
    "Closed": "closed"
  },
  "type_to_labels": {
    "Incident": ["incident"],
    "Problem": ["problem"],
    "Request": ["request"]
  },
  "preserve_in_note": [
    "desk365_ticket_number",
    "desk365_company",
    "desk365_department",
    "original_created_on",
    "original_closed_on"
  ]
}

This makes your mapping decisions reviewable and testable before any data moves.

Handling Custom Fields

Desk365 supports unlimited custom fields (cf_ prefix). Enchant has none. Three options:

Option 1: Labels. For boolean or dropdown custom fields (e.g., Tier: 1, Product: Mobile), create corresponding Labels in Enchant and attach them to the ticket during creation.

Option 2: Body injection. For text-heavy custom fields, dates, or data that doesn't fit as a label, inject an HTML block into the first message of the ticket. This ensures historical context is immediately visible to the agent opening the migrated ticket.

# Appending Desk365 custom fields to the Enchant message body
custom_fields = {
    "cf_department": "Engineering",
    "cf_product": "Widget Pro",
    "cf_region": "EMEA"
}
 
cf_block = "\n".join([f"<b>{k.replace('cf_', '')}:</b> {v}" for k, v in custom_fields.items() if v])
original_body = ticket["description"]
enhanced_body = f"{original_body}<hr><p><em>Migrated Desk365 custom fields:</em></p><p>{cf_block}</p>"

Option 3: Discard. If the custom field data has no operational value post-migration, don't migrate it. Document what was dropped.

Body injection preserves data visibility but makes it unsearchable. Labels are searchable but don't work well for free-text values. Most migrations use a combination of both — labels for structured dropdown fields, body injection for free-text and date fields.

Handling Companies

Desk365's Company object groups contacts by organization. Enchant has no equivalent. Options ranked by utility:

  • Label-based: Create a label for each company (e.g., Company: Acme Corp) and apply it to all tickets from that company's contacts. This preserves filterability.
  • Customer summary: Append the company name, account tier, and contract IDs to each customer's summary field. This is immediately visible when an agent opens a customer profile.
  • Sidebar app: For B2B teams with complex account data, consider using Enchant's sidebar app feature to load live company data from your system of record instead of storing it statically in the helpdesk.
  • All of the above: Use labels for filtering, summary for at-a-glance context, and a sidebar app for live detail.

For most B2B migrations with 50+ companies, the combined approach (label + summary) is the minimum viable replacement. The sidebar app is worth the engineering investment if company data changes frequently post-migration.

Handling Priority

Desk365 has a structured priority field (Low, Medium, High, Urgent). Enchant does not. Map priorities to labels:

Desk365 Priority Enchant Label
Low Priority: Low
Medium Priority: Medium
High Priority: High
Urgent Priority: Urgent

Automation Rule Mapping

Desk365 supports automations triggered by ticket events, time conditions, and field values. Enchant's equivalent is its Rules engine. The paradigms differ: Desk365 automations can trigger on SLA breach (which has no Enchant equivalent), while Enchant rules are event-driven and apply within a single inbox context.

The five most common Desk365 automation patterns and their Enchant equivalents:

Desk365 Automation Pattern Enchant Rule Equivalent Notes
Auto-assign by group Inbox-level assignment rule on ticket creation Configure per inbox; no global cross-inbox auto-assignment
Priority escalation on keyword Rule: when ticket subject/body contains keyword → apply label Labels replace priority field; set Priority: Urgent label
SLA breach escalation No equivalent Enchant has no SLA engine. Replicate with a time-based rule if available, or replace with an external monitoring integration
Auto-close after N days inactive Rule: when ticket has no activity for N days → close Available in Enchant Rules; verify trigger availability in your plan
Status change notification to requester Rule: when ticket state changes → send email Enchant handles this natively via notification settings per inbox

Automation rules must be rebuilt manually. Export your Desk365 automation list from Settings → Automations before migration and map each rule against this table. Flag any rule that depends on SLA breach or custom field values — those require redesign, not just rebuilding.

Extraction: Getting Data Out of Desk365

Two-Pass Extraction

Extract Desk365 data in two passes:

Pass one: Pull ticket-level data — subject, description, contact, company, group, status, type, category, subcategory, assignee, and every custom field you need. Also extract the full list of agents, groups, companies, and contacts.

Pass two: Pull conversation history (replies, notes) and attachment metadata for each ticket.

This split matters because Desk365's conversations endpoint does not include the ticket description. The description is a separate field on the ticket record. If you rebuild the first message from the conversations endpoint alone, you will lose the original ticket description. (apps.desk365.io)

If you use company or contact data operationally, capture the exact API labels early. Desk365's contact and company field docs note that API labels are used for updates through APIs and Entra sync, and once saved they cannot be edited. (help.desk365.io)

Extraction Order

  1. Agents / Groups — Extract first. You need these to map assigned_to and group values to Enchant users and inboxes.
  2. Companies — Extract if you plan to preserve company data as customer summaries or labels.
  3. Contacts — Extract all contacts with their associated company and custom contact fields.
  4. Tickets — Extract with descriptions (include_description parameter), custom field values, and all metadata.
  5. Conversations — Extract replies and notes for each ticket.
  6. Attachments — Download all attachment files referenced in conversations.

CSV Export Limitations

Desk365's CSV export (from the ticket list view in the Agent Portal) exports ticket metadata — ticket number, subject, status, priority, contact info, custom fields. It does not include conversation threads (replies, notes, or attachments). For any migration that needs conversation history, the API is the only viable extraction path.

Info

Desk365 does not support idempotency keys on extraction. If your script fails mid-run, track progress externally (e.g., store the last successfully extracted ticket ID in your state file) to avoid re-processing.

Store all extracted data in local staging (JSON files or a database) to decouple extraction from loading. This lets you re-run the load phase without hitting Desk365's rate limits again.

Throughput Benchmarks

Based on migrations using the Desk365 Standard plan API:

  • Ticket metadata (Pass 1): ~100 list calls/hour × 100 tickets/call = ~10,000 ticket metadata records/hour.
  • Conversation extraction (Pass 2): Each ticket requires at least 1 additional API call. A ticket with 10 conversation messages may require 2–3 calls depending on pagination. Realistic throughput: 2,000–4,000 fully hydrated tickets per hour on Standard plan.
  • Desk365 Plus/Premium plan: 50 calls/minute = ~3,000 calls/hour theoretical, but conversation depth and attachment volume are the practical bottleneck. Expect 8,000–12,000 hydrated tickets per hour under ideal conditions.
  • Enchant load side: At 100 credits/minute, a ticket with 5 messages and 3 attachments consumes approximately 9–11 credits (1 ticket create + 5 message creates + 3 attachment uploads + query overhead). Sustainable throughput: roughly 500–600 fully hydrated tickets per hour on the Enchant side.

Enchant's 100 credits/minute limit is the binding constraint on load throughput for any migration with significant conversation history or attachments. Plan accordingly — a 50,000-ticket migration with average attachment density will take 80–100 hours of load time. Extract everything first, then schedule the load phase across multiple days.

Loading: Getting Data Into Enchant

Create Customers First

Create Enchant customers via POST /api/v1/customers before creating tickets. While Enchant can auto-create customers during ticket creation if you pass a customer object, precreating gives you control over deduplication.

Desk365 supports contacts with multiple secondary emails. (help.desk365.io) Enchant customer profiles also support multiple email addresses and phone numbers. Migrate every verified contact point into one canonical customer record to avoid duplicate customer entries.

For company data: Desk365 has first-class companies and department-level structures. Enchant's customer model is lighter, so company name, account tier, and contract IDs typically belong in the customer summary field, labels, or a sidebar app.

Recreate Tickets and Conversations

The clean sequence for each ticket:

  1. Create the Enchant ticket in its final inbox with type: "email", subject, customer, and assignee.
  2. Upload each attachment to POST /api/v1/attachments (base64-encoded).
  3. Create replies and notes in chronological order via POST /api/v1/tickets/{id}/messages, attaching the returned attachment_ids to the correct message.
  4. Patch the ticket to its final state and labels.

When creating messages, differentiate between public replies and internal notes:

  • Map Desk365 public replies to Enchant messages with direction: "in" (customer) or direction: "out" (agent).
  • Map Desk365 internal notes to Enchant messages with type note.
  • Inbound replies require from_name, from (email address), direction: "in", body, and htmlized. Missing any of these returns a 422 validation error.
  • Set htmlized: true if the message body contains HTML. Desk365 descriptions often include HTML formatting.

CC and BCC handling: Desk365 stores CC and BCC recipients on the ticket or individual message level. Enchant handles CCs at the message level. When migrating a public reply, extract the CC array from Desk365 and include it in the Enchant message payload to ensure secondary stakeholders stay looped in.

Info

If your old Desk365 workflows reassign tickets across groups, design that carefully. In Enchant, moving a ticket to another inbox automatically unassigns it. The safe pattern is usually: choose the final inbox first, then apply the final assignee. (help.enchant.com)

Suppressing Outgoing Emails

When you POST a message to an Enchant ticket via the API, the system may attempt to email the customer.

Danger

You must explicitly disable email sending during migration. Check Enchant's API documentation for the current flag to suppress notifications on message creation. Failing to do this will result in customers receiving thousands of historical emails within minutes. This is the most operationally damaging failure mode in the entire migration.

Attachment Processing

Attachments must be physically moved from Desk365 to Enchant:

  1. Download: GET the attachment from Desk365. Pass your Desk365 Bearer token — attachments are authenticated.
  2. Encode: Base64-encode the file content.
  3. Upload: POST to Enchant's POST /api/v1/attachments with name, type (MIME type), and data (base64 string).
  4. Link: Capture the returned attachment id and include it in the attachment_ids array when creating the corresponding message.
Danger

Unassociated attachments in Enchant are automatically deleted. Always create the message immediately after uploading its attachments. If your script fails between upload and message creation, those attachments are lost and must be re-uploaded. Design your retry logic to re-upload attachments on any message creation failure — do not assume the attachment still exists.

Rate limit impact: Each attachment upload consumes one API credit. A ticket with 5 attachments across 3 messages costs at minimum 9 credits (5 uploads + 3 message creates + 1 ticket create), before counting query overhead. At 100 credits per minute, attachment-heavy migrations will be the bottleneck. Pre-inventory your attachment volume from Desk365 before estimating migration duration.

The Inline Image Trap

Desk365 users frequently paste screenshots directly into the editor. These become inline images with <img> tags pointing to Desk365 URLs. If you push this HTML directly to Enchant, the images will break — the Desk365 URLs require authentication, and eventually your Desk365 instance will be decommissioned.

Parse the HTML body of every Desk365 message before pushing it to Enchant. The safe pattern is to download each inline image, upload it to Enchant as a standalone attachment, and replace the original src with a reference Enchant will render. The exact inline referencing method (CID vs. attachment URL) varies by platform implementation — confirm with Enchant support before finalizing this pattern:

const cheerio = require('cheerio');
 
async function processInlineImages(htmlBody, desk365Token, enchantAuth) {
  const $ = cheerio.load(htmlBody);
  const images = $('img');
  
  for (let i = 0; i < images.length; i++) {
    const img = images[i];
    const src = $(img).attr('src');
    
    if (src && src.includes('desk365.io')) {
      // 1. Download from Desk365 (authenticated)
      const fileBuffer = await downloadFromDesk365(src, desk365Token);
      
      // 2. Upload to Enchant as a standalone attachment
      // Returns an attachment ID and, depending on Enchant's implementation,
      // either a public URL or a CID reference for inline use.
      // Confirm the returned URL format with Enchant support before production.
      const { attachmentId, attachmentUrl } = await uploadToEnchant(fileBuffer, enchantAuth);
      
      // 3. Replace src with Enchant-hosted URL
      $(img).attr('src', attachmentUrl);
    }
  }
  
  return $.html();
}

Additionally, run all Desk365 HTML through a strict sanitizer (DOMPurify or sanitize-html) to strip proprietary Microsoft classes, styles, and tags — Teams-originated replies in particular contain heavy XML namespaces (<o:p>, VML shapes, nested table structures) that break Enchant's UI rendering. Strip Microsoft-specific markup while retaining core formatting (bold, italics, lists, links).

Rate Limit Strategy

You are dealing with two independent rate limiters:

Platform Rate Limit Burst Limit
Desk365 (Standard) 100 calls/hour Not documented
Desk365 (Plus/Premium) 50 calls/minute Not documented
Enchant 100 credits/minute 6 requests/second

For most migrations, Enchant's rate limit is the binding constraint on the load side, while Desk365's Standard plan limit is the binding constraint on the extract side.

Recommendations:

  • Extract all Desk365 data to local storage first, then load into Enchant as a separate phase. This decouples the two rate limiters.
  • Use exponential backoff on 429 responses from both APIs.
  • Enchant returns Rate-Limit-Remaining and Rate-Limit-Reset headers — use them to throttle proactively rather than reactively.
  • Consider libraries like axios-retry (Node.js) or Tenacity (Python) for resilient HTTP clients. Configure them to retry only on 429 and 5xx codes while failing immediately on 401 or 404. This prevents your script from endlessly retrying a request for a permanently deleted resource.

Error Handling and Idempotency

Neither Desk365 nor Enchant provides native idempotency keys. Your migration script must handle:

  • Duplicate detection. Before creating a customer, check if one already exists by querying GET /api/v1/customers?contacts.type=email&contacts.value={email}. Enchant's auto-deduplication during ticket creation also helps, but precreating customers gives you more control.
  • Partial failure recovery. Track every successfully created Enchant record (customer ID, ticket ID, message ID) in a local state file or database. If the script crashes, resume from the last successful record.
  • Validation errors. Enchant returns 422 with specific error messages. Log the full response body and the source Desk365 record for manual review.
  • Attachment orphaning. If a message creation fails after attachments were uploaded, those attachments are auto-deleted by Enchant. Re-upload them on retry.
  • Dead-letter queue. Log all permanent failures to a dead-letter queue (a simple JSON file or database table). If a specific ticket fails due to a malformed email or corrupted attachment, the script should log the failure, the specific phase of failure (e.g., "failed during attachment upload" vs. "failed during ticket creation"), and the source Desk365 ticket ID, then move on rather than crashing the entire migration.
# Resumable migration with state tracking
import json
 
state_file = "migration_state.json"
state = load_state(state_file)
 
for ticket in desk365_tickets:
    if ticket["id"] in state["completed_tickets"]:
        continue
    
    try:
        enchant_ticket = create_enchant_ticket(ticket)
        for message in ticket["messages"]:
            attachment_ids = upload_attachments(message["attachments"])
            create_enchant_message(enchant_ticket["id"], message, attachment_ids)
        
        state["completed_tickets"].append(ticket["id"])
        save_state(state_file, state)
    except RateLimitError:
        wait_for_reset()
        continue
    except ValidationError as e:
        log_error(ticket["id"], e, phase="message_creation")
        continue

Common Failure Modes

Failure Cause Fix
422 on ticket creation Missing customer_id or type not set to email Ensure every ticket has a valid customer and type: "email"
422 on inbound reply Missing from or from_name Extract sender email and name from Desk365 conversation data
Orphaned attachments Message creation failed after attachment upload Re-upload attachments on retry; do not assume uploaded attachment persists
Duplicate customers Same contact email created twice in parallel Query-before-create or use customer object in ticket creation
Missing conversations Used Desk365 CSV export instead of API Always use API for conversation history
Missing ticket description Pulled from conversations endpoint only Description is a separate ticket field — pull it explicitly with include_description
Rate limit exceeded Standard plan's 100 calls/hour is easy to hit Extract to staging first, pace requests
Wrong inbox assignment Group-to-inbox mapping not validated Build and validate mapping table before migration
Broken inline images Desk365 image URLs require authentication Download and re-upload inline images; replace src before loading
UI rendering failure on Teams replies Microsoft-proprietary HTML pushed directly to Enchant Strip <o:p>, VML, and Microsoft XML namespaces before loading
Timestamp confusion Migration timestamp appears on all records Append original dates to message body as visible annotation
Inactive agent attribution failure No valid Enchant user_id for departed agents Use Legacy Agent pattern; prepend original author name to message body

What You Lose in the Migration

Be explicit with stakeholders about what doesn't survive:

  • SLA history and policies — Enchant has no SLA engine.
  • Custom field structure — Data can be preserved as text, but not as structured, filterable fields.
  • Company hierarchy — No organizational grouping in Enchant.
  • Original timestamps — Enchant's published API does not document writable created_at fields; migrated records will show the migration execution date.
  • Ticket types as a structured field — Flattened to labels.
  • Priority as a structured field — Flattened to labels.
  • Time entries — No equivalent in Enchant.
  • Surveys / satisfaction ratings — Not transferable.
  • Automation rules — Must be rebuilt using Enchant's rules engine; see Automation Rule Mapping.
  • Watchers and Share To — Require workflow redesign, not field mapping.
  • Group movement history — Tickets are migrated to their final group; historical inbox reassignment trail is not reproducible.

This isn't a failure of the migration — it's the trade-off of moving to a simpler system. If any of these are non-negotiable, revisit whether Enchant is the right target. As we covered in our Enchant to Desk365 migration guide, the data model gap between these two platforms is real in both directions.

Pre-Migration Checklist and Validation

Before You Start

  1. Audit Desk365 data. Count tickets, contacts, companies, and attachments. Use the throughput benchmarks above to estimate migration time.
  2. Audit Desk365 automations. Export the full automation list. Map each rule against the Automation Rule Mapping table and flag those requiring redesign.
  3. Create Enchant account and configure inboxes. Map each Desk365 group to an Enchant inbox. Verify that Enchant has no sandbox/staging environment in your plan tier before running dry runs against production — if staging is unavailable, run dry runs on a small set of non-critical tickets first.
  4. Add users (agents) to Enchant. Match by email address. Build an agent ID mapping table. Create a "Legacy Agent" user for inactive agents.
  5. Create labels in Enchant. One label per Desk365 category, type, priority level, and any company names you want to preserve.
  6. Decide on custom field handling. Document which fields get appended to messages, stored in summaries, converted to labels, or discarded.
  7. Freeze Desk365 field dictionaries. No more renaming statuses or types until extraction is complete.
  8. Inventory integrations. Export the list of Desk365 webhooks, Power Automate flows, and third-party app connections. Map each to a Enchant webhook equivalent or identify integrations that require redesign. "Plan replacements" is not sufficient — identify the specific Enchant webhook events and payloads that correspond to each Desk365 trigger before cutover.
  9. Confirm timestamp behavior with Enchant support. If original timestamps are a compliance requirement, get written confirmation of available options before committing to the API path.
  10. Run a test migration. Migrate 10–20 tickets spanning each major group, status, attachment pattern, and form variant. Verify conversation threading, attachments, agent attribution, label assignment, and timestamp annotation.

Validation Checks Before Cutover

Do not stop at record counts. Check a sample across each mapped status, each major group, each form variant, and each attachment pattern:

  • Description check: The original Desk365 ticket description is visible in the first message — remember the conversations endpoint does not include it.
  • Attachment check: Every sampled file downloads from the message you expect. Inline images render without broken-image placeholders.
  • Timestamp annotation check: Original creation and closure dates are visible in the message body for each sampled ticket.
  • State check: Every Desk365 custom status collapsed into the intended Enchant state, with the original status preserved as a label where needed.
  • Customer check: Secondary emails and phone numbers merged into the right Enchant customer instead of splintering into duplicates.
  • Workflow check: Tickets that depended on Watchers, Share To, or department routing have a documented replacement workflow.
  • Automation check: SLAs, routing rules, macros, and notifications are rebuilt in Enchant rather than assumed to migrate with the data. Test each rebuilt rule against a live ticket before cutover.
  • Live reply check: Migrated open tickets can receive and send normal replies in Enchant without triggering notification suppression.
  • Multi-group check: Tickets that moved across groups during their Desk365 lifecycle landed in the correct final inbox, with a structured note documenting their movement history.

The Path Forward

A good Desk365 to Enchant migration does not try to preserve every source construct literally. It keeps live work live, preserves the business meaning of statuses and categories, and makes the gaps explicit. Companies, Watchers, Share To, custom form logic, historical timestamps, and source ticket numbers all need a deliberate plan — documented in a mapping configuration before a single API call runs. Design those choices up front and the technical work becomes predictable. Skip that step and the API script is the easy part — the operational cleanup is what hurts.

Frequently Asked Questions

Can I migrate Desk365 tickets to Enchant using CSV export?
Only partially. Desk365's CSV export includes ticket metadata but not conversation threads, replies, notes, or attachments. For a full migration with conversation history, you must use the Desk365 API v3 for extraction and the Enchant REST API for loading.
What happens to Desk365 custom fields when migrating to Enchant?
Enchant does not support custom fields. You can append custom field values to the ticket's first message body as formatted text, store customer-level data in Enchant's customer summary field, convert boolean or dropdown values to labels, or discard the data. The structured, filterable nature of the fields is lost regardless.
Does Enchant preserve original ticket timestamps during API migration?
Enchant's published API docs do not document writable created_at fields on tickets or messages. Migrated records may carry the timestamp of when they were created during migration. Confirm with Enchant support if this is a hard requirement, or append original dates to message bodies as annotations.
Does Desk365 Type map to Enchant type?
No. Desk365 Type is a business classification (Question, Incident, Problem, Request). Enchant type is the channel (email, chat, phone, WhatsApp). Copying values straight across will corrupt your data. Use Enchant labels for the business classification instead.
What are the API rate limits for Desk365 and Enchant during migration?
Desk365 Standard plan allows 100 API calls per hour; Plus and Premium plans allow 50 calls per minute. Enchant allows 100 credits per minute with a burst limit of 6 requests per second. Extract Desk365 data to local staging first, then load into Enchant as a separate phase to decouple the two rate limiters.

More from our Blog

Enchant to Desk365 Migration: A Technical Guide
Enchant/Migration Guide/Help Desk

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