Skip to content

Thena to Kustomer Migration: A Technical Guide

Technical guide to migrating from Thena to Kustomer. Covers API mapping, rate limits, Slack mrkdwn conversion, and AI metadata preservation.

Abdul Abdul · · 22 min read
Thena to Kustomer Migration: A Technical Guide
TALK TO AN ENGINEER

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

Thena to Kustomer Migration: A Technical Guide

Info

TL;DR: Thena is a Slack-native, AI-first B2B ticketing platform. Kustomer is a CRM-first customer service platform organized around a customer timeline. No native migration path exists — Thena's Account → Contact → Request model must be transformed into Kustomer's Company → Customer → Conversation → Message hierarchy using a custom API-based ETL pipeline. Key challenges: Slack user identity resolution, mrkdwn-to-HTML conversion, preserving AI metadata as Kustomer custom attributes, and navigating rate limits on both sides (Thena: 60 rpm standard tier; Kustomer: 300–2,000 rpm depending on plan). Use Kustomer's importedAt field on Conversations and Messages to bypass per-customer rate limits. Budget 84–136 engineer-hours for a mid-size dataset (10K–50K requests).

Migrating from Thena to Kustomer means moving from a Slack-first B2B support tool to a CRM-centric omnichannel platform that anchors everything to a unified customer timeline. These platforms solve overlapping problems using fundamentally different architectural models, so this is a data-model transformation — not a lift-and-shift.

There is no built-in connector, no CSV import that preserves conversation threading with messages, and no third-party migration tool supporting Thena as a source and Kustomer as a target. Just as we've seen when migrating Thena to LiveChat or Podium, every migration requires a custom ETL pipeline built against both vendors' APIs.

This guide covers the exact API endpoints, data-model mapping, rate-limit math, error handling, and cutover logic required to move your historical data from Thena to Kustomer without data loss or downtime. If you are still evaluating your data export options, start with our guide to exporting data from Thena. For Kustomer's import mechanics, see our Kustomer data export and portability guide.

The Data Model Shift: Thena vs. Kustomer

Thena organizes support around Requests — conversations captured from Slack, email, MS Teams, web chat, and Discord. Thena's Accounts View turns Slack, email, and API signals into a unified, editable customer table, automating data capture and giving every team full context. Each Request carries AI-generated metadata: title, summary, tags, urgency, sentiment, and category. Requests are grouped under Accounts (companies) and linked to Contacts (people).

Kustomer flips the model. Rather than a queue of tickets, Kustomer gives each customer a single timeline — a chronological feed of every interaction and event across every channel. An agent opening a customer record sees the whole relationship in one view. Conversations still exist, but they hang off the customer record rather than defining it.

The Kustomer Data Model is made up of two types of objects: Standard Objects and Custom Objects. Each object type can define additional custom attributes, and objects can relate to each other through defined relationships. Kustomer uses its own internal term, Klass, to refer to the schema definition of an object type — for example, the "Conversation Klass" is the schema that defines what fields a Conversation object can carry. This distinction matters because you configure Klasses before importing data, not during.

The hierarchical import order that follows from this model is non-negotiable: a Message cannot exist without a Conversation, a Conversation cannot exist without a Customer, and a Customer should be linked to a Company that already exists. If you attempt to create in the wrong order, you will receive 422 Unprocessable Entity or 404 errors on parent references. Build your pipeline to enforce this sequence.

Core Object Mapping

Thena Object Kustomer Object Transformation Notes
Account Company 1:1 mapping. Match on domain name or external ID.
Contact Customer Email is the natural dedup key. Slack IDs must be stored as custom attributes.
Request Conversation One Request = one Conversation. AI metadata maps to custom attributes on the Conversation Klass.
Message / Reply Message Nested under the Conversation. Slack mrkdwn must be converted to HTML.
Internal Note / Thread Message (Note) Map each thread message to its own Kustomer note to preserve author and timestamp fidelity.
AI Metadata Custom Attributes Title, summary, urgency, sentiment, category → custom fields on the Conversation Klass.
Attachment Attachment Download from Thena/Slack, re-upload to Kustomer, then link to the Message.
Tags Tags Create tags in Kustomer first, then reference by ID during import.
Warning

