---
title: "Zoho Desk to Re:amaze Migration: A Technical Guide"
slug: zoho-desk-to-reamaze-migration-a-technical-guide
date: 2026-08-05
author: Wahab
categories: [Zoho Desk, "Re:amaze", Migration Guide]
excerpt: "Technical guide to migrating from Zoho Desk to Re:amaze. Covers API extraction, data model mapping, status translation, rate limits, and the edge cases that break migrations."
tldr: "Zoho Desk to Re:amaze is a medium-complexity API migration. Key risks: Zoho's daily credit limits slow extraction, Re:amaze won't accept custom timestamps on messages, and missing suppress_notifications sends live emails to customers."
canonical: https://clonepartner.com/blog/zoho-desk-to-reamaze-migration-a-technical-guide/
---

# Zoho Desk to Re:amaze Migration: A Technical Guide


Migrating from Zoho Desk to Re:amaze means translating a ticket-centric, department-scoped help desk into a conversation-led, brand-scoped support platform. This is not a CSV swap. Zoho Desk organizes data around Tickets, Threads, Contacts, Accounts, Departments, and Agents. Re:amaze organizes around Conversations, Messages, Contacts, Channels, and Staff — with a flatter schema, no dedicated Account/Organization object, and different lifecycle rules.

This guide covers every step: extraction from the Zoho Desk API, data model translation, loading into Re:amaze, and the edge cases that will break your migration if you miss them.

## Why Teams Move from Zoho Desk to Re:amaze

The structural differences between the platforms — not feature preferences — determine whether a migration is sensible:

- **E-commerce workflow integration.** Re:amaze provides native Shopify, WooCommerce, and BigCommerce sidebars that surface live order data, purchase history, and refund actions inside the conversation view without leaving the support interface. Zoho Desk's e-commerce integrations are marketplace extensions that open separate windows or require custom API work. This is an architectural difference: Re:amaze treats order context as a first-class UI element; Zoho Desk treats it as an add-on.
- **Continuous conversation model vs. discrete tickets.** Re:amaze groups all interactions from a single customer into continuous, reopenable threads. A Done conversation automatically reopens on customer reply — there is no permanently closed state. Zoho Desk treats each ticket as a discrete unit with a definite lifecycle: Open → On Hold → Escalated → Closed. For B2C support with repeat customers, the continuous model reduces duplicate ticket creation.
- **Unified inbox vs. departmental routing.** Re:amaze routes by Brand and Channel, letting agents handle email, live chat, social, and SMS in one view. Zoho Desk routes through Departments, adding routing configuration overhead for small teams handling fewer than 3–4 distinct queues.
- **Live chat architecture.** Re:amaze's chat widget includes co-browsing, automated message sequences, and an embedded FAQ — all within one product. Zoho Desk's live chat runs through Zoho SalesIQ, a separate product with its own authentication, configuration, and pricing.

**When this migration is wrong:** Do not move to Re:amaze if you need native SLA timers and escalation policies (Re:amaze has none), if your stack is deeply integrated with Zoho CRM, Zoho Books, or Zoho Analytics (those native integrations don't survive the move), if you need multi-level B2B organizational hierarchy (Re:amaze's flat contact model cannot replicate Zoho Desk's Account → Contact → Ticket structure), or if you have HIPAA or data residency requirements (Zoho Desk supports HIPAA on Enterprise with data residency controls; Re:amaze's compliance posture is materially different).

## Core Architectural Differences

These structural mismatches define the migration's complexity.

### Ticket vs. Conversation Model

Zoho Desk uses a **Ticket** as the core object. Each ticket has a discrete lifecycle: Open → On Hold → Escalated → Closed. Tickets belong to a Department and are linked to a Contact and optionally an Account. Threads (customer replies, agent replies) and Comments (internal notes) live as sub-objects under the ticket.

Re:amaze uses a **Conversation** as the core object. Conversations are continuous, reopenable threads. A Done conversation automatically reopens when the customer replies. There is no permanent "Closed" state — only Done, which is reversible.

### Departments vs. Brands and Channels

Zoho Desk routes tickets through **Departments** — each with its own agents, SLA policies, and views. A single Zoho Desk portal can have dozens of departments.

Re:amaze scopes everything by **Brand**. Each Brand has its own Channels (email, chat, social, etc.). API requests are scoped per-brand via the subdomain in the URL (`https://{brand}.reamaze.io/api/v1/`). If your Zoho Desk instance uses multiple departments, you need to decide: map each department to a Re:amaze Brand, or collapse them into channels within a single Brand.

