Zoho Desk to Re:amaze Migration: A Technical Guide
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.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
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) |
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:
- Register a Server-based Application in the Zoho API Console
- Generate an authorization code via the OAuth flow
- Exchange it for access + refresh tokens
- Implement automatic token refresh before each extraction batch (tokens expire hourly regardless of activity)
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-USEDresponse header returned on each API call
Extraction Endpoints
# 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/ticketStatusDefsCSV 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:
- Agents
- Departments
- Custom status definitions (
ticketStatusDefs) - Accounts
- Contacts
- Tickets
- Threads & Comments (per ticket, sorted by
createdTime) - Attachments (per ticket)
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_ticketsStep 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:
{
"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.
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:
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:
- Take the Zoho Ticket description and format it as the first Message in the conversation.
- Iterate through all Threads and Comments, sorting them strictly by
createdTimeascending. - Append these as subsequent Messages in sorted order.
- Flag internal Zoho Comments with
visibility: 1. - Apply the forward-thread decision rule (external→regular message, internal→note).
- Prepend original timestamps in message body since Re:amaze does not accept custom
created_aton 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:
- Download each attachment from Zoho Desk using the authenticated API.
- Upload it to a temporary hosting location (S3 bucket, GCS, or similar with public read ACL).
- Pass the public URL to Re:amaze using the
message [attachments]array field for multiple files, ormessage [attachment]for a single file. Both are supported:message [attachment]accepts a single URL string;message [attachments]accepts an array of URL strings. Usemessage [attachments]when a Zoho thread contains multiple files. - 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.
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.
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:
- 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.
- Channels — Configure email, chat, and social channels corresponding to your Zoho Desk departments.
- Contacts — Create contacts via
POST /api/v1/contactswith custom data attributes. Capture returned contact IDs. - Conversations — Create each conversation with its first message via
POST /api/v1/conversations. Capture the returnedslugfrom each response. - Messages — Add remaining threads as messages via
POST /api/v1/conversations/{slug}/messages, increatedTimeascending order. - Status updates — Set final conversation status via
PUT /api/v1/conversations/{slug}. - Tags — Apply tags during conversation creation via
tag_listor via PUT. - FAQ Articles — Create via
POST /api/v1/articlesif migrating the knowledge base.
Creating a Conversation and Capturing the Slug
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:
{
"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
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
}
}'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_atfor 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 originalcreatedTimein 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: 1messages - 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:
- Freeze new ticket creation in Zoho Desk — Disable incoming email channels and web forms.
- Run final delta extraction — Pull all tickets created or updated since your last full extraction, using
createdTimeRangeormodifiedTimeRangefilters. - Import delta into Re:amaze — Load the final batch; use the same suppression flags.
- Enable Re:amaze channels — Point email forwarding, chat widgets, and social integrations to Re:amaze.
- Verify live ticket flow — Send test messages through every channel (email, chat, social) and confirm they appear in Re:amaze with correct routing.
- 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.
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.
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.