What does NOT transfer: Thena's AI agent configurations, workflow automations, SLA definitions, routing rules, and dashboard configurations must all be rebuilt manually in Kustomer. These are platform logic, not data — no API can migrate them. Knowledge base articles require a separate pipeline or manual recreation.

Phase 1: Extract Data from Thena

API-Based Extraction

All Thena APIs require authentication using an x-api-key header. API keys are tied to individual users and can be generated from Dashboard → Organization Settings → Security and Access.

Verify your Thena tier before writing extraction code. Some endpoints — including /v1/comments and certain analytics endpoints — are only available on Standard and Enterprise tier plans. To confirm your tier: navigate to Dashboard → Organization Settings → Billing, or contact Thena support. If you discover a required endpoint is gated after you've started, you will need to upgrade before extraction can complete.

The key extraction endpoints:

  • Accounts: GET /v2/accounts — Pull all accounts. Store the mapping between Thena's accountId and the company domain.
  • Contacts: GET /v2/contacts — Returns comprehensive contact details including firstName, lastName, email, phoneNumber, associated accountId, and contact type.
  • Requests: GET https://bolt.thena.ai/rest/v2/requests — Returns all requests, sorted in creation order (oldest to newest). Pagination is cursor-based — use the data.next_cursor value from each response to iterate through pages. When next_cursor is null or absent, extraction is complete.
  • Messages per Request: GET https://bolt.thena.ai/rest/v2/requests/{requestId}/messages — Returns the full message thread for a given request. This endpoint is available on Standard and Enterprise tiers.
  • AI Metadata: Each request object includes the following AI-generated fields directly in the response body: ai_metadata.title, ai_metadata.summary, ai_metadata.tags [], ai_metadata.category, ai_metadata.userDefinedTags, ai_metadata.urgency, ai_metadata.sentiment, and ai_metadata.actionable.
  • Custom Fields: GET /v3/custom-fields for field definitions.

Delta sync parameter: Thena's request list endpoint supports an updatedAt filter for incremental extraction. Pass it as a query parameter: GET /v2/requests?updatedAfter=2024-05-10T00:00:00Z (ISO 8601 format, UTC). Use this during the delta sync phase to retrieve only records modified after your initial extraction freeze date. If Thena returns a 400 on this parameter for your tier, fall back to filtering on updatedAt client-side from a full export.

Extract your relational data (Accounts and Contacts) first to build the identity map before pulling Requests and Messages.

Rate Limit Math (Thena Side)

Thena's standard tier allows 60 requests per minute per user, org, and IP. If you exceed the rate limit, you will receive a 429 Too Many Requests error.

For a workspace with 25,000 Requests, each requiring one list call per page plus one detail call for messages:

  • At ~50 requests/page (leaving headroom): ~500 pages = ~500 minutes ≈ 8.3 hours for Requests alone
  • Add Contacts and Accounts: budget 10–14 hours total extraction time
  • Enterprise tier customers can negotiate higher limits
Tip

Speed up extraction: Run Contact, Account, and Request extraction in parallel using separate API keys if your org allows it. Each key gets its own 60 rpm allocation.

Because fetching messages requires a call per Request, you will hit the 60 req/min limit fast. Implement a queueing system (Redis + Celery, AWS SQS, or similar) with exponential backoff. Do not attempt to run this extraction synchronously in a single script.

Dashboard Export as a Cross-Reference

You can export the entire set of requests from your Thena platform in XLSX or JSON format. Go to the Requests section within the Customer Support tab, use the export option in the top right corner, and select either "Filtered requests in view" or "All requests in time frame."

This export is useful for validation and spot-checking, but it won't give you the relational depth (linked contacts, full message threads, attachments) that the API provides. Use it as a cross-reference, not as your primary data source.

Phase 2: Transform the Data

This is where the majority of migration failures occur. You cannot pass Thena's JSON payload into Kustomer without significant transformation.

Identity Resolution

In Kustomer, every Message must be tied to either a Customer (the end-user) or a User (your support agent). Thena often represents message authors via Slack IDs (U04ABCDE12). You must resolve these to email addresses.