The mapping decision is irreversible after import. Multi-Brand setups have separate agent pools and separate billing implications. If your departments share agents heavily, collapsing to channels within one Brand preserves cross-assignment without duplicating staff.

### Accounts Have No Direct Equivalent

Zoho Desk has a first-class **Account** object — company-level records that group contacts and tickets. This is central to B2B support where multiple contacts belong to one organization.

Re:amaze has no dedicated organization entity. Company data must be flattened into **Contact-level custom data attributes** or managed through tags. This is a lossy translation — account-level reporting and hierarchical ticket views are not reconstructable.

To preserve B2B relationships, map Zoho Account data to Re:amaze Contact custom data attributes. A Zoho Contact named "Jane Doe" belonging to "Acme Corp" becomes a Re:amaze Contact with attributes like `zoho_account_name: Acme Corp`. For reporting continuity, also apply consistent tags (e.g., `account:acme-corp`) to all conversations from contacts in that account.

### Threads vs. Messages

Zoho Desk stores communication as **Threads** (public replies, private replies) and **Comments** (internal notes) under each ticket. Thread types are `reply`, `comment`, and `forward`. Direction is tracked as `in` (from customer) or `out` (from agent).

Re:amaze stores all communication as **Messages** under a Conversation, with a `visibility` field: `0` for Regular (customer-visible) and `1` for Internal Note. There is no forward type. For Zoho's forward threads, use this decision rule: if the forward was sent to an external party (vendor, escalation), map it as a regular message (`visibility: 0`) with a header line like `*[Forwarded to: vendor@example.com]*`. If the forward was internal (routing to another team), map it as an internal note (`visibility: 1`). Leaving this ambiguous causes data loss discovered only post-migration.

### Custom Fields vs. Custom Data Attributes

Zoho Desk supports **typed custom fields** per module — text, picklist, date, checkbox, multi-select — with layout-level control and multiple layouts per department.

Re:amaze supports **flat key-value custom data attributes** on both conversations and contacts. They are untyped (strings only) and only one level deep. Multi-level or strongly typed field data loses structure during migration. A Zoho picklist field with value `"Priority: High"` becomes a string attribute `"priority": "High"` in Re:amaze — there is no enforcement of valid values.

## Data Model Mapping: Zoho Desk → Re:amaze

| Zoho Desk Object | Re:amaze Object | Notes |
| :--- | :--- | :--- |
| Ticket | Conversation | Status mapping required (see below) |
| Thread (reply/comment) | Message | `visibility` field controls public vs. internal |
| Contact | Contact | Direct map; supports multiple identities |
| Account (Org) | Contact custom data | Flattened to key-value pairs — lossy |
| Department | Brand or Channel | Decide on 1:1 or N:1 mapping before import |
| Agent | Staff user | Must exist before import; cannot be created via API |
| Knowledge Base Article | FAQ Article | Requires HTML sanitization |
| Tags | Tags | Direct map via `tag_list` array |
| Custom Fields | Custom Data Attributes | Typed → untyped; one level deep only |
| Attachments | Attachments (URL-based) | Must be hosted at accessible URL before import |
| SLA Policies | ❌ Not available | No equivalent; rebuild with workflow automations |
| Blueprints/Workflows | ❌ Not available | Rebuild using Re:amaze workflow automations |
| Time Tracking | ❌ Not available | Export separately for archival |
| Community/Forums | ❌ Not available | No equivalent in Re:amaze |
| Satisfaction Ratings | Satisfaction Ratings | Re:amaze has its own CSAT system |
| Forward-type Threads | Message or Internal Note | Apply decision rule: external→message, internal→note |
| Merged ticket history | ❌ Not available | No relationship structure in Re:amaze |
| Response Templates/Macros | Response Templates | Cannot be imported via API; manual recreation required |

### Status Mapping

Zoho Desk supports custom ticket statuses mapped to two underlying states: **Open** and **Closed**. Common defaults are Open, On Hold, Escalated, and Closed. To retrieve your account's full custom status registry dynamically, call `GET /api/v1/ticketStatusDefs` — this returns all status names with their underlying Open/Closed state, which lets you build the mapping table programmatically rather than hardcoding defaults.

Re:amaze has fixed conversation states accessible via the API:

| Re:amaze Status | Integer Value | Mapping from Zoho Desk |
| :--- | :--- | :--- |
| Open | 0 | Open, Escalated, any custom "Open" status |
| Responded | 1 | Tickets with a staff reply but not resolved |
| Done | 2 | Closed |
| On-Hold | 5 | On Hold (requires `hold_until` datetime) |

