---
title: Desk365 to Jira Service Management Migration Guide
slug: desk365-to-jira-service-management-migration-guide
date: 2026-08-28
author: Roopendra Talekar
categories: [Jira Service Management, Migration Guide, Help Desk, Desk365]
excerpt: "Technical guide to migrating tickets, contacts, and knowledge base data from Desk365 to Jira Service Management. Covers API extraction, field mapping, ADF conversion, and common failure modes."
tldr: "Model JSM first, extract via Desk365 API with include_description=1, convert HTML to ADF, use the request API when private notes matter, and plan for Jira Cloud's three-layer rate limiting."
canonical: https://clonepartner.com/blog/desk365-to-jira-service-management-migration-guide
---

# Desk365 to Jira Service Management Migration Guide


# Desk365 to Jira Service Management Migration Guide

Migrating from Desk365 to Jira Service Management (JSM) means moving tickets, conversation history, attachments, contacts, knowledge base articles, and operational workflows between two systems with fundamentally different data models. Desk365 is a Microsoft 365-native helpdesk with a flat ticket structure. JSM sits on top of Jira's issue-tracking engine, where every ticket is an "issue" tied to a project, issue type, and workflow.

This guide covers the data objects you can extract, API mechanics on both sides, field mapping decisions, import method tradeoffs, knowledge base migration, and the failure modes you need to plan for. It assumes **Jira Service Management Cloud**.

## Why Teams Move from Desk365 to JSM

Desk365 works well as a lightweight, Teams-integrated helpdesk. Teams outgrow it when they need deeper ITSM workflows — change management, problem management, asset CMDB, or tight coupling between service requests and engineering work in Jira Software.

JSM provides native integration with Confluence for knowledge bases, Jira Software for dev escalation, and a full ITIL-aligned workflow engine. The tradeoff: JSM is significantly more complex to configure, and its pricing model charges per agent.

The migration itself is not symmetrical. Desk365 makes it relatively easy to get data out. Getting data into JSM — with correct field types, statuses, user mappings, and comment visibility — is where most teams hit friction.

## What You Can Extract from Desk365

Desk365 offers two extraction methods: **CSV export** and **REST API v3**.