Build and maintain a local identity map (SQLite, Redis, or similar) during the migration:

  • Thena Contact IDKustomer Customer ID
  • Thena Agent IDKustomer User ID
  • Slack User ID → email address

Resolve Slack IDs to emails using the Slack API (users.info or users.list) before or during the transform step.

Danger

Slack API dependency: If the Slack workspace that Thena was connected to is decommissioned or the bot token is revoked before you complete extraction, you lose the ability to resolve Slack user IDs to real identities. Extract and cache Slack user mappings early — this is a non-recoverable failure if missed.

For messages authored by Slack users with no corresponding Contact in Thena, you need a fallback. Create a "Historical User" customer in Kustomer to attribute orphaned messages rather than dropping them. Create a "Historical Employee" user for messages from agents who have since left the company.

Slack mrkdwn → HTML Conversion

Thena messages originating from Slack use Slack's proprietary mrkdwn format, not standard Markdown. Kustomer expects plaintext or HTML for message bodies. Skip this step and your Kustomer timeline will be littered with unreadable raw tags.

Slack mrkdwn HTML Equivalent
*bold* <strong>bold</strong>
_italic_ <em>italic</em>
~strikethrough~ <s>strikethrough</s>
`code` <code>code</code>
```codeblock``` <pre>codeblock</pre>
<@U12345> Resolved user name from identity map
<#C12345|channel-name> #channel-name
<https://url|display text> <a href="https://url">display text</a>

For a well-maintained, widely-used conversion library, use slack-to-html (npm, actively maintained as of 2024) or build a custom regex pipeline. If you build your own, implement the substitutions in this order: links before mentions (to avoid partial matches), then block-level elements before inline. User mentions (<@U123456>) must be regex-matched against your identity map and replaced with the user's display name before any other substitution that might alter the angle brackets.

Preserving Thena's AI Metadata

One of Thena's differentiators is its AI-generated metadata on every request. You lose this context entirely if you don't plan for it.

Before importing any conversations, define custom attributes on Kustomer's Conversation Klass. Kustomer uses a naming convention for custom attributes to specify the field data type via name suffix: Num (number), At (date-time), Str (string, max 1,024 chars), Txt (string, max 1,024 chars), Bool (boolean), Url (URI format).

Add custom attributes programmatically using PATCH /v1/klasses/conversation:

// PATCH /v1/klasses/conversation
// Authorization: Bearer <api_key>
{
  "fields": [
    { "name": "thenaRequestIdStr",  "type": "str" },
    { "name": "thenaTitleStr",      "type": "str" },
    { "name": "thenaSummaryTxt",    "type": "txt" },
    { "name": "thenaCategoryStr",   "type": "str" },
    { "name": "thenaUrgencyStr",    "type": "str" },
    { "name": "thenaSentimentStr",  "type": "str" },
    { "name": "thenaTagsStr",       "type": "str" },
    { "name": "thenaSourceStr",     "type": "str" }
  ]
}

Similarly, add Thena-specific fields to the Customer and Company Klasses before loading those records:

// PATCH /v1/klasses/customer
{
  "fields": [
    { "name": "thenaContactIdStr", "type": "str" },
    { "name": "slackUserIdStr",    "type": "str" }
  ]
}
// PATCH /v1/klasses/company
{
  "fields": [
    { "name": "thenaAccountIdStr", "type": "str" }
  ]
}

The full field mapping for AI metadata:

Thena Field Kustomer Custom Attribute Type Suffix Notes
ai_metadata.title thenaTitleStr Str Max 1,024 chars
ai_metadata.summary thenaSummaryTxt Txt Max 1,024 chars; truncate if needed
ai_metadata.category thenaCategoryStr Str
ai_metadata.urgency thenaUrgencyStr Str
ai_metadata.sentiment thenaSentimentStr Str
ai_metadata.tags [] thenaTagsStr Str Comma-separated
original_message_source thenaSourceStr Str e.g., "SLACK", "EMAIL"

Kustomer supports up to ~500 custom attributes per Klass on higher-tier plans. This gives agents full historical context in the Kustomer timeline without losing the AI intelligence Thena provided.