> [!WARNING]
> **On-Hold requires a `hold_until` datetime.** When setting status to `5` (On-Hold) via the Re:amaze API, you must also pass a `hold_until` attribute. If your Zoho Desk "On Hold" tickets don't have a scheduled follow-up date, set a synthetic future date (e.g., 30 days from migration date) or map them to Open instead.

## Step 1: Extract Data from Zoho Desk

### Authentication Setup

The Zoho Desk API uses **OAuth 2.0** with short-lived access tokens (1-hour TTL). You'll need to:

1. Register a Server-based Application in the Zoho API Console
2. Generate an authorization code via the OAuth flow
3. Exchange it for access + refresh tokens
4. Implement automatic token refresh before each extraction batch (tokens expire hourly regardless of activity)

> [!NOTE]
> **Data center routing matters.** Zoho Desk uses region-specific base URLs: `desk.zoho.com` (US), `desk.zoho.eu` (EU), `desk.zoho.com.au` (AU), `desk.zoho.in` (IN). Using the wrong DC URL returns errors or silently targets the wrong organization. Confirm your DC by checking the URL you use to log into Zoho Desk.

### API Credit Limits

Zoho Desk enforces **daily API credit limits** per organization, tracked on a 24-hour cycle that resets at midnight in your data center's timezone:

| Plan | Base Daily Credits |
| :--- | :--- |
| Free | 25,000 |
| Standard | 50,000 |
| Professional | 75,000 |
| Enterprise | 100,000 |

Additional credits are added based on licensed user count (excluding light agents). For a Professional plan with 50 users, total available credits may reach approximately 100,000 per day depending on the per-user allocation for your contract tier — verify your exact limit in Setup → API → Usage.

This matters because extracting full ticket data is **credit-intensive**. Listing tickets costs 1 credit per call (max 100 tickets per page). Each ticket's threads require a separate API call. For 50,000 tickets averaging 5 threads each, you need approximately 500 list calls (50,000 ÷ 100) plus 250,000 thread calls, totaling roughly 250,500+ credits — exceeding most plans' daily limits by a factor of 2–10x.

**Mitigation strategies:**
- Spread extraction over multiple days, chunked by date range
- Use the Data Backup feature (Setup → Data Administration → Data Backup) for bulk export including threads and attachments
- Request a full data export from Zoho support for thread-inclusive exports
- Monitor remaining credit budget via the `X-ZOHODESK-API-CREDITS-USED` response header returned on each API call

### Extraction Endpoints

```bash
# List tickets (paginated, max 100 per page, 1-based index)
GET /api/v1/tickets?from=1&limit=100&include=contacts,assignee,departments

# Get threads for a ticket (includes both replies and forwarded messages)
GET /api/v1/tickets/{ticketId}/threads?from=1&limit=100

# Get ticket comments (internal notes only — separate from threads)
GET /api/v1/tickets/{ticketId}/comments?from=1&limit=50

# List contacts
GET /api/v1/contacts?from=1&limit=100

# List accounts (organizations)
GET /api/v1/accounts?from=1&limit=100

# List departments
GET /api/v1/departments

# List agents
GET /api/v1/agents?from=1&limit=100

# List KB articles
GET /api/v1/articles?from=1&limit=100

# Get ticket attachments
GET /api/v1/tickets/{ticketId}/attachments

# Retrieve custom status definitions (build mapping table dynamically)
GET /api/v1/ticketStatusDefs
```

> [!WARNING]
> **CSV export does not include threads or comments.** Zoho Desk's module-level CSV export (Setup → Data Administration → Export) exports ticket metadata — subject, status, priority, dates — but not conversation content. For a complete migration, use the API or request a Data Backup. Do not rely on CSV export as your extraction method.

### Pagination and Extraction Strategy

Zoho Desk uses a 1-based `from` index with a maximum `limit` of 100. Do not build a direct API-to-API pipeline. Extract Zoho Desk data into a local intermediary store (PostgreSQL, MongoDB, or structured JSON files) first. This decouples extraction from transformation and avoids losing work on API timeouts or credit exhaustion mid-run.

For datasets exceeding 100,000 tickets, deep pagination becomes slow and credit-expensive. Use `sortBy=createdTime` with date-range filters (`createdTimeRange`) to chunk extraction into weekly or monthly windows rather than paginating to offset 100,000+.

Extract entities in this order to maintain relational integrity:

1. Agents
2. Departments
3. Custom status definitions (`ticketStatusDefs`)
4. Accounts
5. Contacts
6. Tickets
7. Threads & Comments (per ticket, sorted by `createdTime`)
8. Attachments (per ticket)

```python
import requests
import time

def paginate_zoho_tickets(base_url, headers):
    all_tickets = []
    from_index = 1
    while True:
        resp = requests.get(
            f"{base_url}/api/v1/tickets",
            headers=headers,
            params={"from": from_index, "limit": 100,
                    "include": "contacts,assignee,departments",
                    "sortBy": "createdTime"}
        )
        if resp.status_code == 429:
            # Zoho returns 429 on per-minute burst limit; retry after backoff
            time.sleep(60)
            continue
        if resp.status_code == 200:
            credits_used = resp.headers.get("X-ZOHODESK-API-CREDITS-USED")
            data = resp.json().get("data", [])
            if not data:
                break
            all_tickets.extend(data)
            from_index += 100
        else:
            resp.raise_for_status()
    return all_tickets
```

## Step 2: Transform Data for Re:amaze

This is where most migrations fail. The transformation layer must handle every structural mismatch between the two platforms.

### Contact Transformation

Zoho Desk Contacts map relatively cleanly to Re:amaze Contacts:

```json
{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "data": {
    "zoho_account_name": "Acme Corp",
    "zoho_contact_id": "4000000012345",
    "phone": "+1-555-0100",
    "industry": "SaaS",
    "website": "https://acme.example.com"
  }
}
```

**Contact deduplication behavior in Re:amaze:** If you POST a contact with an email address that already exists in Re:amaze, the API returns the existing contact record rather than creating a duplicate or returning a 422 error — the operation is effectively idempotent on email. However, any `data` attributes you pass will be merged into the existing contact's custom data. This means incremental migrations are safe for contact creation, but you must verify that attribute merges don't overwrite intentional values set after your initial import. For safety, create all contacts first via `POST /api/v1/contacts` and capture the returned contact IDs before importing conversations.

> [!WARNING]
> **Contact data attributes override on write via the conversation endpoint.** Setting custom data attributes via the conversation POST endpoint **completely overrides** existing attributes on that contact — it does not merge. If you're importing multiple conversations for the same contact, set contact data once via the Contacts API first, then omit `conversation [user][data]` on subsequent conversation imports to avoid wiping previously set attributes.

Zoho Desk allows contacts to have secondary email addresses. Re:amaze supports multiple **identities** per contact (email, phone, social handles) via the Contact Identities API (`POST /api/v1/contacts/{id}/identities`). Map each secondary email as a separate identity to maintain multi-channel lookup.

### Thread-to-Message Transformation

Each Zoho Desk thread becomes a Re:amaze message. The Zoho `direction` field (`in`/`out`) and thread type (`reply`, `comment`, `forward`) determine the Re:amaze `visibility` value:

```python
def transform_thread_to_message(thread):
    thread_type = thread.get("type", "reply")
    direction = thread.get("direction", "in")
    
    if thread_type == "comment":
        # Internal notes always map to internal visibility
        visibility = 1
        user_email = thread.get("author", {}).get("email", "")
    elif thread_type == "forward":
        # Apply decision rule: external forward → regular message with header
        # Internal forward → internal note
        is_external_forward = bool(thread.get("toEmailAddress", ""))
        visibility = 0 if is_external_forward else 1
        user_email = thread.get("responderId", {}).get("email", "") if isinstance(thread.get("responderId"), dict) else ""
    elif direction == "in":
        # Customer reply
        visibility = 0
        user_email = thread.get("fromEmailAddress", "")
    else:
        # Agent reply (direction == "out")
        visibility = 0
        user_email = thread.get("responderId", {}).get("email", "") if isinstance(thread.get("responderId"), dict) else ""
    
    body = thread.get("content", "")
    # Prepend original timestamp since Re:amaze doesn't accept custom created_at
    original_time = thread.get("createdTime", "")
    if original_time:
        body = f"<p><em>[Originally sent: {original_time}]</em></p>{body}"
    
    return {
        "body": body,
        "visibility": visibility,
        "user": {
            "email": user_email,
            "name": thread.get("author", {}).get("name", "")
        },
        "suppress_notifications": True,
        "suppress_autoresolve": True,
        "suppress_surveys": True
    }
```

Your transformation script must:

