Customers post in channels both systems can read, Slack threads are keyed by timestamps rather than ticket IDs, and the SLA clock lives in whichever tool was watching at the time.
Why the parallel run is the part people shorten
A weekend cutover puts open threads and last quarter’s response-time history at risk. A parallel run protects both under one rule: each shared channel belongs to one system at any moment, because two systems answering one customer produce a visible duplicate.
Run both systems for at least four weeks, comparing them on the same requests rather than answering customers twice. Get the old contract’s notice period in writing before week 1, because the overlap means paying for both. Set the exit threshold now. As a suggested starting point, if two or more threads in every hundred sampled show a different status in the two systems on the same day, pause the split. Zero or one passes.
An eight-week timeline example (six weeks of work, two hold weeks)
This is a planning example for eight agents, about 40 shared channels and roughly 3,000 threads from the last year.
- Week 1, export and inventory. Record counts of tickets, macros, triggers and customers. Gate: the counts match the source reports.
- Week 2, build and sign off. Recreate statuses, labels, views and assignment rules. Import customers and check 50 by hand.
- Weeks 3 and 4, shadow and split. Move the 10 lowest-volume channels first. Compare first response time for moved and unmoved channels every business day.
- Week 5, the bulk. Move the remaining 27 channels, leaving the three busiest.
- Week 6, the busiest channels. Move them on a light-volume weekday. Keep the old system writable until each channel’s rollback window has closed, which for the last channel is the end of week 8. Weeks 7 and 8 are hold weeks: no new moves, only the daily channel-map check.
If the week 4 comparison shows slower answers for the same channel mix, stop the split and fix routing before moving anything else.
What to export, and how far back to go
Export each object as a dated file. Customers come with emails and their account. Tickets come with status, assignee, group, tags, custom fields, first reply and solved times, the full comment list, and the Slack channel ID and thread key where the ticket began. Macros, triggers and views come with full text, conditions and actions, and every CSAT response goes out with its ticket ID and score.
Set the history scope first. Import tickets closed in the last 12 months in full. Older closed tickets keep only the three-timestamp CSV described below and the raw export. A thread that exists only in Slack becomes a closed ticket if it holds a customer request and is under 90 days old.
If the source is Zendesk, use the incremental export endpoints (/api/v2/incremental/*). As of 2026-11-11, the Zendesk rate limit page lists that group at 10 requests per minute, or 30 with the High Volume API add-on. A 6,000-page backfill at 10 requests a minute takes at least ten hours before retries. Over the limit the API returns 429 with a Retry-After header giving seconds to wait, so have the script read that header and wait.
Map Slack user IDs to contact emails by lowercased email. Anyone unmatched goes on a manual list, because the deletion steps below only work for people you can find by ID.
Map statuses, fields, macros and integrations
Most failed imports come from a status or field with no exact twin. Sign the mapping as a table before any import. These rows are illustrative:
| Old system | New system | Check before the move |
|---|---|---|
| Status “On hold” | “Waiting on customer” plus label “was-on-hold” | Labelled tickets equal the old count of held tickets |
| Custom field “Contract tier” (dropdown) | Account field with the same values | Every account has exactly one value |
| Group “Tier 2” | Team “Tier 2” with the same agents | Assignees match the old group membership |
A status with no twin maps to one that keeps the thread visible in the queue, never one that hides it. Macros and triggers are configuration, not ticket data, so most importers do not move them. Rebuild the 20 most-used macros first, and leave any macro used fewer than 5 times in the last month until after cutover. Retire any trigger that only worked around the old tool, such as a reminder that fired because a status was missing.
List every integration that reads or writes the old system, such as CRM sync, webhooks and CSAT sends. Give each an owner and test it with one real ticket before its channels move.
A failure mode to watch: a trigger emails a customer from the new system while the old one still sends the same email. Switch the old system’s customer-facing automations off as soon as a channel moves. Keep the support email forward in place until the old inbox backlog is empty.
Attachments, SLA history and personal data
Attachment links may stop working when the old subscription ends. Ask the vendor in writing what happens to links on cancellation, then download the files by script into your own bucket keyed by ticket ID.
The new system will not carry your old SLA clock. Keep a quarterly CSV with three timestamps per ticket (request started, first human reply, resolution) plus the business-hours calendar that applied. Compute attainment and the 90th percentile from that file. The first response time calculator helps with deadlines.
Before week 4 ends, compare business-hours settings field by field: time zone, hours for each weekday, and holidays. Then work out the due time for 20 requests that start on a Friday evening, in both systems. A Friday 5:40 pm message with a one-hour target and 9 am to 6 pm hours has 20 minutes left on Friday and is due at 9:40 am on Monday, assuming a weekend closure and no holiday. If one system says 10:40 am, the calendars differ, and you fix that first.
For the Slack side, the workspace export holds messages and file links. On Free and Pro, the path is Admin in the sidebar, Workspace settings, Security, Import & export data, then the Export tab. Free and Pro cannot export private channels or direct messages, and on Free only file links from the last 90 days can be exported.
Exports hold names, emails and message text. Limit file access to a named list, and write the deletion steps before a request arrives: search the customer’s email, Slack user ID and ticket IDs in every store, then delete or redact in each. This is operational guidance, not legal advice.
Slack Connect channels and the Slack app
Most of the cutover risk is in the channels, not the tables.
- One owner per channel. Keep the channel map as the source of truth and check it daily during the overlap.
- Read limits and scopes. conversations.history needs channels:history for public channels, groups:history for private channels, im:history for direct messages and mpim:history for group DMs. The reference lists Tier 3, 50+ per minute, with up to 999 messages per call. A Slack changelog entry dated May 29, 2025 says new non-Marketplace commercially distributed apps, and new installations of existing unlisted apps, drop to 1 request per minute with 15 objects per call. Internal customer-built apps keep 50+ per minute and 1,000 objects. At 100,000 messages, 15 per call is 6,667 calls, about 111 hours, while 999 per call is 101 calls. Ask your vendor in writing which limit its app runs under.
- Events and the gap. Your endpoint must return a 2xx within three seconds. Slack retries up to three times, nearly immediately, after 1 minute and after 5 minutes, and by default does not deliver an event more than two hours late. The Events API docs call this best-effort. Keep the old app subscribed until a channel moves, then reconcile the downtime with conversations.history for top-level messages and conversations.replies for each thread active in that window. The history reference points to conversations.replies for retrieving a message from a thread, so history alone will miss customer replies inside threads.
- Key threads on a value. Store channel ID plus a thread key on every ticket in both systems: thread_ts when the message has one, otherwise its ts. The message reference calls ts unique per channel. The message_replied reference says a reply’s thread_ts points to its parent, and its example parent has thread_ts equal to ts, so the key holds after the first reply arrives. The reference does not say whether a top-level message with no replies carries thread_ts, so do not assume it does. Old tickets with no key match on requester email and first-message time, and unmatched ones go on a manual list.
- No orphans, then reconcile. Before a channel moves, list every thread waiting on the customer and every thread waiting on you, and carry each across with its status. Check each customer message in the 24 hours around cutover against a reply or ticket.
Timing rule: if a channel has more than 20 open threads at cutover, move it on a day when fewer than 10 new customer messages arrived in the previous 24 hours. If it still has more, split its threads across two days and say so in the notice.
Go/no-go gates and rollback per channel
Name an owner for each gate before week 2.
| Gate | When | Suggested criteria | Owner |
|---|---|---|---|
| Mapping sign-off | Start of week 2, before any import | Table signed, 50 contacts checked | Support lead and systems owner |
| Parallel comparison | End of week 4 | Fewer than 2 mismatches in 100 sampled threads, no slower response on the same channel mix | Support lead |
| Channel move | Before each channel’s cutover | Open threads carried, no orphan threads, reconcile done | Channel owner |
Rollback window and rule (suggested): a channel’s rollback window is the two weeks after the day it moves. Inside that window, move the channel back if its first-response attainment in the new system falls more than five points below its baseline, meaning the same channel’s attainment in the old system over the two weeks before it moved, or if a customer message passes the SLA target with no reply. After the window closes, the channel stays where it is. Decide per channel, not for the whole migration.
Keep each moved channel’s old Slack connection disconnected and its triggers disabled, not deleted, so reconnecting is a switch rather than a rebuild. Rollback steps, which need the old system still writable:
- Switch the channel’s owner back to the old system in the channel map, and disable the new system’s automations for it.
- Export the tickets touched since the move, and write each one’s status and new replies back to the old ticket as internal notes, matched on the stored thread key.
- Reconnect the old Slack connection and re-enable its triggers for that channel.
- Run the reconcile check over the time since the move, then post in the channel which system now owns it.
The old system goes read-only only after the last channel’s rollback window closes, and then stays read-only for at least one quarter after cutover, as a suggested minimum.
Cutover notice templates
Send one notice per channel two business days ahead, as a suggested lead time. Post it at top level, not in a thread, and email your named contacts too.
Before the cutover:
“Hi [name], from [date] we are moving our support queue to a new system. Nothing changes in this channel: keep posting here, and the same people will reply. Email to [new address] will go to the new system from that date. Your open requests will move with their current status, and we will confirm each one in this thread.”
On cutover day:
“We have finished the move. Your open requests are carried over. If you posted between [time] and now and have not had a reply, post ‘Still waiting’ in this thread and we will answer it first.”
Use the second message to find gaps, not to reassure. Log each “Still waiting” reply against the original message time, so the gap is counted in your reconcile step rather than found by chance.