Status, Channel, and Direction Mapping

Status mapping:

Thena Status Kustomer Status Notes
OPEN open Direct map
ASSIGNED open Kustomer uses assignedUsers/assignedTeams separately
RESOLVED / CLOSED done Kustomer's terminal state
SNOOZED snoozed Direct map (if Kustomer plan supports it)

Channel attribution: Thena captures the originating channel (Slack, email, MS Teams, etc.) per request. Kustomer's channel field expects specific values: email, chat, sms, voice, facebook, twitter, instagram, whatsapp. Slack is not a native Kustomer channel. Map Slack-origin conversations to chat and store the original source in the thenaSourceStr custom attribute.

Message direction: Thena messages from customers vs. agent replies must be mapped to Kustomer's direction field: in for customer messages, out for agent responses. Get this wrong and your conversation timelines will be nonsensical.

Timestamp Handling

To ensure Kustomer's timeline displays conversations chronologically, map Thena's created_at timestamps to both createdAt and importedAt fields on the Kustomer payload. Both must be in ISO 8601 format with explicit UTC offset (e.g., 2024-05-10T14:30:00Z). The importedAt field serves double duty — it preserves the historical timestamp and bypasses per-customer rate limits. More on that in the next section.

Normalize all Thena timestamps to UTC before transformation. Thena stores timestamps in UTC, but if your extraction pipeline introduces any local timezone conversion (common in Python's datetime without explicit tz=UTC), your Kustomer timelines will display in the wrong order.

Phase 3: Load Data into Kustomer

Authentication and Pre-Import Setup

Kustomer uses API key authentication with Authorization: Bearer <api_key> in the request header. Kustomer uses different base domains by region: api.kustomerapp.com for US-hosted customers, and api.prod2.kustomerapp.com for EU-based clients. Using the wrong domain returns a 401 even with a valid key.

Generate your API key in Kustomer under Settings → Security → API Keys. For migration, you need at minimum org.admin permissions to set historical timestamps and modify Klass schemas.

Before loading any records:

  1. Define custom attributes on Company, Customer, and Conversation Klasses using the PATCH /v1/klasses/{name} calls shown in Phase 2.
  2. Create all tags in Kustomer via POST /v1/tags and record their IDs. Tags are stored as IDs in the Kustomer API — you cannot reference tags by name string in Conversation payloads.

The importedAt Field — Your Rate Limit Bypass

This is the single most important detail for high-volume migrations into Kustomer.

Conversations are subject to a per-customer rate limit: a single client can create up to 120 conversations per minute per customer. Conversations with importedAt in their body are not subject to this per-customer limit.

Messages are subject to the same per-customer ceiling: up to 120 messages per minute per customer. Messages with importedAt in their body are also exempt from this limit.

Always include importedAt (set to the original Thena creation timestamp) in every Conversation and Message payload. This does two things: bypasses per-customer object rate limits, and signals to Kustomer workflows that the data is historical.

Warning

Review any Kustomer workflows or business rules that trigger on Conversation or Message creation before you begin loading. Consider temporarily disabling affected automations or adding a condition that checks for the presence of importedAt — imported records that trigger live workflows can create ghost notifications, incorrect SLA timers, or unintended email sends to customers. You can add a conditional step to your Kustomer workflows: if importedAt is present, exit the workflow without executing.

Import Sequence

Order matters because Kustomer's data model is strictly hierarchical. Parent objects must exist before child objects can reference them. Attempting to create a Conversation before its Customer exists returns a 422. Attempting to create a Message before its Conversation exists returns a 404.

Step 1: Create Companies

Map Thena Accounts → Kustomer Companies via POST /v1/companies. Store the returned Kustomer Company IDs for use in Step 2.

// POST /v1/companies
{
  "name": "Acme Corp",
  "domains": ["acme.com"],
  "externalId": "thena_account_12345",
  "custom": {
    "thenaAccountIdStr": "acc_12345"
  }
}

The externalId field accepts a string up to 255 characters. Set it to the Thena Account ID. This allows you to look up the Kustomer Company by Thena ID on retry runs without creating duplicates.

Step 2: Create Customers

Map Thena Contacts → Kustomer Customers via POST /v1/customers. Link to the corresponding Company using the Kustomer Company ID from Step 1.

// POST /v1/customers
{
  "name": "Jane Doe",
  "emails": [{ "type": "work", "email": "jane@example.com" }],
  "phone": "+15551234567",
  "company": "kustomer_company_id",
  "externalId": "thena_contact_c_12345",
  "importedAt": "2024-03-15T10:00:00Z",
  "custom": {
    "thenaContactIdStr": "c_12345",
    "slackUserIdStr": "U04ABCDE12"
  }
}

externalId constraints: The field must be unique across all Customers in your Kustomer org. It accepts strings up to 255 characters. Using the Thena Contact ID as externalId means you can GET /v1/customers?externalId=thena_contact_c_12345 to check for existence before creating, enabling idempotent retry logic.

Deduplication: Kustomer matches on email. If a Customer with the same email already exists, the API returns a 409 Conflict. Use GET /v1/customers?email=jane@example.com to check before creating, or handle 409 responses by reading the conflicting Customer ID from the response body and updating the record instead. If a Thena Contact appears across multiple Accounts, normalize to a single Kustomer Customer before import — Kustomer enforces that an email address can belong to only one customer profile.

Always store legacy Thena IDs in externalId or custom attributes. This creates an audit trail and enables your scripts to safely resume from the last successful record after a crash.

Tip

CSV hybrid approach: Kustomer's CSV importer can bulk-load Customers, Users, Teams, and Companies through the Kustomer UI. You can bulk-load Companies and Customers via CSV, then use the API exclusively for Conversations and Messages. This reduces API call volume but adds a manual step and requires that you still map CSV-loaded IDs back to Thena IDs before the API phase.

Step 3: Create Conversations

For each Thena Request, create a Kustomer Conversation via POST /v1/conversations.

// POST /v1/conversations
{
  "customer": "kustomer_customer_id",
  "name": "Request Title or AI-Generated Title",
  "status": "done",
  "channel": "chat",
  "createdAt": "2024-05-10T14:30:00Z",
  "importedAt": "2024-05-10T14:30:00Z",
  "externalId": "thena_req_999",
  "tags": ["tag_id_1", "tag_id_2"],
  "custom": {
    "thenaRequestIdStr": "req_999",
    "thenaSummaryTxt": "Customer reported intermittent login failures...",
    "thenaCategoryStr": "authentication",
    "thenaUrgencyStr": "high",
    "thenaSentimentStr": "negative",
    "thenaSourceStr": "SLACK"
  }
}

Step 4: Load Messages

Messages must be loaded in chronological order within each Conversation. Use POST /v1/conversations/{conversationId}/messages.

// POST /v1/conversations/{id}/messages
{
  "channel": "chat",
  "direction": "in",
  "preview": "Plain text preview of the message (max 255 chars)...",
  "meta": {
    "html": "<p>Converted HTML from Slack mrkdwn</p>",
    "text": "Original plain text"
  },
  "importedAt": "2024-05-10T14:32:00Z",
  "createdAt": "2024-05-10T14:32:00Z",
  "sentAt": {
    "datetime": "2024-05-10T14:32:00Z"
  }
}

The Create Message endpoint records a message on an existing conversation timeline. It does not send anything to a customer — it only creates the timeline entry.

For internal notes, set the message type to note. Each Thena internal thread message should become its own Kustomer note to preserve author and timestamp fidelity.

Tip

Create imported notes with a dedicated long-lived API key or machine user. Kustomer notes can only be edited or deleted later by the same user or API key that created them. Using a shared team key risks losing edit access when team members leave.

Step 5: Handle Attachments

Attachments require a multi-step process. You cannot pass a URL to Kustomer and expect it to fetch the file.

  1. Download the file from Thena's signed URL (or Slack's authenticated URL) using a valid bot token.
  2. Upload the file to Kustomer via POST /v1/attachments using multipart/form-data.
  3. Link the returned attachmentId to the Message.