1. Take the Zoho Ticket description and format it as the first Message in the conversation.
2. Iterate through all Threads and Comments, sorting them strictly by `createdTime` ascending.
3. Append these as subsequent Messages in sorted order.
4. Flag internal Zoho Comments with `visibility: 1`.
5. Apply the forward-thread decision rule (external→regular message, internal→note).
6. Prepend original timestamps in message body since Re:amaze does not accept custom `created_at` on messages (see timestamp limitation below).

### Attachment Handling

Zoho Desk serves attachments through authenticated API endpoints. You cannot pass Zoho attachment URLs to Re:amaze — Re:amaze's servers cannot authenticate against Zoho's API and will be denied. Re:amaze expects attachments as **publicly accessible URLs** it fetches at creation time.

The correct workflow:

1. Download each attachment from Zoho Desk using the authenticated API.
2. Upload it to a temporary hosting location (S3 bucket, GCS, or similar with public read ACL).
3. Pass the public URL to Re:amaze using the `message [attachments]` array field for multiple files, or `message [attachment]` for a single file. Both are supported: `message [attachment]` accepts a single URL string; `message [attachments]` accepts an array of URL strings. Use `message [attachments]` when a Zoho thread contains multiple files.
4. Clean up hosted files after confirming Re:amaze has fetched them (check conversation rendering, not just API 200 response).

For large attachment volumes (10,000+ files), budget for S3 storage and data transfer costs. At $0.023/GB/month storage and $0.09/GB transfer (AWS US-East), 100GB of attachments costs roughly $11/month storage plus $9 in transfer per migration pass.

> [!WARNING]
> **Attachment size limits.** Re:amaze enforces a per-file upload limit. If a Zoho Desk ticket contains an oversized file, the Re:amaze API rejects the entire message payload — not just the attachment. Your script must check file size before upload and, for oversized files, skip the attachment and append a text note to the message body: `[Attachment not migrated: filename.ext exceeded size limit]`. Do not let oversized files silently drop entire messages.

### Inline Images and HTML Sanitization

Zoho Desk threads often contain inline images as `<img>` tags pointing to Zoho's CDN or embedded as Base64 strings. Zoho-hosted image URLs are authenticated and will return 403 errors when Re:amaze attempts to render them. Base64 payloads bloat request bodies beyond API limits.

During transformation, scan all thread HTML for `<img>` tags. Download Zoho-CDN-hosted images or decode Base64 images, upload them to your staging bucket, and rewrite `src` attributes with public URLs before pushing to Re:amaze. Re:amaze sanitizes `<script>` tags and complex `<iframe>` embeds — test transformed HTML against a small sample batch before running at scale.

### Knowledge Base Migration

Zoho Desk's Knowledge Base maps to Re:amaze's FAQ feature. Zoho Articles belong to Categories, which belong to Departments. Re:amaze Articles belong to Topics, which belong to Brands.

Extract articles via `GET /api/v1/articles` in Zoho. Map Zoho's `draft` and `published` statuses to Re:amaze's equivalents. For internal-only articles, explicitly set the `internal` boolean to `true` in the Re:amaze POST request — failing to do so exposes internal SOPs publicly. Article HTML requires the same sanitization as thread content: rewrite Zoho-hosted image URLs and strip unsupported tags.

## Step 3: Load Data into Re:amaze

### Authentication

Re:amaze uses **HTTP Basic Auth** with your login email and API token. API requests are scoped by Brand via the subdomain. Every Staff user has their own individual API token (Settings → Developer → API Token). A Staff user's API token inherits that user's permission set — if you use a restricted agent's token, you may not be able to create conversations attributed to other agents or access all channels. Use an admin-level Staff user's token for the import pipeline to avoid permission failures mid-run.

```bash
curl 'https://{brand}.reamaze.io/api/v1/conversations' \
  -u {login-email}:{api-token} \
  -H 'Accept: application/json' \
  -H 'Content-type: application/json'
```

### Rate Limits

Re:amaze rate-limits API calls **per minute per API token**. The exact per-minute limit is not publicly documented, and Re:amaze reserves the right to adjust limits per endpoint. You will receive HTTP 429 when throttled. Re:amaze does not return a `Retry-After` header on 429 responses. Implement exponential backoff starting at 10 seconds, doubling on each retry up to a maximum of 120 seconds.

In production import testing with sequential (non-concurrent) write operations against a Re:amaze Business plan, throughput degraded noticeably beyond approximately 40–50 requests per minute before 429s became frequent. High concurrency — multiple simultaneous threads hitting the same API token — risks a temporary IP block beyond rate limiting. Run your import pipeline with a single thread per API token and introduce a minimum 1.5-second sleep between requests as a baseline, adjusting based on observed 429 frequency.

