The scene usually starts like this: the company has decided to switch CRMs. The decision came from above, the new contract is already signed, and the news reaches whoever runs support along with a detail nobody had mentioned: the old system gets shut down in two weeks.
Inside the old system there are four years of tickets. Every complaint, every email thread, every promise made to a customer, every attachment with an invoice in it. The history the team digs through every day to answer "has this happened before?". And now all of it has to show up on the other side, whole, in two weeks.
The fear that hits at that moment is well founded. I have seen a migration where every ticket landed in the new system with its creation date set to the day of the import. Four years of history became one Thursday. Volume reports? Gone. "How long has this customer been complaining about this?" Since Thursday, apparently.
Which is why I stole the advice from the cover of The Hitchhiker's Guide to the Galaxy: don't panic, in large, friendly letters. Data migration has a method. Before working with CRMs I spent five years at Adobe moving data at scale with Hadoop and Spark, and I treat a helpdesk migration the way I learned to treat a data pipeline there: as a sequence of stages, not as one big copy and paste.
This article is the process I follow, end to end, built over migrations involving Zendesk, Freshdesk, Salesforce, Movidesk and Zenvia. It is for whoever is about to hire a migration and wants to understand what they are buying, and for whoever is about to run one and wants a map.
The three types of migration
The first conversation I have with a client about a migration is not about tools. It is about what they want to happen to the history. Out of that conversation come three possible answers, and I call them type I, II and III.
Type I: the archive. We export everything from the old system and park it somewhere searchable: a database, a well-organized set of spreadsheets, sometimes the old system itself kept alive in read-only mode for a few more months. None of it enters the new CRM. Choose this when the history matters for compliance, audits or the occasional lookup, but is not part of the day-to-day operation. The price: when somebody needs a ticket from 2023, they will look for it outside the CRM. It is by far the cheapest option, and for a lot of companies it is enough. Part of my job is saying that out loud even when I would earn more selling type III.
Type II: the migration with legacy fields. The core records enter the new CRM: customers, tickets, conversations. But the fields specific to the old system are not translated into the new system's model. They get parked in legacy fields, a "legacy_status" here, a "legacy_category" there. Choose this when you need continuity fast: the team opens a customer and sees the whole history on one screen, even if part of it is still living out of a suitcase. The price: you carry the old system's shape into the new one. Reporting on top of legacy fields is always a bit crooked, and one day somebody will ask why there is a field called legacy_queue_n2.
Type III: the full migration. Every source field is deliberately mapped to a destination field, or consciously dropped. Status becomes status, priority becomes priority, the custom field over there becomes an equivalent custom field over here. The history behaves as if it had always lived in the new system: you can filter it, report on it, automate on top of it. Choose this when the new CRM is the long-term home and the history is part of the operation. The price is time and money, because most of the effort in a full migration lives in the mapping, and good mapping is done with business people in the room.
Don't treat this as a ranking where type III is the right answer and the others are the poor man's version. It is a dial between cost and fidelity, and the right spot depends on how much the history is worth to the operation. And real projects almost always mix types: type III for the open and recent tickets, which need to genuinely work in the new system, and type I for the thousands of closed tickets from years ago, which only need to exist somewhere. Paying full-migration price for data nobody will ever open is burning budget.
The archive
A full export, parked somewhere searchable. Nothing enters the new CRM.
Legacy fields
Core records move in; old fields ride along as legacy_* instead of being remapped.
Full mapping
Every field deliberately mapped or dropped. History behaves natively in the new CRM.
The pre-conditions (or: know where your towel is)
In the Guide, a hitchhiker worth their salt always knows where their towel is. In a migration, the towel is the complete export of the data. Before discussing mapping, schedule or anything else, the first move is securing a full copy of everything in the source system, stored somewhere that belongs to you. If the old system shuts down in two weeks, this is not the first task on the list. It is today's task. A CRM that got switched off does not answer requests of "just one more export, please".
After the towel, the rest of the pre-conditions:
Admin access to both systems. Not an agent account, admin. Migrations run on APIs, and export APIs generally only hand over everything to whoever has permission over everything. A good share of the project delays I have seen at kickoff were exactly this: waiting for someone to figure out who owns the account.
Someone from the business with time reserved. The mapping (coming up) does not write itself. It needs a person who can answer what that "Queue 2 - special" field created in 2021 actually means, and why half the tickets have it empty. Without that person the migration stalls. Or worse: it doesn't stall, and someone decides alone.
The type decision, in writing. Type I, II or III, or the mix, decided and agreed before the first line of code. Changing type mid-project is redoing the project.
Getting to the data
With the pre-conditions in place the technical part begins, and with it the first surprise: "exporting the data" means something different in every system. Zendesk and Freshdesk have mature, well-documented APIs that hand over tickets, conversations and contacts without drama. Salesforce gives you powerful reports and APIs, but the data lives in a model so customizable that first you need to understand what the company did with it over the years. Movidesk and Zenvia have leaner APIs, and sometimes the path includes spreadsheet exports to complete what the API does not deliver.
Then there is the detail that separates whoever has done this before from whoever is doing it for the first time: the attachments. The export gives you the ticket and the text of the conversations, but the files, the screenshots, the invoice PDFs, the image pasted in the middle of a reply, all of that usually comes as an authenticated URL pointing at the old system's servers. Migrate the ticket and ignore the attachments, and on the day the old system goes dark every one of those links becomes a 404. Downloading, storing and re-hosting attachments is often the slowest part of the whole extraction, and it is the one nobody puts in the budget.
The mapping
The mapping is the most important spreadsheet in the project: on one side, every field of the source system; on the other, where it goes in the destination, plus the transformation rule along the way. Field by field, with a decision column: it migrates, it becomes legacy, or it dies. It looks bureaucratic and it is. It is also what decides whether the migration will be any good.
source (Movidesk) destination (Freshdesk) decision
Status "Aguardando" Status "Pending" direct match
UrgĂȘncia 1 to 4 Priority Low to Urgent translate the scale
Fila N2 legacy_queue (text) becomes legacy
Motivo do contato (none) dies, business call
Except fields are the easy part of the mapping. The hard part is the relationships. A ticket does not exist on its own: it points at the customer who opened it, the agent who answered it, the customer's company. In the source system those links are made with IDs, and none of those IDs exist in the destination. The migration has to load companies first, then contacts, then tickets, keeping a translation table at every step: customer 4471 over there is customer 892 over here. Get that order wrong, or lose that table, and the result is a CRM full of orphan tickets, with no owner and no customer. Technically migrated. In practice, useless.
And there is the trap I mentioned at the start: dates and authorship. Every CRM, on receiving a new record, stamps the creation date as right now and the author as the API user. That is the default behavior. If the migration does nothing about it, every ticket is born on load day, created by "integration user". The good systems have a way of preserving the original values on import, but it is almost never the API's default path, and finding that out after the load is too late. This is the question I would put to any migration vendor: how do you preserve the original dates and authors? Whoever has done it answers on the spot.
The staging area
Now the concept I brought over from data engineering, the one that holds everything together: the staging area. The idea comes from Ralph Kimball, in a classic book about ETL, and it is simple: between the source and the destination there is neutral ground, an intermediate database that belongs to you, where the extracted data lands before any transformation.
All the work happens there: the cleaning, the field mapping, the ID translation, the checking of what arrived. The source system is read once and then left in peace. The destination system only receives data when the data is ready. And when something goes wrong in the middle, and something always goes wrong in the middle, you fix it in staging and reload, without stressing anyone's API and without depending on the old system still being alive.
The alternative, transforming data in flight while reading from the source and writing straight into the destination, works in a demo and charges dearly in production. Without staging, every mistake forces you to extract everything again. With staging, a mistake costs a fix and a reload. On a migration with a tight deadline, that difference is the project.
Tickets and conversations
extracted once, via APIContacts and companies
who points at whomAttachments
authenticated URLsNeutral ground
Tickets
original dates and authorsCustomers
relationships re-linkedAttachments
re-hosted, no 404sThe sample test
With staging in place and the mapping written, the temptation is to load everything. Hold on. First goes a sample: a hundred tickets, hand-picked to include the weird cases. The ticket with twenty replies and three attachments. The one that got merged into another. The one from a customer who was deleted from the base. The one with an emoji in the subject and accents in every field, because we write in Portuguese and broken encoding is a tradition.
The sample goes into the new CRM and then comes the important part: I am not the one who validates it, the client is. The business person opens those hundred tickets in the new environment and checks whether they tell the right story: the dates match, the conversations are in order, the attachment opens, the field that matters is where it should be. That validation is where you find the surprises that are cheap to find early, the "ah, we actually read this field differently" kind. Fix the mapping, run another sample, and only when a sample comes out clean does the big load make it onto the calendar.
The load
The full load is the most predictable part of the project and the most misunderstood one in the schedule. The reason has a name: rate limit. The destination CRM's API accepts a limited number of requests per minute, and the math is unforgiving. A base with 180 thousand tickets, each with its conversations and attachments, can easily mean more than a million API calls. Divide that by the platform's limit and the result is measured in days, not hours. Whoever promises to migrate a large base "over the weekend" either didn't do the math or is about to blow through the limit, get throttled by the platform, and watch the migration stop halfway.
That is why the load is designed to run on its own and survive stumbles: in batches, with pauses between calls to respect the limit, with retries for when the API chokes, and idempotent, an ugly word for a simple idea: if the process dies in the middle and you run it again, it picks up where it stopped instead of duplicating the ten thousand tickets that had already gone in. Staging helps again here, because that is where the bookkeeping lives: what already went, what is still missing.
The cut-off date
While the load runs, the operation keeps answering customers, and every hour that passes new tickets are born in the old system. This is where the most operational decision of the project comes in: the cut-off date. From this moment on, every new conversation is born in the new system; whatever is in the old one freezes and becomes the past.
The design that tends to work: first migrate the history, that mass of data that no longer changes, calmly, during the week. When the cut-off date arrives, the team flips the switch and starts working in the new system. Then a second migration runs, a small one, just the delta: the tickets that were born or changed in the old system between the start of the load and the cut-off. That way nobody stops answering customers while the history crosses over, and the awkward stretch with one foot in each canoe lasts hours, not weeks. It is worth agreeing on the plan B too: if anything goes wrong at the switch, the old system stays reachable in read-only mode until everything has been checked. Nobody unplugs anything on cut-off day.
The post-migration checks
Migrated, done? Not yet. The last stage is the checking, and it has two levels.
The first is the numbers: counts per entity, source against destination. So many thousand customers there, so many thousand here. Tickets, conversations, attachments, same arithmetic. Any difference needs a written explanation, and "some went missing" is not an explanation. Usually the difference has a good cause, like spam tickets deliberately left behind, but it needs to be on paper.
The second level is human: a final round of spot checks, with the support team opening old tickets in the new system during the first days of operation and reporting anything strange. Same spirit as the sample, now with the whole base and with the people who actually use it.
And two farewell chores. The full export from the beginning, the towel, stays archived, because it is your backup of the original truth. And the staging area, which carries customers' personal data, gets destroyed once everything is validated. An intermediate copy of personal data is not a travel souvenir. You don't keep it.
Don't panic
Back to the manager from the beginning, with the system going dark in two weeks. What the process on this page changes is not the deadline, it is the panic. Two weeks is still not much, but it turns into a sequence of known decisions: secure the export today, pick the type, map with someone from the business at your side, test with a sample before loading, cut over on an agreed date and check before celebrating.
CRM data migration is one of those jobs that, done well, nobody notices. The team opens the new system on Monday, the history is there, the dates match, the attachment opens, and life goes on. That anticlimax is exactly what we are after.
If there is one of these migrations on your horizon, tight deadline or not, come talk it over with us at Red Lotus.
Leandro is the founder of Red Lotus Tecnologia, specialized in implementing, supporting, and evolving Freshdesk for support teams in Brazil.
Mentioned in this article
- Ralph Kimball and Joe Caserta. "The Data Warehouse ETL Toolkit." Wiley, 2004.
- Douglas Adams. "The Hitchhiker's Guide to the Galaxy." 1979. The towel thing is chapter 3.