Danger

Memory constraints: Do not download large attachments directly into your application's RAM. Stream the file from the source directly into the Kustomer upload request using buffered streams. Failing to do this will cause migration workers to crash with Out-Of-Memory (OOM) errors when encountering large log files or video recordings. Kustomer note attachments are capped at 9.5MB — files exceeding this limit must be hosted externally and linked via URL in the message body.

Do not assume Slack file URLs will remain accessible after migration — they expire or become inaccessible if the workspace changes or the bot token is revoked.

Rate Limit Math (Kustomer Side)

The default Kustomer rate limit interval is 60 seconds. For machine users (API keys, not human logins): 300 rpm for Professional, 500 rpm for Business, 1,000 rpm for Enterprise, and 2,000 rpm for Ultimate plans.

This limit is counted against the total sum of API requests across the entire platform, regardless of endpoint or object type.

There is also a per-object update limit: a single Kustomer user or API key can update a given Customer, Conversation, Company, Message, or custom object no more than 50 times within a 10-minute window.

For a 25,000-Request migration at 300 rpm (Professional tier):

  • 25K Companies + 25K Customers + 25K Conversations + ~100K Messages = ~175K API calls
  • At 300 rpm: ~583 minutes ≈ 9.7 hours (global rate limit is the binding constraint)
  • With importedAt set on all Conversations and Messages, the per-customer 120/min object creation limit is bypassed