### Re:amaze API Pagination (for Validation Queries)

When querying Re:amaze to validate imported data, Re:amaze uses **page-based pagination** on GET endpoints. Pass `?page=1`, `?page=2`, etc. There is no cursor. The default and maximum page size varies by endpoint but is typically 30 records per page for conversations. There is no total-count header — you must paginate until you receive an empty array to determine the full count. Factor this into your validation scripts.

### Conversation Slug Handling

Re:amaze identifies conversations by a **slug** — an alphanumeric string generated by Re:amaze at creation time (e.g., `abc123`). The slug is returned in the POST response body. You must capture this slug from the creation response to make subsequent message POST calls for that conversation.

### Import Sequence

Order matters. Follow this sequence to avoid broken references:

1. **Staff users** — Ensure all agents exist in Re:amaze before importing. Staff cannot be created via the API; they must be invited through the Re:amaze dashboard.
2. **Channels** — Configure email, chat, and social channels corresponding to your Zoho Desk departments.
3. **Contacts** — Create contacts via `POST /api/v1/contacts` with custom data attributes. Capture returned contact IDs.
4. **Conversations** — Create each conversation with its first message via `POST /api/v1/conversations`. Capture the returned `slug` from each response.
5. **Messages** — Add remaining threads as messages via `POST /api/v1/conversations/{slug}/messages`, in `createdTime` ascending order.
6. **Status updates** — Set final conversation status via `PUT /api/v1/conversations/{slug}`.
7. **Tags** — Apply tags during conversation creation via `tag_list` or via PUT.
8. **FAQ Articles** — Create via `POST /api/v1/articles` if migrating the knowledge base.

### Creating a Conversation and Capturing the Slug

```bash
curl 'https://{brand}.reamaze.io/api/v1/conversations' \
  -u {email}:{token} \
  -H 'Accept: application/json' \
  -H 'Content-type: application/json' \
  -X POST -d '{
    "conversation": {
      "subject": "Order #12345 shipping delay",
      "status": 2,
      "tag_list": ["migrated", "zoho-desk"],
      "data": {
        "zoho_ticket_id": "4000000098765",
        "original_created": "2024-03-15T10:30:00Z"
      },
      "message": {
        "body": "<p><em>[Originally sent: 2024-03-15 10:30 UTC]</em></p><p>Original ticket content here...</p>",
        "suppress_notifications": true,
        "suppress_autoresolve": true,
        "suppress_surveys": true
      },
      "user": {
        "name": "Jane Doe",
        "email": "jane@example.com"
      }
    }
  }'
```

The response body contains the conversation object including the `slug` field. Capture and store this slug:

```json
{
  "slug": "abc123xyz",
  "subject": "Order #12345 shipping delay",
  "status": 2,
  ...
}
```

The `category` field accepts values configured for your Re:amaze brand (typically `"support"`, `"billing"`, `"sales"`, or custom categories you define). Check your brand's configured categories via `GET /api/v1/conversations` and inspect the `category` values on existing conversations before hardcoding a value in your import.

### Adding Subsequent Messages

```bash
curl 'https://{brand}.reamaze.io/api/v1/conversations/{slug}/messages' \
  -u {email}:{token} \
  -H 'Accept: application/json' \
  -H 'Content-type: application/json' \
  -X POST -d '{
    "message": {
      "body": "<p><em>[Originally sent: 2024-03-15 11:45 UTC]</em></p><p>Agent reply content</p>",
      "visibility": 0,
      "user": {
        "email": "agent@company.com",
        "name": "Support Agent"
      },
      "suppress_notifications": true,
      "suppress_autoresolve": true
    }
  }'
```

> [!CAUTION]
> **Always set `suppress_notifications: true` when importing messages.** Without this flag, Re:amaze sends a live email or integration notification to the customer for every imported message. For a migration of 50,000 tickets with 5 threads each, that's 250,000 outbound emails to real customers about years-old conversations. This is not recoverable — emails are already delivered before you discover the error. Test with a single conversation batch of 5–10 records and verify zero outbound emails before running at scale.

## What You Lose in This Migration

Be explicit with stakeholders about what doesn't transfer:

- **SLA policies and timers** — Re:amaze has no built-in SLA engine. Response time targets must be managed through workflow automations or third-party tools.
- **Blueprints and approval workflows** — Zoho Desk's guided process workflows have no equivalent. Rebuild using Re:amaze's workflow automations.
- **Time tracking entries** — Export from Zoho Desk for archival; Re:amaze has no time tracking.
- **Community/forum posts** — No equivalent in Re:amaze.
- **Ticket views and saved filters** — Rebuild manually.
- **Response templates/macros** — Re:amaze has Response Templates but they cannot be imported via API. Recreate manually.
- **Original message timestamps** — Re:amaze's API does not accept a custom `created_at` for messages. Imported messages carry the timestamp of the API call, not the original Zoho Desk thread date. Mitigate by prepending the original timestamp in the message body (e.g., `*[Originally sent: 2024-03-15 10:30 UTC]*`) and storing the original `createdTime` in conversation-level custom data attributes.
- **Merged ticket history** — Merged ticket relationships in Zoho Desk have no equivalent structure in Re:amaze.
- **Multi-layout custom fields** — Zoho Desk supports multiple field layouts per department. Re:amaze's flat, untyped data attributes cannot replicate layout-level field differentiation.
- **Custom status names** — Re:amaze has four fixed states (Open, Responded, Done, On-Hold). Custom Zoho status names like "Waiting on Customer" are lost; only the underlying Open/Closed state maps.

## Edge Cases That Break Migrations

### Multi-Department Ticket Transfers

Zoho Desk tickets can be transferred between departments. The ticket retains its full thread history but changes its department association. When mapping departments to Re:amaze Brands, a transferred ticket's Brand must reflect the final department — but the thread history may include agents from the original department who don't exist in that Brand's staff pool. Audit for transferred tickets before migration and document agent cross-Brand assignments.

### Contacts with Multiple Email Addresses

Zoho Desk allows contacts to have secondary email addresses stored in the contact record. Map each secondary email as a separate identity via Re:amaze's Contact Identities API (`POST /api/v1/contacts/{id}/identities`) with `type: "email"`. Failing to migrate secondary emails breaks conversation lookup for customers who use different addresses across channels.

### Archived and Spam Tickets

Decide upfront whether to migrate archived or spam-flagged tickets. Zoho Desk exposes `isArchived` and `isSpam` boolean flags on the ticket object. Filter these out during extraction unless you have a compliance reason to include them. Importing spam tickets wastes API credits and clutters Re:amaze search results.

### Large Thread Counts

Some tickets accumulate hundreds of threads over months of back-and-forth. Re:amaze handles long conversation threads, but each thread requires a separate API call. A single ticket with 200 threads requires 200+ sequential API calls — at 1.5 seconds per call minimum, that's 5+ minutes per ticket. Identify outlier tickets (>50 threads) before migration and plan additional time for them. Consider flagging these for manual review rather than automated import.

## Migration Timeline Estimates

| Dataset Size | Estimated Duration | Primary Bottleneck |
| :--- | :--- | :--- |
| < 5,000 tickets | 3–5 days | Re:amaze write rate limits |
| 5,000–25,000 tickets | 5–10 days | Zoho Desk API credit limits + Re:amaze rate limits |
| 25,000–100,000 tickets | 10–21 days | Attachment re-hosting + multi-day API extraction |
| 100,000+ tickets | 3–6 weeks | Full pipeline: extract, stage, transform, load, validate |

These estimates assume a single engineer running a sequential pipeline. Parallelization strategies that compress timelines: use separate Re:amaze API tokens for separate Brand imports, pre-stage Zoho backups before starting the transformation phase, and parallelize attachment upload (S3 uploads) independently from conversation creation.

## Validation Checklist

After loading data, verify before going live:

- [ ] **Conversation count** — Total conversations in Re:amaze matches total tickets extracted from Zoho Desk (use Re:amaze pagination to count; there is no total-count header)
- [ ] **Message count per conversation** — Spot-check 50+ conversations to verify all threads imported in correct chronological order
- [ ] **Contact deduplication** — No duplicate contacts created; verify via `GET /api/v1/contacts?email={address}` for sample addresses
- [ ] **Attachment accessibility** — Attachments render correctly in Re:amaze conversations; check at least one conversation per file type
- [ ] **Status accuracy** — Closed Zoho tickets show as Done (2) in Re:amaze; Open tickets show as Open (0)
- [ ] **Forward thread mapping** — External forwards mapped as regular messages; internal forwards mapped as internal notes
- [ ] **Tag integrity** — Tags applied correctly and consistently; check for tag name collisions or truncation
- [ ] **Internal notes** — Zoho Desk comments and private threads mapped to `visibility: 1` messages
- [ ] **Zero outbound notifications** — Confirm no customers received emails during import (check Re:amaze email delivery logs)
- [ ] **Custom data attributes** — Original Zoho Desk IDs, account names, and metadata preserved as custom data
- [ ] **Original timestamps** — Timestamp headers present in message bodies for historical conversations
- [ ] **FAQ articles** — Knowledge base content renders correctly; internal articles have `internal: true`
- [ ] **Contact identities** — Secondary email addresses migrated as additional identities

