---
title: "Salesforce Service Cloud to eDesk Migration: Technical Guide"
slug: salesforce-service-cloud-to-edesk-migration-technical-guide
date: 2026-08-26
author: Roopi
categories: [Salesforce Service Cloud, Migration Guide, Help Desk]
excerpt: "Technical guide to migrating Salesforce Service Cloud to eDesk. Covers data model mapping, SOQL extraction, API rate limits, channel restrictions, and cutover steps."
tldr: "Salesforce-to-eDesk migration requires a custom API pipeline. eDesk's 60 req/min rate limit, channel restrictions, and flat data model are the primary constraints to plan around."
canonical: https://clonepartner.com/blog/salesforce-service-cloud-to-edesk-migration-technical-guide/
---

# Salesforce Service Cloud to eDesk Migration: Technical Guide


# Salesforce Service Cloud to eDesk Migration: Technical Guide

Migrating from **Salesforce Service Cloud to eDesk** means moving from an enterprise CRM with a deep relational data model to a flat, eCommerce-first helpdesk built around marketplace order context. Salesforce treats a support interaction as a `Case` linked to an `Account` and `Contact`, with conversation history distributed across `EmailMessage`, `CaseComment`, and `LiveChatTranscript` objects. eDesk treats it as a `Ticket` tied to a `Channel` and optionally a `Sales Order`, with all conversation data represented as `Messages` and `Notes` in a single chronological thread.

There is no native migration path between these platforms. A CSV export from Salesforce will shatter conversation threads, drop attachments, and fail to map historical tickets to their e-commerce orders. The viable route is: **Salesforce Bulk API 2.0 extraction → transformation layer → eDesk REST API loading**.

This guide covers data model mapping, SOQL extraction patterns, eDesk API constraints, transformation logic, rate limit math, and the failure modes that surface in production migrations.