The CSV export is accessible from the agent portal and supports filtered exports by date range, status, or assigned agent. It covers tickets, time entries, contacts, and companies. The API provides richer, threaded data including conversation history and knowledge base content. ([help.desk365.io](https://help.desk365.io/en/articles/export-tickets/))

> [!WARNING]
> Desk365 requires separate exports from the **All Tickets** view and the **Archived Tickets** view for complete history. Forgetting archived tickets is a common cause of incomplete migrations. ([help.desk365.io](https://help.desk365.io/en/articles/export-tickets/))

### Desk365 API v3 Basics

- **Authentication:** API key, found at Settings > Integrations > API
- **Base URL:** `https://yoursubdomain.desk365.io`
- **Rate limits:** Plan-specific — Standard: 100 calls/hour; Plus and Premium: 50 calls/minute ([help.desk365.io](https://help.desk365.io/en/articles/desk365-api/))
- **Pagination:** Controlled via `ticket_count` parameter

| Object | API Endpoint | Notes |
|---|---|---|
| Tickets | `/v3/tickets` | Set `include_description=1` — descriptions are not included by default |
| Contacts | `/v3/contacts` | Primary and secondary department associations |
| Companies | `/v3/companies` | Department structure included |
| Knowledge Base | `/v3/kb/categories`, `/v3/kb/folders`, `/v3/kb/articles` | Categories → Folders → Articles hierarchy |
| Surveys | `/v3/surveys`, `/v3/surveys/ratings` | CSAT, NPS, five-star ratings |
| Notes | Included in ticket responses | Private and public notes distinguished |

> [!NOTE]
> By default, the Desk365 "Get all tickets" endpoint does **not** include the ticket description in the response. You must explicitly set the `include_description` parameter. Skipping this leads to migrated tickets arriving in JSM with empty bodies. ([help.desk365.io](https://help.desk365.io/en/articles/desk365-api/))

Desk365 tickets ship with these default fields: **Priority, Status, Subject, Type, Group, Category, Subcategory, Source, and Contact**. The Type field defaults to four values: Question, Incident, Problem, and Request. Custom fields support Dropdown, Text Input, Checkbox, Date, Number, Paragraph Input, and multi-level (cascading) field types.

### CSAT and Survey Data

Desk365 exposes survey data via `/v3/surveys` and `/v3/surveys/ratings`. Extract this before migration — it is the only way to preserve historical satisfaction scores.

JSM does not have a native CSAT engine in the same model as Desk365. Your options for preserving this data:

- Store survey scores as **read-only custom fields** on each migrated issue (e.g., `desk365_csat_rating`, `desk365_csat_comment`)
- Export the full survey dataset as a CSV and store it in Confluence as a historical record linked from the migrated project
- If ongoing CSAT measurement is required in JSM, configure a third-party integration such as Nicereply, Surveypal, or Atlassian's built-in customer satisfaction feature (available on Premium and Enterprise plans)

Do not attempt to map Desk365 survey ratings into JSM's native satisfaction fields — the data models are not compatible and the write path for those fields is restricted.

## How Jira Service Management Accepts Data

JSM has multiple import paths, and picking the wrong one causes problems that are hard to fix after the fact.

### Relevant APIs

1. **Jira Cloud REST API** (`/rest/api/3/issue`) — creates standard Jira issues with direct control over all fields including assignee, reporter, and custom fields
2. **JSM Service Desk API** (`/rest/servicedeskapi/request`) — creates customer requests with service desk context, including `serviceDeskId`, `requestTypeId`, and request participants
3. **Jira Bulk Issue API** (`POST /rest/api/3/issue/bulk`) — creates up to 50 issues per call; unlike single-issue creation, this endpoint supports overriding system-managed fields when used through the Jira CSV/JSON import pipeline
4. **Jira CSV/JSON importers** — wizard-based bulk import, but with significant service-desk-specific limitations

For migrations requiring comment visibility control, the JSM Service Desk API is the right choice — it accepts a `public` flag on comments. The Jira Cloud REST API gives broader field control. CSV and JSON importers are simpler but make all imported comments **public** in JSM, which is a deal-breaker if Desk365 private notes must remain internal. ([support.atlassian.com](https://support.atlassian.com/jira-cloud-administration/docs/import-data-from-a-csv-file/))

### JSM Project Type Matters

JSM has two project types: **Classic** (company-managed) and **Team-managed** (formerly Next-gen). Most migration tooling — including Atlassian's own importers — works reliably only with Classic projects. If your target project is Team-managed, either convert it or create a new Classic project for the import.

### Authentication and Rate Limits

JSM Cloud requires an **API token** generated at `id.atlassian.com/manage-profile/security/api-tokens`, paired with the admin user's email for Basic Auth.

Rate limiting is the single biggest operational constraint for migration scripts. Jira Cloud enforces **three independent rate-limiting systems simultaneously**:

- **Points-based quota (per-hour):** Each API call consumes points based on complexity and data volume.
- **Burst rate limits (per-second):** Restricts requests per second per endpoint, enforced per-tenant. Uses a token bucket algorithm.
- **Per-issue write limits:** Restricts how frequently a single issue can be modified.

When you hit a limit, JSM returns a `429 Too Many Requests` response with a `Retry-After` header. Your migration script **must** implement exponential backoff and read the `X-RateLimit-Remaining` and `Retry-After` headers on every response.

> [!WARNING]
> Atlassian does not publish exact rate limit numbers for Jira Cloud. The computation logic changes continuously. Design your scripts to be adaptive, not to target a fixed threshold.

## Field Mapping: Desk365 to Jira Service Management

This is where migrations succeed or fail. Desk365 and JSM have different field semantics, and a naive 1:1 mapping loses data or creates garbage.

### Core Field Mapping

| Desk365 Field | JSM Field | Mapping Notes |
|---|---|---|
| Subject | Summary | Direct map |
| Description | Description | Desk365 returns HTML/plain text; JSM v3 API requires **Atlassian Document Format (ADF)** |
| Status | Workflow Status | Must pre-create statuses in JSM workflow; JSM will not create missing statuses during import |
| Priority | Priority | Map Desk365 values (Low, Medium, High, Urgent) to JSM priority scheme |
| Type (Question, Incident, Problem, Request) | Issue Type + Request Type | Each Desk365 type maps to both a JSM Issue Type and a Request Type |
| Group | Component or Queue | No direct equivalent — use Components, Queues, or a custom field |
| Category / Subcategory | Labels or Custom Field | JSM has no native category hierarchy; use labels or a cascading select custom field |
| Assigned Agent | Assignee | Agent must exist in JSM as a licensed user; match by email to `accountId` |
| Contact (requester) | Reporter / Customer | Must be a JSM customer; create via API if missing |
| Company | Organization | Works for customer grouping; breaks when the Desk365 company also drives routing or asset ownership |
| Department | Custom field, Organization, or Service Project | Desk365 departments control visibility; requires access-model redesign, not a field copy |
| Private Notes | Internal Comments | Use `visibility` property or `public: false` on the request comment API |
| Public Notes | Public Comments | Standard comment creation |
| Created Date | Created | Standard API sets creation date to the API call time; use bulk import to preserve historical dates |
| Custom Fields | Custom Fields (`customfield_XXXXX`) | Map by type; dropdown fields need option IDs, not display values |
| CSAT Score | Custom Field (read-only) | No native equivalent; store as `desk365_csat_rating` custom field |

> [!WARNING]
> **The Request Type Trap:** Importing Desk365 tickets as standard Jira issues without assigning a `Customer Request Type` value means those tickets will not appear in the customer portal, and end-users will not receive email notifications about updates.

### The Description Format Problem

The Jira Cloud v3 REST API requires description and multi-line text fields in **Atlassian Document Format (ADF)**, a JSON-based document structure. Desk365 exports descriptions as plain text or HTML.

You need a conversion layer. A minimal ADF wrapper for plain text:

```json
{
  "type": "doc",
  "version": 1,
  "content": [
    {
      "type": "paragraph",
      "content": [
        {
          "type": "text",
          "text": "Your ticket description here"
        }
      ]
    }
  ]
}
```

For HTML content with formatting, you need to parse the HTML and build corresponding ADF nodes (headings, lists, code blocks, etc.). Two maintained options:

- **`@atlaskit/adf-utils`** — Atlassian-maintained npm package for building and validating ADF documents. Install via `npm install @atlaskit/adf-utils`. Provides builders, validators, and traversal utilities.
- **`adf-builder`** — community-maintained npm package (search npm registry for current version and maintenance status before using in production).

Do not skip ADF conversion — raw HTML dumped into an ADF field renders as literal markup text in the portal. Test your converter against real ticket data, not synthetic examples, before running at scale.

### Department-to-JSM Mapping: Decision Table

Desk365 departments are nested inside companies and control ticket-form, knowledge base, and announcement visibility. JSM has no direct equivalent. The right mapping depends on what the department field actually does in your Desk365 environment:

| Desk365 Department Usage | Recommended JSM Approach | Rationale |
|---|---|---|
| Purely organizational metadata (reporting only) | Custom field (`cf_department`) | Lowest effort; preserves data without restructuring |
| Controls ticket routing to different agent groups | Component + Queue configuration | Maps department-based routing to JSM queue filters |
| Controls visibility (customers see only their department's tickets) | Separate Service Projects per department | JSM project-level permissions are the correct isolation boundary |
| Drives both routing and visibility | Separate Service Projects + Components within each | Cleanest model; higher configuration overhead |
| Mixed usage across different companies | Audit per company first; apply rules above per segment | No single answer; avoid forcing a uniform mapping |

> [!WARNING]
> **Desk365 company and department managers** can see tickets beyond the ones they created, scoped by company or department. JSM organizations provide grouped customer access but use a different permission model. If manager-level ticket visibility is a business requirement, map this to **Organization Managers** in JSM (available via the `PUT /rest/servicedeskapi/organization/{organizationId}/user` endpoint) or to project roles. Do not treat this as a field copy — it is an access model design decision. ([help.desk365.io](https://help.desk365.io/en/articles/company-and-department-managers/))

### Desk365 Companies → JSM Organizations

**Desk365 companies** carry domains, contacts, assets, and department structure. **JSM organizations** are groups of customers shared across service projects. Create JSM organizations via:

```bash
curl -u admin@example.com:API_TOKEN \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp"}' \
  https://your-domain.atlassian.net/rest/servicedeskapi/organization
```

Then add customers to the organization:

```bash
curl -u admin@example.com:API_TOKEN \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"usernames": ["user@acme.com"]}' \
  https://your-domain.atlassian.net/rest/servicedeskapi/organization/{organizationId}/user
```

The company→organization mapping works for customer grouping but breaks when the Desk365 company record also drives routing, department logic, or asset ownership. Audit your company records before assuming a direct copy.

## CSV vs API: Choosing Your Import Method

Atlassian recommends CSV as the default import format for most tools. That guidance is too broad for JSM migrations from Desk365, because imported CSV/JSON comments become public, request-type behavior matters, and Teams/portal workflows add constraints that basic import docs do not address. ([support.atlassian.com](https://support.atlassian.com/jira-cloud-administration/docs/migrate-from-other-issue-trackers/))

### Use CSV import when the migration is simple

- You are loading ticket shells or history where all comments are safe to expose publicly
- You can prebuild the target project, workflow, statuses, and request types before import
- Attachment files are accessible via HTTP/HTTPS URLs that Jira Cloud can reach
- You do not need to control public vs internal comment visibility

### Use API or scripted migration when fidelity matters

- You must control **public vs internal** comment visibility
- You need request-specific semantics such as `serviceDeskId`, `requestTypeId`, or request participants
- You need to migrate knowledge base, assets, customer details, or organization structures alongside tickets
- You need phased backfill plus a short delta sync so the source system can keep running until cutover
- You need to rebuild Teams intake, portal forms, or access rules

> [!TIP]
> JSON import is not a middle ground. Atlassian recommends splitting large JSON imports to around 1,500 work items per file, and comments imported through JSON also become public in JSM. For Desk365-to-JSM work, JSON is a niche fallback, not the default path. ([support.atlassian.com](https://support.atlassian.com/jira-cloud-administration/docs/import-data-from-json/))

### Preserving Historical Timestamps

When creating issues via `POST /rest/api/3/issue`, JSM sets the `Created` date to the moment of the API call. To preserve historical creation dates, you have two options:

**Option 1: Jira CSV/JSON bulk import pipeline** — The Jira importer (accessed via Project Settings > Import) can override `Created` and `Updated` system fields when the import file includes them in the correct column format. This is the most reliable method for timestamp preservation at scale.

**Option 2: Description prepend fallback** — For API-driven migrations where bulk import is not used, prepend the original creation date to the issue description:

```
[Originally created: 2022-11-14 09:32 UTC by jane.smith@acme.com]

<original ticket body>
```

This preserves timestamp visibility without requiring bulk import infrastructure, at the cost of polluting the description field.

The Jira bulk create endpoint (`POST /rest/api/3/issue/bulk`) accepts up to 50 issues per request and shares the same field constraints as single-issue creation — it does not bypass timestamp restrictions. Timestamp override requires the importer pipeline specifically.

## Step-by-Step Migration Runbook

### Step 1: Audit Your Desk365 Data

Before writing any code, inventory everything:

- Total ticket count (active and archived) and date range
- All custom fields in use and their types
- Knowledge base article count, category/folder structure
- Contact and company count, department structure and department manager assignments
- Attachment volume — this drives timeline more than ticket count
- Inbound channels (Teams, email, web forms)
- Custom statuses, groups, categories, subcategories
- SLA configurations and automations
- Survey/CSAT data volume and whether historical scores are needed for compliance

Export a sample CSV and pull a batch via the API to surface unexpected nulls and bad field values before touching production.

### Step 2: Model the JSM Target First

Build the target service project before you move history. This is not optional.

- Create a **Classic** project type
- Define all issue types matching your Desk365 ticket types
- Build the workflow with all needed statuses (JSM will not create missing statuses during import)
- Create custom fields matching Desk365 custom fields (`customfield_XXXXX` identifiers)
- Set up the priority scheme
- Configure Request Types — map Desk365 types to JSM Request Types, and make sure the `Customer Request Type` field is populated for portal visibility

For identity management: if your agents authenticate via Microsoft 365, deploy **Atlassian Guard** (formerly Atlassian Access) and configure user provisioning from Microsoft Entra ID. Force a sync before migrating tickets.

For external customers, create JSM customer accounts via the API before migrating tickets — customer accounts must exist before issues that reference them can be created:

```bash
curl -u admin@example.com:API_TOKEN \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "displayName": "Jane Customer",
    "notify": false
  }' \
  https://your-domain.atlassian.net/rest/servicedeskapi/customer
```

Set `"notify": false` during bulk customer creation to suppress welcome emails to thousands of users simultaneously.

### Step 3: Extract and Normalize Source Data

Use the Desk365 API v3 for extraction:

```bash
curl -X GET "https://yoursubdomain.desk365.io/v3/tickets?include_description=1&ticket_count=100&page=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Paginate through all tickets. For each ticket, also pull:
- Replies and notes (public and private)
- Attachments — download and store locally, since Desk365 URLs may be behind auth or short-lived
- Contact details for reporter mapping

Desk365 API filters use arrays of string values, and `--` represents an unassigned value. Use UTF-8 URL encoding for filter parameters. ([help.desk365.io](https://help.desk365.io/en/articles/desk365-api/))

**Delta sync method:** For the cutover delta sync (Step 7), use the Desk365 API `updated_since` filter parameter to pull only tickets modified after your initial extraction timestamp. The parameter accepts an ISO 8601 date string. Record the exact UTC timestamp when your initial extraction completes — this becomes the `updated_since` value for the delta run. If you miss the sync window or the delta takes longer than expected, run a second delta using the timestamp from when the first delta started.

Desk365 also supports **webhooks** for real-time ticket event notifications. For migrations where the cutover window is under 4 hours, polling with `updated_since` is simpler. For longer parallel-run periods (days to weeks), configure Desk365 webhooks to write ticket update events to a queue, then replay that queue during cutover. Webhook events are not a substitute for a full extraction — use them only for the delta.

> [!TIP]
> Throttle extraction to your plan's limits. Standard plans allow 100 calls/hour; Plus and Premium allow 50 calls/minute. Build retries and backoff into the extraction script from day one. ([help.desk365.io](https://help.desk365.io/en/articles/desk365-api/))

### Step 4: Transform and Map Data

This is the bulk of the engineering work:

- Convert descriptions from HTML/text to ADF
- Map Desk365 statuses to JSM workflow statuses
- Map Desk365 agents to JSM user `accountId` values (build a lookup table by email)
- Map Desk365 contacts to JSM customers
- Map custom fields by type (dropdowns need option IDs, not display values)
- Distinguish private notes from public comments
- Preserve original Desk365 ticket numbers in a custom field for cross-reference

```yaml
desk365:
  company_name: Acme
  group: IT
  category: Access
  sub_category: VPN
  cf_department: Finance
  ticket_number: 48219

jsm:
  organization: Acme
  request_type: Access Request
  component: VPN
  labels:
    - desk365-import
  custom_fields:
    source_ticket_number: 48219
    source_department: Finance
```

That pattern keeps the original Desk365 identity visible inside JSM even when you have to remodel the operational meaning of the data.

### Step 5: Load Data in Dependency Order

A reliable load sequence: **customers/organizations → tickets → comments → attachments → request participants → knowledge base → assets**.

For each ticket:

1. Create the issue with core fields, including the `Customer Request Type` field
2. Add comments with correct public/private visibility via the request comment API
3. Upload attachments via the attachment endpoint with `X-Atlassian-Token: no-check` header
4. Transition the issue to the correct status via the transition API

```bash
curl -D- -u admin@example.com:API_TOKEN \
  -X POST \
  -H "X-Atlassian-Token: no-check" \
  -F "file=@/path/to/local/file.pdf" \
  https://your-domain.atlassian.net/rest/api/3/issue/ITSM-142/attachments
```

For comments, use the JSM request comment endpoint to control visibility:

```bash
curl -u admin@example.com:API_TOKEN \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "body": "This is an internal note.",
    "public": false
  }' \
  https://your-domain.atlassian.net/rest/servicedeskapi/request/{issueIdOrKey}/comment
```

> [!NOTE]
> When creating issues via the standard `POST /rest/api/3/issue` endpoint, JSM sets the `Created` date to the moment the API call is made. To preserve historical timestamps, use the Jira importer pipeline (Project Settings > Import), which allows overriding system fields like `Created`, `Updated`, and `Reporter`. If using the standard API, prepend the original creation date to the issue description as a fallback.

For status transitions: JSM enforces workflow transition rules. You cannot set a ticket directly to "Resolved" if the workflow does not allow a transition from the initial status. Either use the transition API to walk issues through valid paths, or temporarily simplify the workflow during import. The bulk import pipeline can bypass transition rules; the standard REST API cannot.

### Step 6: Migrate Knowledge Base Articles

Desk365 knowledge base articles follow a **Category → Folder → Article** hierarchy. JSM uses Confluence, which has a **Space → Page** hierarchy. This is a separate migration phase — do not combine it with ticket migration.

**Hierarchy mapping:**

| Desk365 Structure | Confluence Structure |
|---|---|
| KB Category | Confluence Space (or top-level page in an existing Space) |
| KB Folder | Parent Page under the Space/Category page |
| KB Article | Child Page under the Folder parent page |

**Step-by-step:**

1. Extract all categories, folders, and articles via the Desk365 API (`/v3/kb/categories`, `/v3/kb/folders`, `/v3/kb/articles`)
2. Create the Confluence Space (or identify the target Space if using an existing one)
3. Create parent pages for each Desk365 category, then child pages for each folder
4. For each article, convert the HTML body content to **Confluence storage format** — an XHTML-based XML format that Confluence uses internally
5. Create pages via `POST /wiki/rest/api/content` with the converted content

**Confluence storage format** is distinct from both ADF and standard HTML. A minimal page creation payload:

```json
{
  "type": "page",
  "title": "Article Title",
  "space": { "key": "SUPPORT" },
  "ancestors": [{ "id": "123456" }],
  "body": {
    "storage": {
      "value": "<p>Article body in Confluence storage format XHTML.</p>",
      "representation": "storage"
    }
  }
}
```

For articles with complex HTML (tables, code blocks, images, embedded videos), you need an HTML-to-storage-format conversion step. The Confluence REST API provides a conversion endpoint: `POST /wiki/rest/api/contentbody/convert/storage` with the body `{"value": "<your HTML>", "representation": "wiki"}` — this converts wiki markup to storage format, but HTML requires preprocessing first.

For images and file attachments within articles: download them from Desk365, upload to Confluence as page attachments via `POST /wiki/rest/api/content/{pageId}/child/attachment`, then rewrite the image src URLs in the article body to point to the Confluence attachment URLs before creating the page.

After creation, link the Confluence Space to your JSM service project under Project Settings > Knowledge Base. This makes articles searchable from the JSM customer portal.

### Step 7: Validate

Do not stop at record counts. Run these checks:

- Total ticket count matches (source vs. target, split by active and archived)
- Random sample of 50+ tickets checked for field accuracy
- All comments present, in correct order, with correct public/private visibility
- Attachments downloadable
- Status distribution matches source
- Custom field values spot-checked
- Knowledge base articles render correctly in Confluence and are surfaced in the JSM portal
- CSAT custom fields populated on surveyed tickets

```jql
project = SUPPORT AND labels = desk365-import
project = SUPPORT AND statusCategory != Done
```

Also compare volumes by status, month, and company/organization to catch bad status mapping and incomplete batches.

## Recreating Microsoft Teams Workflows

Desk365's primary selling point is deep Teams integration. To replicate this in JSM, configure **Atlassian Assist** (formerly Halp, now natively integrated into JSM):

1. Install the Jira Service Management app in your Microsoft Teams tenant
2. Configure **Request Channels** — map specific Teams channels to specific JSM Request Types
3. Train users on the new behavior: Assist requires an emoji reaction (🎫) or direct bot interaction to create tickets

Key constraints:

- A Jira site can connect to only **one** Microsoft Teams tenant at a time ([support.atlassian.com](https://support.atlassian.com/jira-service-management-cloud/docs/receive-requests-in-slack-or-microsoft-teams/))
- Request types with unsupported required fields will block chat intake — design your request types with this in mind
- Each service project gets one agent channel in Teams

> [!NOTE]
> **Behavioral difference:** Desk365 often creates tickets from every message in a designated channel. Atlassian Assist is more deliberate, requiring an emoji reaction or bot interaction to prevent channel chatter from creating false-positive tickets.

## Common Failure Modes

**Missing descriptions:** Forgetting `include_description=1` on the Desk365 API. Tickets arrive in JSM with empty bodies.

**Missing archived tickets:** Exporting only the standard ticket view and forgetting that archived tickets require a separate export. ([help.desk365.io](https://help.desk365.io/en/articles/export-tickets/))

**Private notes exposed:** Importing Desk365 history through Jira CSV or JSON importers makes all comments public. Use the JSM request comment API with `"public": false` when private note visibility matters. ([support.atlassian.com](https://support.atlassian.com/jira-cloud-administration/docs/import-data-from-a-csv-file/))

**ADF conversion errors:** Raw HTML in JSM description fields renders as unreadable markup. Test your converter against real ticket data using `@atlaskit/adf-utils` validation before running at scale.

**User mismatches:** Desk365 agents identified by name/email will not match JSM's `accountId` system. Build a lookup table first. If an agent does not have a JSM license, their tickets land as unassigned.

**Status transition failures:** JSM enforces workflow transitions and will not create missing statuses during CSV or JSON import. Statuses must already exist in the workflow before any import begins.

**Request Type omission:** Tickets imported without a `Customer Request Type` value will not appear in the customer portal, and end-users will not receive email notifications.

**Rate limit cascades:** Hitting Jira's burst limits mid-migration and not handling `429` responses correctly. The script retries immediately, gets throttled harder, and the migration grinds to a halt. Always read `Retry-After` before retrying.

**Attachment URL failures:** Desk365 attachment URLs may be behind auth or short-lived. Stage attachments locally before import — do not assume Jira Cloud can reach Desk365 attachment URLs directly.

**Delta sync window missed:** If the delta sync takes longer than expected and new tickets are created during the delta run, you need a second pass. Record the UTC timestamp when each extraction starts, not when it ends, to ensure complete coverage.

**KB article image links broken:** Desk365 KB article images are hosted on Desk365 infrastructure. After migrating article HTML to Confluence without re-hosting the images, they will break when Desk365 is decommissioned. Download, re-upload to Confluence, and rewrite URLs before cutover.

**SLA data loss:** Historical SLA data cannot be cleanly migrated into JSM's native SLA engine, which calculates SLAs dynamically based on JQL calendars and issue events. Extract Desk365 SLA metrics and map them to read-only custom fields in JSM for compliance reference.

**Customer creation order violation:** Creating issues before customer accounts exist causes reporter/requester fields to fail silently or default to the API user. Always create customers first.

**Welcome email flood:** Creating JSM customer accounts via API with `"notify": true` sends a welcome email to every imported customer. Set `"notify": false` for bulk customer creation.

## Cutover Strategy

A helpdesk migration is not a one-shot event. IT teams cannot stop supporting users during the transition.

1. **Initial sync:** Migrate historical data (closed tickets, old attachments, inactive users) over several days.
2. **Validation:** Verify data integrity, ADF formatting, comment visibility, and attachment accessibility.
3. **Cutover window:** Choose a low-traffic period.
4. **Delta sync:** Pull only tickets created or updated since the initial sync using the `updated_since` filter on the Desk365 API. Record the start timestamp of the initial sync as your delta boundary.
5. **Channel cutover:** Update email forwarding rules to point to JSM's email handler. Remove the Desk365 bot from Teams and deploy Atlassian Assist to all relevant channels.
6. **Make Desk365 read-only:** Only after validation passes on the delta sync.

A pragmatic approach to historical data volume:

- Migrate **open and pending tickets** in full — all comments, attachments, custom fields
- Migrate **closed tickets from the last 12–24 months** with reduced detail — subject, description, status, dates
- Archive **older closed tickets** as CSV/PDF exports stored in a shared drive or Confluence space

This cuts migration volume significantly while keeping actionable history accessible.

## When to Use a Tool vs Custom Scripts

**Third-party tools** like Help Desk Migration (Relokia) offer wizard-based field mapping and basic transformations. Good for straightforward migrations with standard fields and no private note sensitivity.

**Custom scripts** are necessary when:

- You must control public/private comment visibility
- You have deeply nested custom fields or multi-level Desk365 fields
- You need precise control over timestamps and internal note handling
- Your ticket volume exceeds 50,000+ and you need optimized batching
- You need to maintain original ticket IDs as references
- You are migrating KB articles with embedded images that need URL rewriting
- You have department-based access rules that require project-level restructuring in JSM

The tradeoff is engineering time. A tool gets you 80% there in a day. Custom scripts get you to 100% but take one to two weeks of development and testing.

## The Hard Parts Are Not the Records

The mistake teams make is assuming this migration is just a record export. The actual hard parts are comment visibility, access-model redesign, Teams channel behavior, ADF conversion, Confluence storage format conversion for KB articles, delta sync timing, and cutover control.

The extraction side from Desk365 is straightforward. The JSM side — particularly the interaction between request types, customer portal visibility, comment privacy, and organizational structure — is where careful engineering matters. Plan for the access model before writing a single migration script. Every other problem is solvable mid-flight; a misdesigned permission model requires rebuilding the target project.

> Need help migrating from Desk365 to Jira Service Management? Our engineers handle extraction, field mapping, ADF conversion, comment visibility, and cutover control. Book a 30-minute call to scope your migration.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Will Desk365 private notes stay private after migrating to Jira Service Management?

Not if you use Jira's CSV or JSON importers — imported comments become public in JSM. To preserve internal/public visibility, use the JSM request comment API, which accepts a public true/false flag on each comment. ([support.atlassian.com](https://support.atlassian.com/jira-cloud-administration/docs/import-data-from-a-csv-file/))

### How do I handle Desk365 ticket descriptions in JSM?

JSM's v3 REST API requires descriptions in Atlassian Document Format (ADF), a JSON structure. Desk365 exports descriptions as HTML or plain text. You need a conversion layer — dumping raw HTML into an ADF field renders as literal markup text in the portal.

### Do I need to export archived tickets from Desk365 separately?

Yes. Desk365 requires separate exports from the All Tickets view and the Archived Tickets view to get complete history. Missing the archived export is a common cause of incomplete migrations. ([help.desk365.io](https://help.desk365.io/en/articles/export-tickets/))

### Can I keep Microsoft Teams ticketing workflows after moving to JSM?

Yes, but you need to rebuild them. JSM provides Teams integration through Atlassian Assist. Key constraints: a Jira site can connect to only one Teams tenant, request types with unsupported required fields block chat intake, and ticket creation requires an emoji reaction or bot interaction rather than auto-capturing every message.

### How long does a Desk365 to Jira Service Management migration take?

It depends on ticket volume, attachment size, and custom field complexity. A straightforward migration under 10,000 tickets can complete in 1–3 days. Complex migrations with deep custom fields, knowledge base content, private note handling, and full conversation history typically take 1–2 weeks of engineering work.