When you exceed the global rate limit, Kustomer returns 429 Too Many Requests. Inspect the x-ratelimit-reset and x-ratelimit-remaining response headers and pause accordingly. Do not drop the record on a 429 — always retry with exponential backoff. Your stored externalId values let you resume from the last successful record if the script crashes.

Kustomer Error Code Remediation

Migration pipelines will encounter these error codes. Handle each explicitly — do not treat all non-200 responses as fatal:

HTTP Code Kustomer Cause Remediation
400 Bad Request Malformed payload, wrong field type, or value exceeds character limit Log the offending payload; inspect field values against Klass constraints. Common cause: Str field exceeding 1,024 chars, or ISO 8601 timestamp with wrong format.
401 Unauthorized Invalid or expired API key, or wrong regional domain Verify key is active in Kustomer Settings. Confirm you're using api.kustomerapp.com (US) or api.prod2.kustomerapp.com (EU).
404 Not Found Referenced parent object does not exist Conversation's customer ID doesn't exist yet, or Message's conversationId is wrong. Enforce import order: Companies → Customers → Conversations → Messages.
409 Conflict Duplicate email on Customer, or duplicate externalId For Customer conflicts: fetch the existing record by email and update rather than create. For externalId conflicts: you've already imported this record — skip or update.
422 Unprocessable Entity Valid JSON but violates business rules Check required fields (Customer must have at least one identifier), relationship constraints, or enum values (e.g., invalid channel value).
429 Too Many Requests Global rate limit exceeded Read x-ratelimit-reset header (Unix timestamp of reset). Sleep until reset, then retry. Do not drop the record.
500 / 503 Kustomer-side error or transient outage Retry with exponential backoff (max 3 retries). If persistent, check Kustomer status page.

Kustomer Platform Limits That Affect Migration Design

Several documented Kustomer limits directly affect your migration design and post-migration QA:

  • Timeline UI limits: 10,000 conversations, 200 messages, 200 notes, 100 events per customer view. If a customer has more than 200 messages on a single Conversation, older messages will not display in the timeline UI — they are stored but not visible without API retrieval.
  • Search API: Only returns records updated within the last two years by default. For older imported records, use POST /v1/customers/archive/search with the same query syntax as standard search.
  • Conversation search: Done via POST /v1/customers/search with queryContext: "conversation" — there is no standalone /v1/conversations/search endpoint.
  • Message body limits: 10,240 characters for email and chat messages, 1,600 for SMS, 2,000 for Facebook DM. Notes also cap at 10,240 characters. Split messages that exceed these limits deterministically and store the part number in a custom attribute or as a prefix in the message body ([Part 1/3]).
  • Custom string attributes: Max 1,024 characters for Str type; use Txt type for longer values, which also caps at 1,024 chars but is semantically intended for longer content. If a Thena AI summary consistently exceeds 1,024 characters, truncate at a sentence boundary and append [truncated] before storing.
  • Export limits: Search exports cap at 50,000 rows, only one export can run at a time, and records older than two years will not appear in standard search exports at all. Use API-based validation with archive/search for old data.