For details on getting data out of Salesforce, see our [Salesforce Service Cloud export guide](https://clonepartner.com/blog/blog/how-to-export-data-from-salesforce-service-cloud-methods-limits/). For a pre-migration checklist, see our [Salesforce Service Cloud migration checklist](https://clonepartner.com/blog/blog/salesforce-service-cloud-migration-checklist/).

> [!NOTE]
> All API-specific claims reflect documentation as of mid-2026. Verify rate limits, field constraints, and plan requirements against current platform docs before implementation.

## Why E-commerce Teams Leave Salesforce for eDesk

The move is almost always driven by a shift in business model, not dissatisfaction with Salesforce's capabilities. Teams migrate when their support operations become dominated by eCommerce marketplace interactions — Amazon, eBay, Walmart, Shopify — and Salesforce's general-purpose architecture becomes overhead rather than advantage.

Common triggers:

- **Marketplace-native support.** eDesk natively integrates with 250+ eCommerce channels, pulling order details, tracking information, and buyer messages directly into the ticket view. Salesforce requires custom integrations, middleware like MuleSoft, or AppExchange apps for each marketplace. ([edesk.com](https://www.edesk.com/helpdesk/))
- **Order-linked conversations.** eDesk's core design links every ticket to a sales order with product, shipping, and tracking data visible in-context. In Salesforce, this requires custom objects or a connected Commerce Cloud instance.
- **Cost reduction.** Salesforce Service Cloud Enterprise runs approximately $165/user/month (billed annually). eDesk's plans start significantly lower. Verify current rates directly with both vendors before using pricing as a migration justification — both platforms revise pricing regularly.
- **Operational simplicity.** Teams that don't need Salesforce's reporting engine, OmniChannel routing, Entitlement Processes, or Flow-based automation find the platform's complexity slows them down.
- **Contract consolidation around eCommerce.** If the business already runs Shopify, Amazon Seller Central, and a 3PL — and doesn't use Sales Cloud or Marketing Cloud — Salesforce becomes an expensive island.

> [!WARNING]
> **When not to migrate:** If your support team handles B2B cases with multi-level account hierarchies, SLA tracking via Entitlements, or cases that touch Sales and Marketing data in the same org, eDesk is not a replacement. It is purpose-built for eCommerce, not general enterprise service management. (The need to unify these exact data models is [why teams often migrate back to Salesforce](https://clonepartner.com/blog/blog/zendesk-to-salesforce-service-cloud-migration-technical-guide/) from standalone helpdesks.)

## Data Model Comparison: Salesforce Service Cloud vs. eDesk

Understanding the architectural gap is the first step. These platforms solve different problems with fundamentally different data structures. eDesk's architecture is intentionally flat: **Customer → Ticket → Messages/Notes → Sales Orders**. Salesforce's is not: `Case → EmailMessage → ContentDocumentLink → ContentVersion`, plus `Account → Contact → Case`, plus `Entitlement → EntitlementProcess → MilestoneType`.

| Salesforce Service Cloud | eDesk | Migration Impact |
|---|---|---|
| **Case** | **Ticket** | 1:1 mapping. Status values differ. `channel_id` required. |
| **EmailMessage** (on Case) | **Message** (on Ticket) | HTML body, timestamps, direction map directly. Use `send=false` for historical imports. |
| **CaseComment** (public) | **Message** (on Ticket) | Public comments become outbound messages. |
| **CaseComment** (private) | **Note** (on Ticket) | eDesk separates customer-visible Messages from internal Notes. ([developers.edesk.com](https://developers.edesk.com/reference/createmessage?utm_source=openai)) |
| **Account** | *(no equivalent)* | eDesk has no company/organization object. Encode `Account.Name` in a custom field or tag. |
| **Contact** | **Contact** | Partial mapping. eDesk contacts are lightweight — name, email, phone. Fields like `AccountId`, `MailingAddress` have no target. |
| **ContentVersion / Attachment** | **Attachment** (on Message) | Attachments are supported on message creation, not ticket creation. Validate payload format — eDesk returns error `4019` for malformed attachments. Maximum attachment size and supported MIME types should be confirmed with eDesk before bulk loading. |
| **Entitlement / SLA** | *(no equivalent)* | SLA tracking, milestone types, and entitlement processes have no eDesk counterpart. |
| **KnowledgeArticle** | *(no equivalent)* | eDesk has no knowledge base import via API. |
| **LiveChatTranscript** | *(partial)* | eDesk supports live chat but has no API import path for historical transcripts. |
| **Custom Objects** | **Custom Fields** (on Ticket) | eDesk supports custom fields on tickets — flat key-value pairs only, no related objects. |

The pattern is clear: Salesforce's relational depth gets flattened. This is a **lossy transformation** by design—a structural reality common to any enterprise-to-helpdesk move, such as [migrating from Salesforce to Zendesk](https://clonepartner.com/blog/blog/salesforce-service-cloud-to-zendesk-migration-technical-guide/) or [Gorgias](https://clonepartner.com/blog/blog/salesforce-service-cloud-to-gorgias-migration-technical-guide/). The acceptable losses are: Account hierarchies, Entitlements, SLA configurations, Knowledge Articles, LiveChatTranscripts, Approval Processes, and all relational linkages between support data and sales/marketing objects. The preserved data is: ticket metadata, conversation threads (EmailMessage + CaseComment), customer contact records, tags, and flat custom field values.

### What Migrates Cleanly

- **Ticket metadata:** Subject, status, priority, creation date, close date
- **Conversation threads:** EmailMessage body (HTML), sender, timestamps, direction (inbound/outbound)
- **Customer records:** Name, email, phone from Contact
- **Tags/categories:** Case `Type`, `Reason`, and picklist values can map to eDesk Tags
- **Custom field values:** Simple text, number, and picklist values via eDesk's `custom_fields` array

### What Migrates with Data Loss

- **CaseComments:** Public comments map to Messages and private comments map to Notes, but original Salesforce metadata (CreatedBy user details, etc.) may not fully survive.
- **Account context:** Company name, industry, and account hierarchy are lost as relational data. You can encode `Account.Name` into a custom field, but the relational link is gone.
- **Attachments:** Salesforce stores files in `ContentVersion` accessible via REST API. eDesk supports attachments on message creation, but validate the payload format — eDesk returns error `4019` for malformed attachments. ([developers.edesk.com](https://developers.edesk.com/reference/createmessage?utm_source=openai))

### What Does Not Migrate

- Entitlements and SLA configurations
- Knowledge Articles
- Approval Processes and Case escalation rules
- Salesforce Flow / Process Builder automations
- Reports and Dashboards
- LiveChatTranscript records
- Multi-level Account hierarchies
- Custom object relationships

### Cases with No EmailMessages

Salesforce frequently contains Cases created by phone, manually by agents, or via API integrations that never generated an `EmailMessage` record. These are common in B2B environments. Before migration, identify the volume: `SELECT COUNT() FROM Case WHERE Id NOT IN (SELECT ParentId FROM EmailMessage)`.

For these cases, you have three options:
1. **Migrate as stub tickets** — Create the ticket with metadata only (subject, status, dates, custom fields). The ticket exists in eDesk with no conversation history, which is accurate.
2. **Add a synthetic note** — Create a Note on the ticket that says "Originally created via [phone/manual entry] — no email history available." This preserves the audit trail without fabricating conversation data.
3. **Exclude entirely** — If phone cases have no actionable history, exclude them from migration scope. Document the exclusion decision and the count.

There is no universally correct answer. Make the decision explicitly and apply it consistently.

## Step 1: Extract Data from Salesforce

Salesforce doesn't offer a single-click export that preserves object relationships. Use **Bulk API 2.0** for volume extraction — Salesforce recommends it for jobs over 2,000 records, and it supports up to 1 TB/day of extract throughput. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=sf.integrate_what_is_api.htm&language=en_US&utm_source=openai))

Do not use the Salesforce Data Export service (the weekly ZIP file). It is too slow, limits relational mapping, and truncates rich text fields.

### Setting Up Bulk API 2.0 Authentication

Bulk API 2.0 requires OAuth 2.0 authentication via a Connected App in Salesforce. Before writing any SOQL:

1. **Create a Connected App** in Salesforce Setup → App Manager → New Connected App. Enable OAuth, select scopes: `api`, `refresh_token`, `offline_access`.
2. **Use the JWT Bearer Flow** for server-to-server migration scripts (no interactive login required). Generate an RSA key pair, upload the certificate to the Connected App, and sign JWT assertions with your private key.
3. **Alternatively, use Username-Password Flow** for simpler setups: `POST https://login.salesforce.com/services/oauth2/token` with `grant_type=password`, `client_id`, `client_secret`, `username`, `password+security_token`.
4. The response returns an `access_token` and `instance_url`. Use both for all subsequent Bulk API 2.0 calls: `POST {instance_url}/services/data/v59.0/jobs/query`.

For migration scripts running over multiple days, implement token refresh. Access tokens expire; refresh tokens do not (unless revoked).

### Core SOQL Queries

**Cases:**
```sql
SELECT Id, CaseNumber, Subject, Description, Status, Priority,
       Type, Reason, Origin, ContactId, AccountId, OwnerId,
       CreatedDate, ClosedDate, IsClosed,
       Shopify_Order_ID__c, Amazon_Order_Ref__c
FROM Case
WHERE CreatedDate >= 2023-01-01T00:00:00Z
```

**EmailMessages:**
```sql
SELECT Id, ParentId, Subject, TextBody, HtmlBody,
       FromAddress, ToAddress, MessageDate, Status,
       Incoming, HasAttachment, IsExternallyVisible
FROM EmailMessage
WHERE ParentId IN (SELECT Id FROM Case WHERE CreatedDate >= 2023-01-01T00:00:00Z)
```

**CaseComments:**
```sql
SELECT Id, ParentId, CommentBody, CreatedDate,
       CreatedById, IsPublished
FROM CaseComment
WHERE ParentId IN :caseIds
```

**Contacts:**
```sql
SELECT Id, FirstName, LastName, Email, Phone, AccountId
FROM Contact
WHERE Id IN (SELECT ContactId FROM Case WHERE CreatedDate >= 2023-01-01T00:00:00Z)
```

**Accounts (for reference data):**
```sql
SELECT Id, Name, Industry, Website
FROM Account
WHERE Id IN (SELECT AccountId FROM Case WHERE CreatedDate >= 2023-01-01T00:00:00Z)
```

**Attachments (ContentVersion):**
```sql
SELECT Id, ContentDocumentId, Title, FileExtension,
       VersionData, ContentSize
FROM ContentVersion
WHERE ContentDocumentId IN (
  SELECT ContentDocumentId FROM ContentDocumentLink
  WHERE LinkedEntityId IN :caseIds
)
AND IsLatest = true
```

> [!WARNING]
> **Bulk API 2.0 does not support parent-to-child relationship subqueries.** Extract Cases first, collect the Case IDs, then query EmailMessage and CaseComment with `WHERE ParentId IN (...)` using batches of IDs. The REST API supports the subquery syntax but is not suitable for high-volume extraction.

### Extraction Constraints

- **One object at a time.** Each object requires a separate Bulk API extraction job.
- **File bodies require REST API.** Bulk API 2.0 cannot export binary file content from `ContentVersion.VersionData`. Use the REST endpoint: `GET /services/data/vXX.0/sobjects/ContentVersion/{id}/VersionData`.
- **Start from ContentDocumentLink, not ContentVersion.** Use `ContentDocumentLink.LinkedEntityId` to find which documents belong to each case, then pull the required `ContentVersion` rows. Skipping this join is where many attachment migrations break — you end up with files that have no parent case association and no way to reconstruct where they belong. ([developer.salesforce.com](https://developer.salesforce.com/blogs/2020/09/codelive-creating-finding-and-publishing-files?utm_source=openai))
- **HTML vs. Text Bodies.** Salesforce `EmailMessage` records often contain both `TextBody` and `HtmlBody`. Prefer `HtmlBody` to preserve formatting, but sanitize proprietary Salesforce CSS classes that can break eDesk's rendering.
- **Daily API limits.** Bulk API 2.0 has its own limit pool separate from REST API, but large attachment downloads consume REST API calls. Check your org's limits under Setup → Company Information.

> [!TIP]
> For orgs with fewer than 50,000 cases, Data Loader with SOQL is the fastest path. For larger volumes, use Bulk API 2.0 with custom scripting and job polling. Data Loader supports up to 5 million records via Bulk API and 150 million via Bulk API 2.0.

## Step 2: Understand eDesk's API Constraints

The eDesk REST API is the only viable import path for historical data. There is no CSV import for tickets with conversation history, and eDesk's built-in export tools are limited to metadata (extracting full threads later requires custom API work, as detailed in our [eDesk to SurveySparrow migration guide](https://clonepartner.com/blog/blog/edesk-to-surveysparrow-ticket-migration-technical-guide/)).

### Key API Details

- **Base URL:** `https://api.edesk.com/v1/`
- **Authentication:** Bearer token (generated in Settings → API Tokens)
- **Plan requirement:** API access requires at minimum the Professional plan. Some advanced features may require Enterprise. Confirm your plan's API access with eDesk before starting — discovering this on migration day stops the project. ([support.edesk.com](https://support.edesk.com/n-a-12/getting-started-with-the-edesk-api))
- **Rate limit:** 60 requests per minute per client, with a restoration rate of 2 requests per second. Exceeding the limit returns HTTP 429. ([developers.edesk.com](https://developers.edesk.com/reference/rate-limit))
- **No bulk import endpoint.** Tickets and messages must be created one at a time.
- **Sandbox/test environment:** Confirm with eDesk support whether a non-production environment is available for your plan. Test pipeline execution against a staging environment before writing to production. If no sandbox is available, use a test channel and delete test tickets before live migration.
- **`external_id` field:** Check whether eDesk's ticket creation endpoint supports an `external_id` or `reference_id` field for your plan. If available, populate it with the Salesforce `CaseNumber` — this provides idempotency on retry and eliminates the need for an internal tracking table. If not available, you must maintain a local mapping of Salesforce Case ID → eDesk Ticket ID to prevent duplicate creation on failure recovery.

### API Endpoints for Migration

| Endpoint | Method | Use |
|---|---|---|
| `POST /v1/tickets` | Create | One ticket per request. Requires `subject`, `channel_id`, `status`. |
| `POST /v1/messages` | Create | Adds a Message or Note to a ticket. Supports `type`, `direction`, `send`, `attachments`. |
| `POST /v1/tags` | Create | Creates tags for mapping Salesforce picklist values. |
| `POST /v1/sales-orders` | Create | Creates a sales order linked to a ticket. |
| `GET /v1/channels` | Read | Lists available channels — needed for `channel_id` mapping. |
| `GET /v1/users` | Read | Lists agents — needed for owner assignment mapping. |
| `GET /v1/contacts` | Read | Lists contacts — for deduplication. |

### Rate Limit Math

At 60 requests per minute, a migration that creates a ticket + 3 messages per ticket consumes **4 API calls per ticket**: 15 tickets per minute, 900 per hour, ~21,600 per day.

For 10,000 tickets with an average of 5 messages each:
- API calls: 10,000 + 50,000 = 60,000
- Time at 60 req/min: 60,000 ÷ 60 = 1,000 minutes ≈ **16.7 hours**

For 50,000 tickets: roughly **3.5 days** of continuous writes.

These estimates assume zero retries. In practice, factor 10–15% additional time for HTTP 429 backoffs, transient errors, and attachment validation failures.

> [!TIP]
> For migrations exceeding 20,000 tickets, contact eDesk support to request a temporary rate limit increase before starting.

### Channel-Specific Restrictions

Not all eDesk channels support ticket creation via API:

- **Error 4013:** Ticket creation fails on Cdiscount, TikTok Shop, Reverb, and Backmarket channels — these channel types do not support API ticket creation. ([developers.edesk.com](https://developers.edesk.com/reference/createticket?utm_source=openai))
- **Error 4023/4024:** Kaufland, Fnac, Veepee, and Mirakl channels require a `sales_order_id` on the ticket. You must create the sales order first via `POST /v1/sales-orders`. Mirakl specifically needs its **Default messages recipients** setting configured, or ticket and message creation fail silently or with a non-obvious error.

The safest approach: **create a dedicated email-type "Migration" channel** in eDesk for all imported historical tickets. This avoids marketplace-specific API restrictions and prevents polluting your live channel metrics.

## Step 3: Map and Transform the Data

### Status Mapping

Salesforce Case statuses are customizable per org. eDesk's allowed status values via API are: `Open`, `Pending`, `Closed`, `Scheduled`, `Spam`, `Archived`.

| Salesforce Case Status | eDesk Ticket Status |
|---|---|
| New | Open |
| Working | Open |
| Escalated | Open |
| Closed | Closed |
| *(custom statuses)* | Map based on `IsClosed` flag |

Most historical closed cases should map to `Closed` or `Archived`.

> [!WARNING]
> Do not map statuses by label alone. eDesk's help-center UI shows statuses like **New, To Do, Waiting, Snoozed, Resolved** — but the API accepts different values: `Open`, `Pending`, `Closed`, `Scheduled`, `Spam`, `Archived`. Mapping by UI label instead of API-tested values will cause validation errors at load time. Validate the actual API-accepted values against your tenant before full load. ([support.edesk.com](https://support.edesk.com/mailboxtickethandling/ticket-statuses-in-edesk))

### Channel Assignment

Every eDesk ticket **requires a `channel_id`**. Salesforce Cases have `Origin` (Web, Email, Phone) and potentially custom fields — not the same concept.

Your options:

1. **Create a dedicated "Migration" channel** (email type) for all imported tickets. Cleanest approach — no risk of triggering marketplace-specific behaviors.
2. **Map by Case Origin or custom field** to existing eDesk channels. Preserves channel attribution but risks marketplace API restrictions.

### Conversation Reconstruction

This is the hardest part of the migration. A single Salesforce Case may have multiple `EmailMessage` records (inbound and outbound), multiple `CaseComment` records (public and private), and `LiveChatTranscript` records. All must merge into a chronologically ordered sequence of eDesk Messages and Notes on one Ticket.

```python
def build_edesk_messages(case_id, email_messages, case_comments):
    messages = []

    for em in email_messages:
        messages.append({
            "body": em["HtmlBody"] or em["TextBody"],
            "type": "Message",
            "direction": "in" if em["Incoming"] else "out",
            "send": False,  # Historical import — do not deliver to channel
            "timestamp": em["MessageDate"],
            "from": em["FromAddress"]
        })

    for cc in case_comments:
        if cc["IsPublished"]:
            messages.append({
                "body": cc["CommentBody"],
                "type": "Message",
                "direction": "out",
                "send": False,
                "timestamp": cc["CreatedDate"],
                "from": "agent"
            })
        else:
            # Private comments become internal Notes
            messages.append({
                "body": cc["CommentBody"],
                "type": "Note",
                "timestamp": cc["CreatedDate"],
                "from": cc["CreatedById"]
            })

    messages.sort(key=lambda m: m["timestamp"])
    return messages
```

The `send=false` parameter is critical for historical imports. eDesk documents that only `send=true` queues delivery to the channel. For migrated data, `send=false` ensures no historical messages get re-sent to customers. Note that eDesk only lets you edit Notes after creation, not sent Messages — which is another reason to ensure `send=false` on all historical message imports before they are written. ([developers.edesk.com](https://developers.edesk.com/reference/createmessage?utm_source=openai))

### The SLA Timer Problem

eDesk is optimized around marketplace SLAs (e.g., Amazon's 24-hour response requirement). If imported historical tickets are interpreted as "new," eDesk will flag them as SLA violations, destroying your reporting metrics.

You must explicitly set the ticket status to `Closed` or `Archived` for historical data and ensure `created_at` and `updated_at` timestamps are hardcoded to the original Salesforce dates. Confirm with eDesk support whether the API accepts historical `updated_at` values or overwrites them with the current timestamp at creation time — this determines whether your SLA reporting on migrated data will be accurate or permanently distorted. If eDesk's standard validation strips historical timestamps, coordinate with eDesk support to bypass validation during the import window.

### Order Context Mapping

eDesk's primary value is displaying the customer's order alongside the ticket. In Salesforce, you likely stored the e-commerce order number in a custom field (e.g., `Shopify_Order_ID__c` or `Amazon_Order_Ref__c`).

During transformation, map this custom field to eDesk's `order_id` or `order_reference` field. When eDesk ingests the historical ticket, it uses this reference to query the connected marketplace integration and backfill the order data in the UI.

For channels that require `sales_order_id` (Kaufland, Fnac, Veepee, Mirakl), you must create the sales order via `POST /v1/sales-orders` before creating the ticket. The sales order endpoint requires `contact_id`, `channel_id`, a unique `seller_order_id` within the channel, currency, and order items. ([developers.edesk.com](https://developers.edesk.com/reference/createsalesorder?utm_source=openai))

### Contact Deduplication

eDesk contacts are matched by email address. Two common failure modes:

1. **Same email across multiple Salesforce Contacts (B2B shared contacts).** If two Salesforce Contacts at different Accounts share the same email address — common in B2B orgs where a contact like a purchasing department email is linked to multiple accounts — eDesk will merge them into one contact. The Account context that differentiated them in Salesforce is permanently lost. Before migration, run `SELECT Email, COUNT(Id) FROM Contact GROUP BY Email HAVING COUNT(Id) > 1` to identify shared emails. Decide in advance whether to use the canonical email as-is, append a disambiguating suffix for historical records, or encode the Account relationship in a custom field.

2. **No email on Contact.** Salesforce allows Contacts without email addresses (phone-only contacts). eDesk requires email for contact creation. For these records, generate a placeholder (e.g., `sf-contact-{ContactId}@migration.invalid`) and document the substitution.

Use the `contact` object parameter on `POST /v1/tickets` to associate a customer inline, or query `GET /v1/contacts` before creating tickets for deduplication.

### Custom Fields

eDesk supports custom fields on tickets via the `custom_fields` array. Use these to preserve Salesforce metadata that doesn't map to standard eDesk fields:

```json
{
  "subject": "Order #12345 - Damaged item",
  "channel_id": 42,
  "status": "Closed",
  "created_at": "2024-03-15T10:30:00Z",
  "custom_fields": [
    { "id": 1, "value": "SF-00045231" },
    { "id": 2, "value": "Acme Corp" },
    { "id": 3, "value": "Enterprise" }
  ],
  "contact": {
    "email": "jane@acme.com",
    "name": "Jane Smith"
  }
}
```

Recommended custom fields to create in eDesk before migration (Settings → Custom Fields):
- **Salesforce Case Number** — preserves the original identifier for cross-referencing
- **Salesforce Account Name** — captures the company context that has no native eDesk equivalent
- **Salesforce Case Origin** — preserves channel attribution if you route everything through a migration channel

Create the custom fields first to get their IDs before the migration script runs. Custom field IDs are not human-readable — fetch them via API or from the Settings UI and hardcode them into your transformation layer.

## Step 4: Build the Migration Pipeline

### Pipeline Architecture

```
Salesforce Bulk API 2.0 → CSV/JSON staging files
                              ↓
              Transformation layer (Python/Node)
                              ↓
            eDesk REST API (POST /v1/tickets + POST /v1/messages)
```

### Pipeline Steps

1. **Extract** all Cases, EmailMessages, CaseComments, Contacts, Accounts, and ContentVersions from Salesforce. Store as CSV or JSON in a staging area.
2. **Build lookup maps** — Salesforce Case ID → eDesk ticket (to be created), Salesforce Contact → eDesk contact, Salesforce User (agent) → eDesk User.
3. **Pre-create reference data** in eDesk: custom fields, tags (from Salesforce picklist values), and verify channel IDs and user IDs via the API.
4. **Create contacts and sales orders** in eDesk for channels that require order context before ticket creation.
5. **Transform** each Case into an eDesk ticket payload. Merge EmailMessages and CaseComments into a sorted message array with correct `type` (Message vs. Note) and `send=false`.
6. **Load** — For each Case: create the ticket via `POST /v1/tickets`, capture the returned ticket ID, then create each message/note via `POST /v1/messages`.
7. **Throttle** all writes to stay within 60 req/min. Use a token-bucket or leaky-bucket rate limiter.
8. **Log** every API response. Store the Salesforce Case ID → eDesk Ticket ID mapping for validation and idempotent retry.

### Handling Attachments

Salesforce handles attachments via a normalized architecture (`ContentDocument`, `ContentVersion`, `ContentDocumentLink`). After extracting `ContentVersion` files via the Salesforce REST API (`/services/data/vXX.0/sobjects/ContentVersion/{Id}/VersionData`), you have two options for getting them into eDesk:

1. **Include attachments in the message creation payload.** eDesk's `POST /v1/messages` endpoint supports an `attachments` parameter. Test with real files early — eDesk validates the payload shape and returns error `4019` for malformed attachments. Confirm the maximum attachment size and supported MIME types with eDesk support before bulk loading; these constraints are not always documented publicly and violations will fail silently or with a generic error at scale.
2. **Host files on a temporary S3 bucket.** Upload extracted files to a secure, temporary AWS S3 bucket and pass the URLs in the eDesk payload. Destroy the bucket post-migration.

Whichever approach you use, map each file back to its parent Case via `ContentDocumentLink.LinkedEntityId` and then to the specific `EmailMessage` it was attached to (use `EmailMessage.HasAttachment = true` and match on `ParentId` and timestamp proximity). Attaching files to the wrong message in a thread is a data integrity error that is difficult to detect in validation and impossible to retroactively correct in bulk.

### Error Handling

- **HTTP 429 (rate limit):** Back off and retry. eDesk does not return a `Retry-After` header — implement a fixed backoff (e.g., 30 seconds) or a sliding window based on the 2-requests-per-second restoration rate.
- **HTTP 400 (validation error):** Log the full payload and skip. Common causes: missing `channel_id`, invalid status value, or a marketplace channel that doesn't support ticket creation.
- **Error 4013:** Channel doesn't support API ticket creation (Cdiscount, TikTok Shop, Reverb, Backmarket). Switch to an email-type channel.
- **Error 4019:** Malformed attachment payload. Validate file format and size before the bulk run. Check MIME type, encoding, and payload structure against a known-good test file.
- **Error 4023/4024:** Marketplace channel requires a `sales_order_id` that wasn't provided, or Mirakl's default recipients setting is misconfigured.
- **Duplicate detection:** Implement a persistent tracking table (Salesforce Case ID → eDesk Ticket ID, migration status, timestamp). Check this table before creating any ticket. Failed payloads go to a dead-letter queue for re-processing without restarting the entire migration. If eDesk supports an `external_id` field on your plan, use it as the primary idempotency mechanism instead.

## Step 5: Validate the Migration

Validation is non-negotiable. Run these checks before going live:

- **Record counts:** Total tickets in eDesk = total Cases extracted (minus intentional exclusions). Compare by date window, channel, and open/closed state.
- **Message counts:** For a random sample of 50–100 tickets, verify message count in eDesk matches the combined EmailMessage + CaseComment count from Salesforce.
- **Chronological order:** Spot-check 20+ tickets to confirm messages appear in the correct time sequence.
- **Status accuracy:** Closed Cases should be Closed/Archived in eDesk. Open Cases should be Open.
- **Customer mapping:** Confirm tickets are associated with the correct contact email address.
- **Custom field values:** Verify that Salesforce CaseNumber, Account Name, or other preserved metadata appears in the correct fields.
- **Attachments:** Open files from migrated tickets to confirm they transferred intact.
- **Cases with no EmailMessages:** Confirm stub tickets or synthetic notes were created as intended. Verify the count matches your pre-migration audit.
- **Shared-email contact deduplication:** Spot-check tickets from contacts identified during the `GROUP BY Email HAVING COUNT > 1` query. Confirm the Account context was preserved in custom fields.

eDesk's list APIs support filters for contact, channel, status, created date, last updated date, owner, and sales order — use them to build repeatable parity checks instead of eyeballing the mailbox. ([developers.edesk.com](https://developers.edesk.com/reference/listtickets?utm_source=openai))

> [!TIP]
> Build your validation script before the migration script. Automated checks that compare source and target counts, timestamps, and field values will save hours of manual spot-checking.

## Edge Cases and Failure Modes

These are the problems that surface in production, not in planning documents:

- **HTML body encoding.** Salesforce `EmailMessage.HtmlBody` can contain inline CSS, embedded images (as `cid:` references), and non-standard HTML. eDesk's rendering may strip or misrender some of this. Test with your most complex email threads first, not your simplest. Sanitize `cid:` image references before loading — they will render as broken images in eDesk.
- **Large conversation threads.** Cases with 50+ EmailMessages require 50+ sequential API calls. At 60 req/min, a single ticket can take nearly a minute to fully reconstruct. Plan for these in your rate limiting logic and extend your total time estimate accordingly.
- **Timezone handling.** Salesforce stores all dates in UTC. eDesk's `created_at` accepts ISO 8601. Pass UTC timestamps — do not convert to local time. Timezone conversion errors will cause messages to appear out of chronological order in the ticket thread.
- **Agent mapping gaps.** If a Salesforce User who owned Cases doesn't exist in eDesk, the ticket needs to be unassigned or mapped to a catch-all agent. Build a lookup table before migration starts. Handle mismatches by assigning to a designated "migration owner" account, not by failing the ticket creation.
- **Deleted or merged Cases.** If your extract includes soft-deleted records (via `queryAll`), decide upfront whether to migrate them. eDesk has no "deleted" status — use Archived. Salesforce merged Cases retain only the winning Case — if you need the losing Case's history, it is permanently gone from Salesforce and cannot be migrated.
- **Email-to-Case threading.** Salesforce uses `MessageIdentifier` and `ThreadIdentifier` to link emails to Cases. This threading logic doesn't carry over. In eDesk, threading is implicit — all messages on a ticket are the same thread. The consequence: if a customer reply was threaded to a different Case in Salesforce (e.g., forwarded to a new Case), it will appear as an isolated message with no thread context in eDesk.
- **Notification suppression.** Creating tickets and messages in a live eDesk environment triggers auto-responders and satisfaction surveys. **Disable all outbound routing rules, auto-responders, and surveys before executing the import.** Failing to do this results in customers receiving emails from years-old support threads — satisfaction surveys for orders placed two years ago, auto-replies to conversations already resolved. This is the failure mode with the highest customer impact and the one most consistently skipped in migration planning.
- **Marketplace ticket loading order.** On channels that require sales orders (Kaufland, Fnac, Veepee, Mirakl), loading tickets before the required sales orders exist will fail with error 4023/4024. Create all sales orders first, confirm creation via `GET /v1/sales-orders`, then load tickets.
- **Status label mismatch.** Mapping eDesk statuses from UI labels instead of API-tested values will cause validation errors at load time. Test every status value against the API before bulk execution.
- **`updated_at` timestamp overwrite.** If eDesk overwrites `updated_at` with the current timestamp on ticket creation (rather than accepting a historical value), migrated tickets will all show a `last updated` date of your migration date. This corrupts SLA reporting and any queue views sorted by last activity. Confirm behavior with eDesk support before migration and document the outcome.

## Cutover Strategy

The pattern that causes the least disruption is **sample → historical backfill → delta sync → channel switch**.

1. **Sample migration (Week 1):** Migrate 50–100 representative tickets. Validate everything — data mapping, timestamps, attachments, thread order, custom fields, notification suppression.
2. **Historical backfill (Weeks 2–3):** Extract and migrate all Salesforce cases older than 30 days. This tests your pipeline at scale and moves ~90% of the data volume without impacting live operations.
3. **Delta syncs (daily):** Run daily delta syncs for cases updated in the last 24 hours to keep eDesk warm.
4. **Cutover (planned weekend):** Pause Salesforce email routing. Run the final delta sync. Update DNS and mail forwarding rules to point to eDesk. Agents log into eDesk Monday morning with full historical context and live e-commerce orders attached to their tickets.

For parallel-run approaches, see our [zero-downtime migration guide](https://clonepartner.com/blog/blog/zero-downtime-help-desk-data-migration/).

### Timeline Estimates

| Migration Size | Estimated API Write Time | Total Project Time (incl. planning, testing) |
|---|---|---|
| < 5,000 tickets | 4–8 hours | 1–2 weeks |
| 5,000–20,000 tickets | 1–3 days | 2–3 weeks |
| 20,000–50,000 tickets | 3–5 days | 3–4 weeks |
| 50,000+ tickets | 5+ days (request rate limit increase) | 4–6 weeks |

API write time assumes average 5 messages per ticket and zero rate limit increases. Attachment-heavy migrations (average file size > 1 MB per message) add 20–40% to write time due to payload size and retry overhead.

## Automations and Workflow Rebuild

Salesforce's automation layer — Assignment Rules, Escalation Rules, Flows, Process Builder — has no export or import format. These must be manually rebuilt in eDesk.

eDesk's automation tools include:
- **Smart Inbox rules** for ticket routing based on channel, tags, or keywords
- **Auto-responders** per channel
- **SLA timers** (simpler than Salesforce Entitlement Processes — no milestone types, no entitlement templates)
- **Templates and snippets** for canned responses
- **AI Agent** for automated responses

Document every active Salesforce automation before decommissioning the org. For each automation, record: the trigger condition, the action taken, the objects involved, and the business rule it enforces. Cross-reference with eDesk's available triggers and conditions. Expect gaps — eDesk's automation engine is intentionally simpler. Escalation rules, multi-level approval flows, and entitlement-based routing have no equivalents. These must either be handled manually or deprioritized for post-go-live iteration.

## When eDesk Is the Wrong Target

Do not move just because Salesforce feels heavy. If the business still needs:

- Case-linked entitlements or service contracts
- Multi-level account hierarchies
- Custom Apex behavior or complex Flow automations
- Reporting that joins support data to sales data in one relational model
- Knowledge base management at scale
- Phone cases as a primary support channel with call logging

...then eDesk is a narrower system. It is strongest when support is mostly email, marketplace, and order-driven operations across many channels. It is a weaker fit when the Case is one object inside a larger CRM process that also touches Opportunities, Contracts, or Service Agreements. ([developer.salesforce.com](https://developer.salesforce.com/docs/platform/data-models/guide/service-cloud-support-overview.html?utm_source=openai))

## What to Do Before You Start

1. **Audit your Salesforce data.** Run `SELECT COUNT() FROM Case` and `SELECT COUNT() FROM EmailMessage` to understand volume. Run `SELECT COUNT() FROM Case WHERE Id NOT IN (SELECT ParentId FROM EmailMessage)` to identify email-free cases requiring a handling decision. Run `SELECT Email, COUNT(Id) FROM Contact GROUP BY Email HAVING COUNT(Id) > 1` to identify shared-email contacts requiring a deduplication decision.
2. **Confirm your eDesk plan.** API access requires at minimum the Professional plan. Do not discover this on migration day.
3. **Confirm sandbox availability.** Ask eDesk support whether a non-production environment is available. If not, plan your test approach explicitly.
4. **Confirm `updated_at` behavior.** Ask eDesk support whether the API accepts historical `updated_at` timestamps or overwrites them. Document the answer — it affects SLA reporting.
5. **Set up a migration channel.** Create a dedicated email channel in eDesk for imported tickets.
6. **Map your agents.** List all Salesforce Users who own Cases. Match them to eDesk Users. Assign a catch-all migration owner for unmatched users.
7. **Disable notifications.** Turn off all auto-responders, routing rules, and satisfaction surveys before any migration writes.
8. **Define your cutover strategy.** Will you run both platforms in parallel? Will you freeze Salesforce and go live on eDesk on a specific date?
9. **Test with a small batch.** Migrate 50–100 tickets first. Validate everything. Then scale up.

> Migrating from Salesforce Service Cloud to eDesk? ClonePartner handles the full pipeline — extraction, transformation, API orchestration, rate limit management, and post-migration validation. Book a free 30-minute call with our engineers to review your data model and map out the approach.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I migrate Salesforce Service Cloud data to eDesk using CSV import?

No. eDesk has no CSV import for tickets with conversation history. You must use eDesk's REST API (POST /v1/tickets and POST /v1/messages) to create tickets and messages programmatically. API access requires at minimum the Professional plan.

### What is eDesk's API rate limit for migration?

eDesk allows 60 requests per minute per client, with a restoration rate of 2 requests per second. There is no bulk import endpoint — tickets and messages are created one at a time. For large migrations (20,000+ tickets), contact eDesk support to request a temporary rate limit increase.

### How long does a Salesforce Service Cloud to eDesk migration take?

API write time depends on volume: 10,000 tickets with 5 messages each takes roughly 16–17 hours at 60 req/min. Total project time including planning, script development, and validation is typically 2–4 weeks for mid-size migrations.

### Do Salesforce Case attachments migrate to eDesk?

eDesk supports attachments on message creation (not ticket creation). Extract files from Salesforce ContentVersion via REST API, then include them in the eDesk message payload. Validate the format early — eDesk returns error 4019 for malformed attachments.

### What Salesforce data cannot be migrated to eDesk?

eDesk has no equivalent for Salesforce Entitlements, Knowledge Articles, multi-level Account hierarchies, LiveChatTranscripts, or Flow/Process Builder automations. These must be rebuilt manually in eDesk or left behind.