## How to Handle the Cutover

The cutover window is when you switch from Zoho Desk to Re:amaze as your live support tool:

1. **Freeze new ticket creation in Zoho Desk** — Disable incoming email channels and web forms.
2. **Run final delta extraction** — Pull all tickets created or updated since your last full extraction, using `createdTimeRange` or `modifiedTimeRange` filters.
3. **Import delta into Re:amaze** — Load the final batch; use the same suppression flags.
4. **Enable Re:amaze channels** — Point email forwarding, chat widgets, and social integrations to Re:amaze.
5. **Verify live ticket flow** — Send test messages through every channel (email, chat, social) and confirm they appear in Re:amaze with correct routing.
6. **Keep Zoho Desk in read-only mode** — Maintain access for 30–90 days for historical reference and potential audit.

For a zero-downtime approach, see our guide on [zero-downtime help desk migrations](https://clonepartner.com/blog/blog/zero-downtime-help-desk-data-migration/).

## Self-Serve vs. Managed Migration

**Self-serve is viable when:**
- Fewer than 5,000 tickets
- Single Zoho Desk department
- Minimal attachments (under 1,000 files)
- No complex custom field structures requiring type-preservation
- Engineer available for 3–5 days of dedicated work

**A managed migration makes sense when:**
- 10,000+ tickets with full thread history
- Multi-department setup requiring Brand mapping decisions
- Large attachment volumes requiring staging infrastructure
- Original timestamp preservation is a compliance or audit requirement
- Zero tolerance for customer-facing notification leakage
- Migration timeline must be under 2 weeks

## Making the Call

A Zoho Desk to Re:amaze migration is a medium-complexity project with two primary technical constraints: Zoho Desk's credit-limited API makes extraction slow (50,000 tickets with threads requires 2–3+ days of extraction alone), and Re:amaze's refusal to accept custom `created_at` on messages means you lose chronological timestamp fidelity on imported threads unless you embed timestamps in message bodies. Everything else — contacts, tags, statuses, FAQ articles — maps with reasonable effort given the transformation rules above.

The highest operational risk is notification leakage. Set `suppress_notifications: true` on every single API write call. Run a 10-conversation pilot batch, check Re:amaze email delivery logs, and verify zero outbound emails before importing at scale. The second-highest risk is contact data attribute overwrite — set contact attributes via the Contacts API first and never pass `conversation [user][data]` on subsequent conversation imports for the same contact.

> Need help migrating from Zoho Desk to Re:amaze? Our engineers handle extraction, transformation, loading, and validation — typically in under two weeks. Book a 30-minute technical scoping call.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I migrate Zoho Desk tickets to Re:amaze with a CSV export?

No. Zoho Desk's CSV export includes ticket metadata (subject, status, priority, dates) but not thread content, comments, or attachments. You need the Zoho Desk API or a Data Backup request to extract full conversation history for import into Re:amaze.

### Does Re:amaze preserve original ticket timestamps during migration?

No. The Re:amaze API does not accept a custom created_at when creating messages. Imported messages carry the timestamp of the API call, not the original Zoho Desk thread date. Store original timestamps in custom data attributes or message body headers for reference.

### How do I prevent Re:amaze from emailing customers during migration?

Set suppress_notifications, suppress_autoresolve, and suppress_surveys to true on every POST request when creating conversations and messages. Without these flags, Re:amaze sends live notifications for every imported message.

### Can I migrate Zoho Desk Accounts to Re:amaze?

Re:amaze does not have a native Account object. Zoho Desk Accounts must be flattened and mapped to custom data attributes on the Re:amaze Contact object. Use consistent tags for account-level grouping in reporting.

### How long does a Zoho Desk to Re:amaze migration take?

For under 5,000 tickets: 3–5 days. For 5,000–25,000 tickets: 5–10 days. For 25,000–100,000 tickets: 10–21 days. Primary bottlenecks are Zoho Desk API credit limits on extraction and Re:amaze rate limits on loading. Attachment re-hosting adds significant time for large volumes.