Warning

Do not validate a large migration solely through Kustomer UI exports. Use API-based counting: GET /v1/customers/count, GET /v1/conversations/count, or paginated search to verify totals match your extraction counts.

Phase 4: Delta Sync and Cutover

A complete historical migration of 50K+ requests will take multiple days due to rate limits. Your support team cannot stop working during this time. You need a delta sync strategy.

  1. Initial Load: Extract and load all data up to a specific freeze date (e.g., Friday at midnight). Record the exact freeze timestamp.
  2. Gap Period: Your team continues working in Thena while the historical migration runs.
  3. Delta Sync: Once the initial load completes, query Thena for any Requests created or modified after the freeze date using GET /v2/requests?updatedAfter=<freeze_timestamp> (ISO 8601 UTC). The safe pattern: fetch updated record IDs → re-fetch each full record via GET /v2/requests/{requestId} → write to Kustomer. Do not rely on Thena platform event payloads as your data source — large comments can be truncated near the 256KB platform event payload limit. Always re-fetch the canonical record via API.
  4. Cutover: Once delta record counts drop to near-zero and record counts reconcile, reroute inbound channels (email, Slack integrations, webhooks, chat) away from Thena and into Kustomer. Set Thena to read-only.
  5. Final Delta: Run one last delta sync to catch any records created between the previous sync run and the channel cutover moment.

If your Thena workflows depend on Slack-linked internal threads, store the original Thena Request URLs in a custom attribute or note during the hypercare period. Kustomer does not recreate Thena's Slack-native collaboration model — agents expecting Slack thread-style replies will need workflow retraining.

Edge Cases and Failure Modes

These are the specific issues that will derail your migration if you don't handle them:

  • Orphaned Slack threads: If a Slack user replies to a thread but their email is hidden and their Slack ID isn't mapped to a Contact, the message will fail Kustomer validation. Implement a fallback "Unknown User" Customer assignment rather than skipping the message.
  • Contacts with no email: Kustomer deduplicates Customers by email. If a Thena Contact has no email (Slack-only user), create the Customer with just a name and Slack ID stored in slackUserIdStr. Use the Slack ID as externalId with a prefix (slack_U04ABCDE12) to guarantee uniqueness.
  • Cross-account contacts: If the same person appears under multiple Thena Accounts, apply an explicit merge rule before import. Kustomer enforces that an email address can belong to only one Customer profile. Attempting to create a second Customer with the same email returns a 409.
  • Silent rejection on field limits: Kustomer rejects payloads where custom Str attributes exceed 1,024 characters. The API returns a 400 with a field-level error message. Pre-validate field lengths in your transform step.
  • Timezone mismatches: Ensure all timestamps extracted from Thena are explicitly converted to UTC (Z suffix or +00:00 offset) before pushing to Kustomer. Python's datetime.now() without tz=timezone.utc is a common source of this error.
  • Per-object update storms: A single API key cannot update the same Customer, Conversation, Company, Message, or custom object more than 50 times within any 10-minute window. If your transform logic updates a Customer record multiple times during import (e.g., adding Company linkage in a separate pass), batch those writes or space them across the window.
  • Thena tier restrictions: Verify which endpoints your Thena plan exposes before writing extraction code. /v2/requests/{requestId}/messages may return 403 on free-tier workspaces.

Validation and QA Checklist

