---
title: How Whittard of Chelsea Moved 3.9M Records from Three Freshworks Products into One Gorgias Workspace
slug: whittard-of-chelsea-freshworks-to-gorgias-case-study
date: 2026-09-29
author: Abdul Aleem
categories: [Case Studies]
excerpt: "Whittard of Chelsea consolidated Freshdesk, Freshchat and Freshcaller into a single Gorgias workspace with ClonePartner. Over a million of those tickets were invisible to Freshdesk's API, because Freshdesk had auto-archived them and archived tickets cannot be listed or searched."
tldr: "Whittard of Chelsea consolidated Freshdesk, Freshchat and Freshcaller into one Gorgias workspace — 3.9 million records, most hidden by auto-archiving."
canonical: https://clonepartner.com/blog/whittard-of-chelsea-freshworks-to-gorgias-case-study
---

# How Whittard of Chelsea Moved 3.9M Records from Three Freshworks Products into One Gorgias Workspace


## TL;DR

**Customer:** Whittard of Chelsea (tea, coffee and hot chocolate retailer, London. Founded 1886, now selling in 26 countries.)

**The Move:** Freshdesk, Freshchat and Freshcaller into one Gorgias workspace. Three separate Freshworks products, three separate migrations, one destination.

**The Scope:** Roughly 3.9 million records. Over 1 million Freshdesk tickets with more than 700,000 replies and over 500,000 contacts; roughly 73,000 chat conversations carrying over 1.3 million messages; close to 117,000 phone calls. A decade of email history, and seven years of chat and phone.

**The Roadblock:** Freshdesk auto-archives old tickets, and archived tickets cannot be listed, searched or filtered through the API. A normal full-history pull returned fewer than 6,000 tickets. The real archive held over a million.

**The Outcome:** Email, live chat and telephony history that used to live in three disconnected products now sits in one Gorgias workspace, on one customer timeline, every record tagged by the system it came from.

---

## By the Numbers

- **Total records migrated:** roughly 3.9 million
- **Chat messages:** over 1.3 million
- **Freshdesk tickets:** over 1 million
- **Ticket replies:** over 700,000
- **Contacts:** over 500,000
- **Phone calls:** close to 117,000
- **Chat conversations:** roughly 73,000
- **Knowledge base articles:** over 400
- **Canned responses:** close to 300
- **Ticket types mapped:** 10
- **Data span:** a decade of email history, seven years of chat and phone

---

## The Challenge: Three Products, One Customer History

Whittard of Chelsea has been selling tea and coffee since 1886, when Walter Whittard opened a shop on Fleet Street and blended everything on site. The business now trades in 26 countries, and its customer service runs on the volume you would expect of a retailer that size across peak gifting seasons.

That service had grown across three separate Freshworks products. Email support ran in Freshdesk. Live chat ran in Freshchat. Phone ran in Freshcaller. Each held part of the same customer's history, and none of them held the whole of it. Moving to Gorgias meant collapsing all three into a single workspace without losing the decade of context that made the history worth keeping.