After loading data, run these checks before declaring the migration complete:

  • Record counts match: Total Customers, Conversations, and Messages in Kustomer match extraction counts from Thena (use GET /v1/customers/count and equivalent endpoints)
  • Conversation threading is correct: Messages appear in chronological order under the right Conversation
  • Customer ↔ Company linkage is intact: Spot-check 20+ Customers to verify Company association
  • Custom attributes populated: Thena AI metadata fields are visible on Conversation records
  • Tags applied: Thena tags map to correct Kustomer tag IDs
  • Timestamps preserved: createdAt on Conversations and Messages reflects original Thena dates, not import dates
  • No orphaned Conversations: Every Conversation is linked to a Customer
  • Attachments accessible: Re-uploaded files open correctly from Kustomer's timeline
  • Slack mrkdwn rendered correctly: HTML in message bodies displays properly in Kustomer timeline
  • Old records searchable: Records older than two years appear via POST /v1/customers/archive/search
  • Message direction correct: Customer messages show as in, agent replies as out
  • Error log review: Zero 400/422 errors in the load log (these indicate data loss, not transient failures)
  • externalId round-trip: Querying GET /v1/customers?externalId=<thena_id> returns the expected record for a sample of 20+ Customers

Timeline and Effort Estimate

Phase Effort (engineer-hours) Elapsed Time
Audit & schema design 8–16 hrs 1–2 days
Kustomer Klass setup (custom attributes + tags) 4–8 hrs 0.5–1 day
Extraction pipeline (Thena API) 16–24 hrs 2–3 days
Transform logic (mapping + mrkdwn conversion + identity resolution) 24–40 hrs 3–5 days
Load pipeline (Kustomer API) 16–24 hrs 2–3 days
Validation & QA 16–24 hrs 2–3 days
Total 84–136 hrs 2–4 weeks

The main time sinks are the transform layer (Slack mrkdwn conversion, identity resolution, status mapping) and QA. Extraction and loading are mechanically straightforward but time-gated by rate limits. A 25,000-request workspace at Kustomer's Professional tier takes approximately 10 hours of API time for loading alone at full throughput — plan infrastructure accordingly.

When to Build In-House vs. Bring in Help

This migration is within reach for a team with one or two engineers experienced with REST APIs, pagination, and data pipeline work. Both the Thena and Kustomer APIs are well-documented and predictable.

Consider bringing in external help if:

  • Your dataset exceeds 100K requests — rate limit math becomes a serious constraint and you'll want parallelization strategies (multiple API keys, sharded queues)
  • You have complex custom objects in Thena that need to become KObjects in Kustomer with defined relationships
  • Your Slack workspace is about to be decommissioned and you're under time pressure to resolve user identities before bot tokens expire
  • You need zero downtime during cutover with real-time sync during the transition period
  • You have messy account/contact relationships with cross-account duplicates that require dedup logic before import

A small Thena workspace with clean identities and limited attachment history is a reasonable in-house project. A Slack-heavy workspace with internal threads, shared contacts across multiple accounts, old data that must remain searchable, or a hard no-downtime requirement is where migrations stop being "write a script" and start being an operations project.

Frequently Asked Questions

Is there a native migration tool from Thena to Kustomer?
No. There is no built-in connector, no CSV import that preserves conversation threading, and no third-party tool supporting Thena as a source and Kustomer as a target. You must build a custom API-based ETL pipeline using Thena's Platform API and Kustomer's REST API.
How do Thena objects map to Kustomer's data model?
Thena Accounts become Kustomer Companies. Thena Contacts become Kustomer Customers (deduplicated by email). Thena Requests become Kustomer Conversations with Messages nested underneath. AI metadata (title, summary, tags, urgency, sentiment) should be stored as custom attributes on the Kustomer Conversation Klass.
How do I handle rate limits when importing into Kustomer?
Include the importedAt field in every Conversation and Message payload. This bypasses Kustomer's per-customer object rate limit of 120 creations per minute. Your binding constraint becomes the global API rate limit, which ranges from 300 rpm (Professional) to 2,000 rpm (Ultimate).
What happens to Thena's AI-generated metadata during migration?
Thena's AI metadata (title, summary, category, urgency, sentiment, tags) has no native Kustomer equivalent. Define custom attributes on Kustomer's Conversation Klass before import using Kustomer's naming convention (Str, Txt, Bool suffixes), then populate them during the load phase.
How long does a Thena to Kustomer migration take?
Budget 84–136 engineer-hours and 2–4 weeks elapsed time for a mid-size dataset (10K–50K requests). The main time sinks are the transform layer (Slack mrkdwn conversion, identity resolution) and validation/QA. Extraction and loading are time-gated by API rate limits on both sides.

More from our Blog