Three things made that harder than a standard [Freshdesk to Gorgias migration](https://clonepartner.com/blog/blog/how-to-migrate-from-freshdesk-to-gorgias-complete-technical-guide):

- **Most of the history was invisible to the API.** Freshdesk automatically archives old tickets. Archived tickets do not appear in list calls, cannot be filtered for, and do not come back from ticket search. A standard full-history pull — asking Freshdesk for everything updated since the year 2000 — finished cleanly and returned fewer than 6,000 tickets. It was not an error and nothing failed. It was simply everything Freshdesk was prepared to admit existed.
- **Three products meant three data models.** A Freshdesk ticket, a Freshchat conversation and a Freshcaller call are not the same object. They have different identifiers, different status vocabularies, different notions of who the participants are, and in the case of calls, no text content at all.
- **The destination had hard limits that failed loudly in some places and silently in others.** Gorgias caps an individual message at 1 MB. It rejects some contact lookups that seem reasonable. And in at least one case it answers a malformed request with a bare HTTP 500 and no explanation.

---

## Recovering a Million Archived Tickets

Establishing the true scope came first, and it came from probing rather than from documentation. Individual ticket IDs chosen well above the visible range returned 404 on a normal GET and returned the ticket when the request declared `is_archived=true`. So the records existed and were individually retrievable. They just could not be discovered.

The route around it was an account-level export. Whittard generated a CSV of ticket IDs from Freshdesk's own export tooling — a file of roughly 400 MB listing **over a million unique ticket IDs**, running from the first ticket the account ever raised through to its most recent. That file became the work list. Rather than asking Freshdesk what tickets existed, the pull walked the CSV and fetched each ticket by ID with the archive flag set.

At that volume the retrieval itself becomes the engineering problem. The pull ran at 15 concurrent fetches against a ceiling of 40 requests per 10 seconds, with per-ID error isolation so one bad record could not end a run that had hours of work behind it. Ticket IDs already in the database were preloaded into a set and skipped, so everything the original list pull had already retrieved was left untouched and re-runs cost nothing.

One efficiency fix mattered more than it sounds. The comment pull had a flag to skip re-fetching the ticket body, because the body was already stored. The condition implementing it was wrong, and it re-fetched every ticket anyway — consuming API quota that the archive pull needed and stalling runs. Correcting a single boolean returned the quota to the work that needed it.

---

## The ClonePartner Solution: Three Migrations, One Destination

Each product was run as its own migration job against the same Gorgias account, so a failure in one could never damage another, and each could be paced independently against its own source rate limits.

**Email.** Freshdesk tickets were recreated in Gorgias with their full reply history. Status was mapped from Freshdesk's numeric codes to Gorgias open and closed states, with resolved and closed both landing as closed. Support groups became tags — Customer Service, Corporate Sales, B2B/Wholesale, Automated — as did priority and each of the ten ticket types, so the routing knowledge encoded in the old helpdesk survived as something searchable in the new one. Original creation and resolution timestamps were written into dedicated custom fields, because a migrated ticket that reports today's date as its creation date is worse than useless for a retailer looking at seasonal patterns.

**Chat.** Freshchat conversations became Gorgias tickets, with each message classified on the way in: customer, agent and bot messages arrived as public replies, system messages as internal notes. Bot-handled conversations were preserved rather than discarded — Freshworks' assistant had resolved a meaningful share of chats, and dropping them would have left gaps in the customer timeline with no explanation.

**Phone.** Freshcaller calls have no message body, so the migration had to synthesise the record: direction tagged inbound or outbound, outcome tagged answered, missed, voicemail or abandoned, and call metrics attached as a note. Abandoned calls and calls with no audio were migrated deliberately rather than skipped — a customer who called three times and gave up twice is telling you something, and that signal only exists if the abandoned calls are there. Phone numbers were normalised against the UK region so the same caller resolved to the same Gorgias contact across the whole history.

Every record carries a tag naming its source system, and each migration wrote to its own Gorgias view. A year from now, anyone can still tell which records came from where.

---

## What Broke, and Why

Four of the failures found during this migration were silent. They reported success while losing data, which is the category worth documenting.

**Attachments failed 100% and reported nothing.** The function that attached chat files was called without the channel and agent arguments Gorgias requires, so every attachment note came back rejected for a missing required field. Over three thousand files failed and none succeeded. The type checker had been reporting the argument-count error the whole time; it was invisible because the build aborted on an unrelated TypeScript configuration error before the real error was ever printed.

**Inline images silently destroyed whole conversations.** Freshchat sometimes returns an image as a base64 data URI rather than a link to storage. Embedded into the message body, that pushed the comment past Gorgias's 1 MB message limit, and Gorgias rejected the entire ticket — so the conversation migrated as nothing at all, rather than as a conversation missing one image. Inline images are now decoded and uploaded as real attachments.

**An emoji cut in half returned a bare HTTP 500.** Subject lines were truncated to 80 characters using a method that counts UTF-16 code units, which can slice an emoji down the middle and leave an unpaired surrogate. Gorgias answers that with a 500 and no hint as to the cause.

**A contact lookup that could never succeed.** The call migration looked up Gorgias contacts by phone number — a filter Gorgias does not support. The lookup returned nothing every single time, and close to five hundred calls parked as having no contact. It now consults a phone index built in advance, and says so plainly when a number cannot be resolved.

One more sat on the pull side. The chat attachment fetcher swallowed every failure and returned nothing, and the caller then marked the conversation as fetched regardless — so a file that returned a 403 was permanently recorded as successfully retrieved. It now distinguishes stored, unavailable and failed, and only a non-transient outcome marks the work done.

A separate throughput problem came from two upload steps using a bare HTTP call that bypassed the shared request queue. Local backoff slowed one worker while the queue kept creating tickets against the same account limit. Routing those calls through the queue dropped upload rate-limit responses from 45 to 4 across roughly five thousand downloads.

---

## The Results: Complete on All Three

Chat and phone were pushed in under two weeks. Email ran for roughly six weeks, the longer window reflecting the million-ticket archive.

All three finished complete. Every one of the Freshdesk tickets landed in Gorgias, along with effectively all of the replies. Every chat conversation and every chat message migrated. Every call migrated. The counts were verified by reading Gorgias back rather than by trusting the push, and each migration's records are retrievable in Gorgias under its own tag and view.

What Whittard holds now is a single workspace where an agent opening a customer can see the email thread from the early years, the chat from the middle of the decade and the call from last month in one place — which was not possible in any of the three systems they came from.

---

## What Made This Migration Different

For teams planning a move off Freshworks, three details from this project are worth noting:

- **A clean pull is not a complete pull.** Freshdesk's list API returned fewer than six thousand tickets without an error, a warning, or any indication that more than a million others existed. Auto-archiving is a storage feature with an API consequence that nothing in the response tells you about. Before trusting any full-history export, probe a few high ticket IDs directly and compare what comes back against what the list gave you, the way any [Freshdesk migration checklist](https://clonepartner.com/blog/blog/freshdesk-migration-checklist) should start. If the numbers disagree, the export tooling in the account UI is the route to the real work list.

- **Silent failures are the expensive ones.** Of the bugs found here, the loud ones cost hours. The quiet ones — attachments that failed every time, conversations dropped entirely because of one oversized inline image, files recorded as fetched after a 403 — would have produced a migration that looked finished and was not. Verification has to read the destination back and count it, because a push that reports success is only reporting that it sent something.

- **Consolidating products is not three migrations glued together.** Email, chat and phone have genuinely different shapes, and the decisions that matter are the ones about what a record means rather than where a field goes. Whether an abandoned call is worth migrating, whether bot-resolved chats belong in the history, whether a system message is a reply or a note — none of those are answered by a field mapping, and all of them change what the destination is worth to the people who have to use it.

## Frequently asked questions

### Why does a Freshdesk full-history export return far fewer tickets than the account holds?

Freshdesk automatically archives old tickets, and archived tickets do not appear in list calls, cannot be filtered for and do not come back from ticket search. On this account a standard full-history pull finished cleanly and returned fewer than 6,000 tickets while the real archive held over a million. Nothing in the response indicates that anything is missing.

### Can archived Freshdesk tickets be retrieved through the API?

Individually, yes. An archived ticket returns 404 on a normal GET and returns the ticket when the request declares the archive flag, so the records are retrievable but not discoverable. The route around it is an account-level CSV export of ticket IDs from Freshdesk's own export tooling, which then becomes the work list the pull walks.

### Can Freshdesk, Freshchat and Freshcaller be merged into a single Gorgias workspace?

Yes. Each product was run as its own migration job against the same Gorgias account, so a failure in one could never damage another and each could be paced against its own source rate limits. Every record carries a tag naming its source system and each migration wrote to its own Gorgias view, so a year later anyone can still tell which records came from where.

### How do Freshcaller phone calls migrate into a help desk that expects text?

Freshcaller calls have no message body, so the record has to be synthesised. Direction was tagged inbound or outbound, outcome tagged answered, missed, voicemail or abandoned, and call metrics attached as a note. Abandoned calls and calls with no audio were migrated deliberately rather than skipped, because a customer who called three times and gave up twice is telling you something.

### Does Gorgias preserve original creation dates from a Freshdesk migration?

Not in its own timestamp fields. Original creation and resolution timestamps were written into dedicated custom fields instead, because a migrated ticket that reports today's date as its creation date is worse than useless for a retailer looking at seasonal patterns.
