Kustomer to Missive Migration: The Technical Guide
Technical guide to migrating from Kustomer to Missive. Covers API constraints, KObject flattening, custom channel loading, rate limits, and step-by-step ETL process.
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
Kustomer to Missive Migration: The Technical Guide
Migrating from Kustomer to Missive is a data model compression problem. Kustomer is a CRM-first helpdesk built around a customer-centric timeline with extensible custom objects (KObjects). Missive is a collaborative team inbox — lean, conversation-first, with no custom object layer. There is no native migration path between these platforms: no import wizard, no built-in connector, no one-click transfer.
The practical path is Kustomer REST API extraction → transformation layer → Missive REST API loading. Contacts can also be imported via CSV, but conversation history requires Missive's custom channel Messages API or Posts API. This guide covers the exact object mapping, API constraints, extraction strategies, and failure modes you'll hit.
For general scoping guidance, see the Help Desk Data Migration Playbook. For Missive-specific pre-flight checks, see the Missive Migration Checklist.
Why Teams Migrate from Kustomer to Missive
The migration drivers fall into four categories:
- Cost structure mismatch. Kustomer's per-seat pricing on Enterprise ($89/agent/month) and Ultimate ($139/agent/month) tiers creates a significant gap versus Missive's per-user pricing ($14–$36/user/month as of 2024). A 30-agent team on Kustomer Enterprise paying ~$32,000/year could run Missive's Productive plan at ~$12,960/year — a ~60% reduction in licensing cost alone. The savings scale linearly with headcount.
- CRM weight without CRM use. Many teams don't use Kustomer's CRM depth — KObjects, custom timelines, workflow builders. Missive's lighter model (shared inbox + labels + rules) fits teams that want fast collaboration without configuration overhead. If your CRM and marketing automation already live in HubSpot, Salesforce, or a similar system, Kustomer's built-in CRM layer is redundant.
- Agency and multi-client workflows. Missive's multi-organization model and per-conversation collaboration features (shared drafts, @mentions, internal comments) work well for agencies managing multiple client brands from one account.
- Channel consolidation without routing overhead. Missive unifies email, SMS, WhatsApp, Messenger, Instagram, and live chat in a single collaborative inbox — appealing for teams that don't need Kustomer's queue-based routing and SLA enforcement.
If Kustomer is still your system of record for customer context via KObjects, you need to plan where that data goes before you start. That decision shapes the entire migration architecture.
Core Data Model Differences
Understanding the architectural gap is the first step. Get this wrong and your field mapping will silently lose data.
| Concept | Kustomer | Missive |
|---|---|---|
| Architecture | Customer-centric timeline | Conversation-centric inbox |
| Primary record | Customer (with timeline of all interactions) | Conversation (with messages and comments) |
| Conversations | Hang off Customer records | Top-level objects |
| Companies | Company object (linked to Customers) | Contact Organizations (linked to Contacts) |
| Custom objects | KObjects with Klasses (up to ~500 attributes per Klass) | None — use contact custom fields or external systems |
| Tags/Categories | Conversation tags, customer tags | Shared labels (conversations), contact groups |
| Internal notes | Notes on conversations | Comments on conversations |
| Canned responses | Snippets | Responses |
| Workflows/Rules | Business Rules, Workflows (complex visual logic builder with branching) | Rules (condition/action pairs, no branching or multi-step logic) |
| Custom fields | Custom attributes on any object (Customer, Conversation, Company, KObject) | Custom info fields on contacts only |
The biggest structural loss: Kustomer's KObject layer has no Missive equivalent. If your Kustomer instance has custom objects for orders, subscriptions, shipments, or policies, that data either stays in an external system, gets flattened into contact notes/custom fields, or doesn't migrate.
Migration Approaches
1. CSV-Based Contact Import
How it works: Export contacts from Kustomer (via reporting exports or API), transform to Google or Outlook CSV format, import into Missive via the built-in CSV contact import. If the original email mailbox still contains your message history, connect it directly to Missive so email history is pulled from the mailbox rather than replayed from Kustomer.
When to use: Contact-only migrations, or when your history is mostly email and the source mailbox still contains it.
Pros: Simple, no code required for the import step. Missive natively supports CSV contact imports.
Cons: Only covers contacts — no conversations, messages, or history. Kustomer CSV exports from reporting are limited to 30-day windows and 25 attributes. Kustomer CSV/reporting exports contain conversation attributes and message previews, not full message content (help.kustomer.com). Kustomer saved search exports cap at 50,000 rows.
Complexity: Low | Scalability: Fine for under 50K contacts.
2. API-Based Migration (Kustomer API → Missive API)
How it works: Extract data from Kustomer's REST API (/v1/customers, /v1/conversations, /v1/messages, /v1/companies), transform it to match Missive's schema, and load via Missive's REST API. Historical messages are imported through a Missive custom channel using POST /v1/messages. Labels, assignments, and conversation state are applied via POST /v1/posts and PATCH /v1/conversations.
When to use: Any migration where you need conversation history, message bodies, attachments, or structured contact data.
Pros: Full access to message bodies, metadata, and attachments. Preserves conversation threading. Can be incremented and re-run.
Cons: Requires engineering effort. Rate limits on both sides constrain throughput. Missive has no native "ticket import" endpoint — conversations are created implicitly when you post a message through a custom channel. Attachments must be base64-encoded (10 MB payload limit per request).
Complexity: High | Scalability: Works for small to enterprise datasets, but large migrations (100K+ conversations) require careful batching and rate-limit management.
API access requirements: Missive's REST API is available on all paid plans (Starter, Productive, Business). API tokens are generated per-user under Preferences → API. Kustomer API access requires an Enterprise or Ultimate plan with API keys created under Settings → Security → API Keys.
3. Middleware/iPaaS (Zapier, Make)
How it works: Use Zapier or Make to connect Kustomer triggers to Missive actions. Zapier publicly exposes app actions for both Kustomer and Missive. Make has a verified Missive app and a generic HTTP toolkit (zapier.com).
When to use: Ongoing contact sync between systems, or very small one-time transfers (under 1,000 records).
Pros: No code required. Missive has native Zapier integration for contact creation and conversation actions.
Cons: Not designed for bulk historical migration. No Kustomer trigger for "all existing records." Per-task pricing makes large volumes expensive (at Zapier's Professional tier, 50K tasks costs ~$250/month). Cannot migrate conversation history in bulk. No attachment support in most Zapier/Make templates.
Complexity: Low | Scalability: Poor for migration; fine for ongoing sync.
4. Third-Party Migration Tools
How it works: Using an automated SaaS tool to map standard fields and run the migration. Help Desk Migration, for example, publicly lists both Kustomer and Missive in its supported ecosystem (help-desk-migration.com).
When to use: When you have standard data, low engineering bandwidth, and mostly standard objects.
Pros: Quicker start, less custom code. Typically includes a free demo migration of a sample dataset.
Cons: Support matrices don't tell you how well a tool handles Kustomer KObjects, Missive custom channels, or oversized threads. Always run the sample migration against your actual data — the demo dataset matters more than the feature page.
Complexity: Medium | Scalability: SMB to mid-market.
5. Custom ETL Pipeline / Managed Service
How it works: A dedicated extraction, transformation, and loading pipeline using Python, Node.js, or a data engineering tool. Uses both platform APIs but adds orchestration, error handling, retry logic, and state management. Can be built in-house or outsourced to a migration partner.
When to use: Large-scale migrations (50K+ conversations), complex KObject schemas, compliance requirements, or when engineering bandwidth is limited and the timeline is tight.
Pros: Full control over transformation logic. Supports incremental runs with checkpoint/resume. Enables parallel extraction and loading within rate limits. Audit trail and rollback capability.
Cons: Highest engineering effort if built in-house. Requires knowledge of both APIs' quirks.
Complexity: High (DIY) / Low (managed) | Scalability: Enterprise-grade.
Approach Comparison
| Approach | Conversations | Contacts | Attachments | KObjects | Complexity | Best For |
|---|---|---|---|---|---|---|
| CSV import | ❌ | ✅ | ❌ | ❌ | Low | Contact-only |
| API-based | ✅ | ✅ | ✅ | Partial | High | Full migration with history |
| Zapier/Make | ❌ | ✅ | ❌ | ❌ | Low | Ongoing sync |
| Third-party tool | ✅ | ✅ | Varies | Rarely | Medium | Standard schemas |
| Custom ETL / Managed | ✅ | ✅ | ✅ | Partial | High / Low | Enterprise scale |
Recommendations by Scenario
- Small team (<20 agents, <10K conversations): API-based migration if you have a developer available for 2–3 weeks. CSV for contacts if you only need contact data and can connect your email mailbox directly to Missive for history.
- Mid-market (20–100 agents, 10K–100K conversations): Custom ETL pipeline or managed migration service.
- Enterprise (100+ agents, 100K+ conversations, KObjects): Managed migration service. The KObject flattening, parallel extraction orchestration, and rate-limit management alone justify external expertise.
- Ongoing sync after cutover: Zapier/Make for contacts, or API-based webhook listeners for real-time conversation sync.
When to Use a Managed Migration Service
DIY migrations fail for predictable reasons:
- Broken relationships. Kustomer's Customer → Conversation → Message → Note chain must be preserved in Missive's Conversation → Message → Comment model. One broken link and you lose thread context.
- Data loss from CSV exports. Kustomer's CSV exports only include message previews, not full message bodies. Teams that export via CSV and discover this after decommissioning Kustomer lose their support history permanently.
- Rate-limit miscalculation. At Missive's sustained rate of ~1 request/second (300 per minute, 900 per 15 minutes), importing 50K conversations with an average of 5 messages each takes roughly 70 hours of continuous API calls — before retries. On the extraction side, Kustomer Enterprise allows 1,000 RPM, which can be parallelized across multiple extraction workers — but the Missive loading side remains the bottleneck.
- Idempotency failures. If a migration script crashes at message 47,000 of 250,000, you need to resume without duplicating the first 47,000. This requires external state tracking (checkpoint files or a database),
external_iddeduplication, and the ability to query Missive for existing records — none of which are trivial.
Disclosure: ClonePartner (the author of this guide) provides managed helpdesk migration services, including Kustomer-to-Missive migrations. The technical content in this guide is accurate regardless of which migration approach you choose. If you want to evaluate managed options, see how we run migrations.
Pre-Migration Planning
Data Audit Checklist
Before touching an API key, inventory your Kustomer instance:
- Customers — Total count, active vs. archived, fields in use
- Companies — Count, linked customers per company
- Conversations — Total count, date range, channels (email, chat, SMS, social)
- Messages — Total count, average per conversation, attachment count and total size
- Notes — Internal notes on conversations
- KObjects — Types (Klasses), record counts per type, fields per Klass
- Tags — Conversation tags, customer tags, count of each
- Teams — Team structure, agent assignments
- Snippets — Canned responses count
- Workflows/Business Rules — Document manually (cannot be exported via API)
- Knowledge Base — Articles and categories (Missive has no KB; plan an alternative)
Also review merged customers — Kustomer exposes merge history through the /v1/customers/{id}/merges endpoint. Sample this before you finalize dedupe rules to avoid re-creating duplicate identities in Missive.
Identify What Not to Migrate
Not everything should move. Common exclusions:
- Conversations older than 3–5 years (archive to cold storage instead)
- Spam, test, or auto-generated conversations
- Deleted or merged customer records (use merge history to identify these)
- KObjects that are better served by a dedicated system (orders → your OMS, subscriptions → your billing system)
- Workflows and business rules (must be rebuilt as Missive Rules regardless)
Kustomer Data Retention After Cancellation
Kustomer's terms state that customer data is deleted after subscription termination, but the exact retention window is not publicly documented and varies by contract. Do not assume your data will be accessible after cancellation. Extract everything via API before starting the cancellation process, and keep your Kustomer subscription active for at least 30 days post-migration to allow for validation and re-extraction.
Migration Strategy
| Strategy | When to Use | Risk Level |
|---|---|---|
| Big bang | Small dataset (<10K conversations), short cutover window acceptable | Medium |
| Phased | Multiple teams or channels, migrate one at a time | Low |
| Incremental | Must keep both systems live during migration | Low, but complex |
For most Kustomer-to-Missive moves, a phased approach works well: migrate contacts first, then historical conversations, then cut over live channels. For smaller teams, a big bang cutover over a weekend avoids split-brain data scenarios.
For deeper Kustomer export guidance, see How to Export Data from Kustomer.
Data Model & Object Mapping
This is where migrations succeed or fail. Kustomer's rich CRM model must be compressed into Missive's leaner structure.
Object-Level Mapping
| Kustomer Object | Missive Equivalent | Notes |
|---|---|---|
| Customer | Contact | Map to Missive contact in a shared contact book |
| Company | Contact Organization | Use memberships array with kind: "organization" |
| Conversation | Conversation | Created implicitly via custom channel Messages API |
| Message | Message (custom channel) | Use POST /v1/messages with custom channel account |
| Note | Comment or Post | Internal notes → Comments; system events → Posts |
| KObject | Contact custom fields / Notes / External | No direct equivalent. Flatten key fields into contact infos |
| Tag (conversation) | Shared Label | Create labels first, apply via add_shared_labels |
| Tag (customer) | Contact Group | Use memberships with kind: "group" |
| Team | Team | Create via POST /v1/teams |
| Snippet | Response | Create via POST /v1/responses |
| Agent/User | User | Manual setup — no user creation API in Missive |
Field-Level Mapping: Customer → Contact
| Kustomer Field | Missive Field | Notes |
|---|---|---|
name |
first_name + last_name |
Split on first space; handle single-word names gracefully |
emails [].email |
infos [{kind: "email"}] |
Map all emails; first becomes primary |
phones [].phone |
infos [{kind: "phone_number"}] |
Map all phones |
socials.twitter |
infos [{kind: "twitter"}] |
If present |
urls |
infos [{kind: "url"}] |
Map all URLs |
custom.fieldName |
infos [{kind: "custom", custom_label: "fieldName"}] |
Custom attributes → custom infos |
company |
memberships [{group: {kind: "organization"}}] |
Company association |
tags |
Contact group memberships | kind: "group" |
notes |
notes field on contact |
Free text |
Handling KObjects
KObjects are the hardest part of any Kustomer outbound migration. Missive has no custom object model. Your options, with tradeoffs:
- Flatten into contact notes. Serialize KObject data as structured text in the contact
notesfield. Tradeoff: Readable by agents but not queryable, not filterable, and not automatable. Works for low-volume reference data (e.g., a customer's subscription tier or account manager). - Map key fields to custom infos. Use Missive's
infosarray withkind: "custom"for the most important KObject attributes (e.g.,order_status,subscription_tier). Tradeoff: Visible in the contact sidebar and searchable, but limited to string values only — no dates, numbers, picklists, or validation. A Kustomer KObject Klass with 50 fields becomes 50 string-typed custom infos with no relational integrity. - Keep in an external system. If KObjects represent orders, shipments, or subscriptions, they likely already exist in your OMS or billing system. Use Missive's iFrame sidebar integrations to surface that data contextually. Tradeoff: Best data integrity, but requires the external system to be the source of truth and the iFrame integration to be built.
- Archive to a database. Export KObjects to a PostgreSQL, BigQuery, or S3-backed datastore and link via Kustomer customer ID for historical reference. Tradeoff: Data is preserved but not accessible within Missive's UI without additional tooling.
Recommendation: For most teams, option 3 (external system) for active data combined with option 4 (archive) for historical data produces the cleanest outcome. Options 1 and 2 are workarounds, not solutions.
Missive's infos array with kind: "custom" only supports string values. There are no typed fields (date, number, picklist). If your Kustomer KObjects have structured data types, you'll lose type enforcement in Missive.
Missive Contact Deduplication Behavior
When you POST /v1/contacts with an email address that already exists in the target contact book, Missive's behavior depends on context:
- If you include a
contact_bookID and the email matches an existing contact in that book, the API may return the existing contact or create a duplicate — behavior is not consistently documented. - Safeguard: Before loading, query
GET /v1/contacts?email=user@example.comto check for existence. Build deduplication into your transformation layer, not at the API boundary. Merge Kustomer records that share a primary email before loading.
Migration Architecture
Data Flow
Kustomer REST API ──→ Extract Layer ──→ Transform Layer ──→ Missive REST API
/v1/customers │ │ POST /v1/contacts
/v1/companies │ │ POST /v1/messages
/v1/conversations │ │ POST /v1/posts
/v1/messages │ Map & clean PATCH /v1/conversations
/v1/kobjects │ │ POST /v1/shared_labels
JSON files ID mapping POST /v1/responses
+ attachments table (DB)
Kustomer API: Extraction Constraints
- Authentication: Bearer token via API key. Create under Settings → Security → API Keys.
- Rate limits: Vary by plan. Enterprise: 1,000 RPM for machine users. Ultimate: 2,000 RPM. The limit is org-wide across all API keys. Machine searches have a separate 100 RPM limit (help.kustomer.com).
- Parallel extraction: At 1,000 RPM (Enterprise), you can run 10 concurrent extraction workers at 100 RPM each, or 5 workers at 200 RPM each. Partition by customer ID range or conversation date range. This reduces extraction time for 1M messages from ~17 hours (single-threaded at 1,000 RPM) to the same ~17 hours but with better fault isolation per partition.
- Pagination: Cursor-based using
page [size](max 100) andpage [after]parameters. - Data completeness: CSV exports only include message previews. You must use the API for full message bodies.
- Search constraints: The standard Search endpoint (
POST /v1/customers/search) only returns records updated within the past two years and caps queries at 100 pages (10,000 records at page size 100). For older records, use Archive Search (POST /v1/customers/search/archive), which accepts the same query format but returns results from the full history. Archive Search responses are asynchronous — you submit the query and poll for results. - KObject extraction: Use
GET /v1/klassesto discover schemas, thenGET /v1/customers/{id}/kobjects?klass={klassId}to extract per-customer per-Klass.
Missive API: Loading Constraints
- Authentication: Personal Bearer token from Preferences → API. No org-level tokens exist. The token owner's permissions scope what the API can access. Available on all paid plans (Starter, Productive, Business).
- Rate limits: 5 concurrent requests, 300 per minute, 900 per 15 minutes. Sustained throughput is ~1 request/second (missiveapp.com).
- Conversation creation: No direct "create conversation" endpoint. Conversations are created implicitly when you create a message or post. Use the custom channel Messages API (
POST /v1/messages) with a custom channel account ID. - Contact creation: Batch-capable via
POST /v1/contacts— you can send an array of contact objects. Send contacts in batches of 10–25 to reduce API call count. Each batch counts as one request against the rate limit. - Payload limit: 10 MB per request (relevant for attachment-heavy messages). If a payload exceeds 10 MB, Missive returns HTTP
413 Request Entity Too Large. Split oversized messages: send the text body first, then post attachments individually. - Conversation cap: Missive splits conversations at 400 messages and starts new messages in a separate thread after that (missiveapp.com). Long Kustomer conversations must be segmented during load.
- Custom channel type: Choose carefully. If you need email-like headers (From, To, Subject) and attachment rendering, start with an Email custom channel type. If you choose a different type, the rendering of imported messages will differ — and the custom-channel type cannot be changed after creation. Choosing wrong means you'd need to create a new channel and re-import.
- HTML rendering: Missive uses
<div>tags with<br>for paragraph spacing, not<p>tags. Kustomer's HTML email bodies may need reformatting —<p>content renders but with inconsistent spacing in Missive's UI. - Attachment MIME types: Missive's custom channel accepts standard MIME types for attachments (images, PDFs, office documents, plain text). Binary/unknown MIME types should be tested with a sample before bulk import.
Custom channels in Missive do not currently support the rule engine (missiveapp.com). Migrated historical threads imported via custom channel will not trigger rules. Only new conversations arriving through native channels (email, SMS, chat) will be processed by rules.
Missive Contact Book Scoping
Missive supports multiple contact books: personal, shared within an organization, and shared across organizations. When planning your import:
- Create a shared contact book scoped to your organization for imported contacts.
- Contacts in a shared book are visible to all organization members.
- If you run a multi-org Missive setup (e.g., agencies), each org needs its own contact book — contacts do not cross org boundaries automatically.
- Specify the
contact_bookID in everyPOST /v1/contactscall. Omitting it creates contacts in the API token owner's personal book, which is not visible to the team.
Rate-Limit Arithmetic
At Missive's sustained rate of ~1 request/second:
| Dataset | Estimated API Calls | Time at 1 req/sec | Time at 5 concurrent |
|---|---|---|---|
| 5K contacts (batched 20/call) | 250 | ~4 min | ~1 min |
| 5K contacts (individual) | 5,000 | ~1.5 hours | ~20 min |
| 10K conversations (5 msgs avg) | 50,000 | ~14 hours | ~3 hours* |
| 50K conversations (5 msgs avg) | 250,000 | ~70 hours | ~14 hours* |
| 100K conversations (5 msgs avg) | 500,000 | ~140 hours | ~28 hours* |
*Concurrent throughput assumes 5 parallel requests within the 300/min and 900/15-min limits. Actual throughput depends on retry rates and payload sizes. Plan for 1.5–2× these estimates to account for retries.
Plan for 24/7 execution over multiple days for large datasets.
Error Taxonomy
Common API errors and correct handling:
| Error Code | Source | Cause | Handling |
|---|---|---|---|
429 |
Both | Rate limit exceeded | Exponential backoff: wait 1s, 2s, 4s, 8s… up to 60s. Kustomer returns Retry-After header. |
413 |
Missive | Payload exceeds 10 MB | Split: send message body first, then attachments individually |
422 |
Missive | Malformed payload (missing required field, invalid kind) |
Log full request/response, fix transformation logic, do not retry without fixing |
422 |
Kustomer | Invalid search query or filter | Check query syntax against Kustomer Search API docs |
401 |
Both | Expired or invalid token | Regenerate token; Kustomer API keys don't expire but can be revoked |
404 |
Kustomer | Record deleted or merged since extraction | Log and skip; check merge history for alternate ID |
500/502/503 |
Both | Server error | Retry with exponential backoff, max 3 retries, then dead-letter queue |
Implement a dead-letter queue for records that fail after 3 retries. Review and reprocess these manually after the bulk migration completes.
Step-by-Step Migration Process
Step 1: Set Up the Missive Target Environment
- Create your Missive organization and invite all team members
- Set up a custom channel for historical message import — choose Email type if you need email-like headers (From, To, Subject) and attachment rendering
- Create a shared contact book for imported contacts (note the contact book ID)
- Create shared labels that map to your Kustomer conversation tags
- Create teams matching your Kustomer team structure
- Generate a Missive API token (Preferences → API → Create a new token)
- Verify API access with a test request:
GET https://public.missiveapp.com/v1/organizations
Step 2: Extract Data from Kustomer
Extract in dependency order: Companies → Customers → Conversations → Messages → Notes → KObjects.
Use Kustomer's Search API to iterate through conversations in stable date windows. For records older than two years, switch to Archive Search.
import requests
import time
import json
import os
KUSTOMER_API = "https://api.kustomerapp.com/v1"
HEADERS = {"Authorization": "Bearer YOUR_KUSTOMER_API_KEY"}
def extract_customers(output_dir="./extract/customers"):
os.makedirs(output_dir, exist_ok=True)
customers = []
cursor = None
page = 0
while True:
params = {"page[size]": 100}
if cursor:
params["page[after]"] = cursor
resp = requests.get(
f"{KUSTOMER_API}/customers",
headers=HEADERS, params=params
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue
if resp.status_code != 200:
print(f"Error {resp.status_code}: {resp.text}")
break
data = resp.json()
batch = data.get("data", [])
if not batch:
break
customers.extend(batch)
cursor = batch[-1]["id"]
page += 1
# Checkpoint every 100 pages
if page % 100 == 0:
with open(f"{output_dir}/checkpoint_{page}.json", "w") as f:
json.dump({"cursor": cursor, "count": len(customers)}, f)
time.sleep(0.06) # ~16 req/sec, well under 1000 RPM
return customersFor conversation search with date windowing:
def search_conversations(start_date, end_date):
"""Use standard search for last 2 years; archive search for older."""
search_body = {
"queryContext": "conversation",
"and": [
{"conversation_updated_at": {"gte": start_date}},
{"conversation_updated_at": {"lte": end_date}}
],
"sort": [{"conversation_updated_at": "asc"}],
"timeZone": "UTC"
}
# Standard search: POST /v1/conversations/search
# Archive search: POST /v1/conversations/search/archive
# Archive search is async — returns a job ID, poll for results
endpoint = f"{KUSTOMER_API}/conversations/search"
resp = requests.post(endpoint, headers=HEADERS, json=search_body)
return resp.json()Download all attachments during extraction. Kustomer attachment URLs are signed and expire (typically within hours). You cannot pass URL strings to Missive — you must download the physical file and store it locally for re-upload as base64 in the Missive payload.
def download_attachment(url, dest_path):
resp = requests.get(url, stream=True)
if resp.status_code == 200:
with open(dest_path, "wb") as f:
for chunk in resp.iter_content(8192):
f.write(chunk)
return True
return False # URL may have expired — log and flagStep 3: Transform Data
Key transformations:
- Split names: Kustomer stores
nameas a single field. Split intofirst_nameandlast_namefor Missive. Handle edge cases: single-word names (put infirst_name, leavelast_nameempty), names with multiple spaces (split on first space only). - Map emails/phones: Kustomer's
emailsarray → Missive'sinfosarray withkind: "email". - Map companies: Kustomer company associations → Missive
membershipswithkind: "organization". - Map tags: Kustomer tags → Missive contact group memberships or conversation shared labels.
- Flatten KObjects: Extract key fields into
infoswithkind: "custom". Convert all values to strings. - Build ID map: Maintain a mapping table (SQLite or PostgreSQL) of Kustomer IDs → Missive IDs for relationship preservation. Include a
statuscolumn (pending/created/failed) for checkpoint/resume. - Reformat HTML: Convert
<p>...</p>tags to<div>...</div>with<br>for Missive's rendering engine. Strip Kustomer-specific HTML classes or inline styles that won't render in Missive. - Deduplicate contacts: Group Kustomer customers by primary email. Merge custom fields from duplicates (choose latest value or concatenate). Check Kustomer merge history to identify records that were already consolidated.
Step 4: Load Contacts into Missive
MISSIVE_API = "https://public.missiveapp.com/v1"
MISSIVE_HEADERS = {
"Authorization": "Bearer YOUR_MISSIVE_TOKEN",
"Content-Type": "application/json"
}
CONTACT_BOOK_ID = "your-contact-book-id"
def create_contacts_batch(customers_batch):
"""Send up to 20 contacts per request."""
contacts = []
for customer in customers_batch:
contacts.append({
"contact_book": CONTACT_BOOK_ID,
"first_name": customer.get("first_name", ""),
"last_name": customer.get("last_name", ""),
"infos": build_infos(customer),
"memberships": build_memberships(customer),
"notes": customer.get("notes", "")
})
payload = {"contacts": contacts}
resp = requests.post(
f"{MISSIVE_API}/contacts",
headers=MISSIVE_HEADERS, json=payload
)
if resp.status_code == 429:
time.sleep(1)
return create_contacts_batch(customers_batch)
if resp.status_code == 422:
# Log the full error for debugging; do not retry
print(f"Validation error: {resp.text}")
return None
return resp.json()Store the resulting Missive Contact IDs alongside the Kustomer Customer IDs in your mapping database. For checkpoint/resume:
def save_mapping(kustomer_id, missive_id, status="created"):
# Insert into your mapping DB (SQLite example)
cursor.execute(
"INSERT OR REPLACE INTO id_map VALUES (?, ?, ?)",
(kustomer_id, missive_id, status)
)Step 5: Load Conversations via Custom Channel
For each Kustomer conversation, create a Missive conversation by posting messages through your custom channel. Sort messages chronologically and thread them into the same conversation.
def import_conversation(conv, messages, custom_channel_id):
messages.sort(key=lambda m: m["delivered_at"])
conversation_id = None
external_conv_id = f"kustomer-conv-{conv['id']}"
# Check if this conversation was already partially imported
existing = get_mapping(conv["id"])
if existing and existing["status"] == "created":
return existing["missive_id"] # Already done
if existing and existing["missive_id"]:
conversation_id = existing["missive_id"] # Resume
for i, msg in enumerate(messages):
external_msg_id = f"kustomer-msg-{msg['id']}"
# Skip if this message was already imported
if message_exists(external_msg_id):
continue
payload = {
"messages": {
"account": custom_channel_id,
"from_field": {
"id": msg["from_id"],
"name": msg["from_name"],
"username": msg["from_email"]
},
"body": msg["body"],
"delivered_at": msg["delivered_at"],
"external_id": external_msg_id,
"close": (i == len(messages) - 1
and conv["status"] == "done")
}
}
if conversation_id:
payload["messages"]["conversation"] = conversation_id
resp = requests.post(
f"{MISSIVE_API}/messages",
headers=MISSIVE_HEADERS, json=payload
)
time.sleep(1) # Respect rate limits
if resp.status_code == 429:
time.sleep(5)
# Retry this message (add proper retry logic)
continue
if not conversation_id and resp.ok:
result = resp.json()
conversation_id = result.get("messages", {})\
.get("conversation", {}).get("id")
save_mapping(conv["id"], conversation_id, "in_progress")
if conversation_id:
save_mapping(conv["id"], conversation_id, "created")
return conversation_idFor conversations with more than 380 messages, split into segments. Use deterministic external IDs (e.g., kustomer-conv-123-part-1, kustomer-conv-123-part-2) and add a message at the boundary: "Continued in Part 2 — [original Kustomer conversation ID: 123]". Missive caps conversations at 400 messages and starts a new thread after that limit. Leave headroom below the cap for future replies.
Step 6: Apply Labels, Assignments, and Close State
After conversations exist in Missive, apply metadata:
def apply_metadata(conversation_id, labels, assignees, is_closed):
payload = {
"conversations": [{
"id": conversation_id,
"organization": ORG_ID,
"add_shared_labels": labels,
"add_assignees": assignees,
"close": is_closed
}]
}
resp = requests.patch(
f"{MISSIVE_API}/conversations/{conversation_id}",
headers=MISSIVE_HEADERS, json=payload
)
if resp.status_code == 422:
print(f"Metadata error for {conversation_id}: {resp.text}")
return respStep 7: Migrate Snippets → Responses
Export Kustomer snippets, then create Missive responses via API:
def create_response(snippet):
payload = {
"responses": [{
"title": snippet["name"],
"body": snippet["body"],
"organization": ORG_ID
}]
}
resp = requests.post(
f"{MISSIVE_API}/responses",
headers=MISSIVE_HEADERS, json=payload
)
return resp.json()Step 8: Validate
Compare record counts, spot-check field values, and verify conversation threading. See the validation section below.
Edge Cases & Challenges
Duplicate Contacts
Kustomer allows multiple customer records with the same email. Missive contacts within a shared contact book can also have duplicate emails — but this creates confusion in the UI when agents look up a customer. Before loading, deduplicate by primary email and merge custom fields from duplicate records. Check Kustomer's merge history via the API (GET /v1/customers/{id}/merges) to avoid re-creating identities that were already consolidated.
Multi-Channel Conversations
Kustomer conversations can span email, chat, SMS, and social in a single thread. When importing via Missive's custom channel, all messages render as custom channel messages regardless of original channel. Preserve original channel metadata in the message body header (e.g., " [Originally received via SMS]") or as a post annotation so agents have context.
Missing Authors
If a Kustomer agent left the company, their ID won't exist in Missive. Map their historical messages to the from_field name from Kustomer (preserving the original agent name in the rendered message) rather than linking to a Missive user account. Alternatively, create a shared "Legacy Agent" identity in the from_field and include the original agent name in the message body.
Attachments
Kustomer attachment URLs are signed and expire. Download all attachments during extraction and re-upload as base64 in Missive's message payload. The 10 MB payload limit per request means attachments larger than ~7 MB (after base64 encoding inflates size by ~33%) must be handled individually — send the message text first, then the attachment in a separate API call within the same conversation.
Inline Images
Images embedded in HTML message bodies (using <img src="...">) must be parsed, downloaded, and re-uploaded. Either convert them to Missive-hosted attachments and rewrite the src URLs, or base64-encode them inline (which inflates payload size significantly). Test with a sample conversation before bulk processing.
Conversation State Mapping
Kustomer conversation statuses map imperfectly to Missive states:
| Kustomer Status | Missive Equivalent | Implementation |
|---|---|---|
open |
Team inbox / Inbox | Default state after message creation |
snoozed |
No API equivalent | Import as closed with a "Previously Snoozed" label |
done |
Closed | Set close: true on final message/post |
waitingForCustomer |
No direct equivalent | Import as open with "Waiting for Customer" label |
Missive's API cannot set the "snoozed" state on conversations programmatically. Snoozed conversations from Kustomer should be imported as closed with a "Previously Snoozed" label for agent visibility.
Oversized Threads
Missive splits conversations at 400 messages. Long Kustomer conversations (common in enterprise support with ongoing technical issues) must be segmented during load with deterministic references to keep parts linked and findable. Add a shared label (e.g., "Multi-part Thread") to all segments for easy retrieval.
Limitations & Constraints
What Missive Cannot Replicate from Kustomer
| Kustomer Feature | Missive Status | Workaround |
|---|---|---|
| KObjects/Custom Objects | No equivalent | Flatten to contact infos, externalize, or archive |
| Customer Timeline View | Per-conversation only | Search by contact email to see all conversations |
| SLA Policies | No native SLA tracking | Labels + rules + external analytics |
| Workflow Builder | Rules only (no branching) | Simplify logic or move to Zapier/Make/n8n |
| CSAT Surveys | No native CSAT | Integrate Nicereply, Klaus, or custom survey tool |
| Snoozed State via API | Cannot be set programmatically | Use "Snoozed" label |
| Archive via API | No archive parameter | Use closed state |
| Custom Channel Rule Triggers | Not supported | Rules only fire on native channel conversations |
Missive API Constraints Summary
- No bulk conversation import. Each message is a separate API call (contacts accept arrays, but conversations/messages do not).
- Personal tokens only. All API tokens are personal, not org-level. Token revocation happens if the user is removed from the org.
- Message body rendering. HTML requires
<div>tags with<br>for spacing;<p>tags render with inconsistent spacing. - Conversation list requires a filter. You cannot list all conversations without specifying
inbox,closed,team_inbox, etc. - 10 MB payload limit per request, including base64-encoded attachments.
- No conversation delete API. If test data is loaded into production, it must be manually deleted from the UI.
Validation & Testing
Record Count Comparison
| Object | Kustomer Count | Missive Count | Delta | Status |
|---|---|---|---|---|
| Customers/Contacts | X | X | 0 | ✅ |
| Companies/Organizations | X | X | 0 | ✅ |
| Conversations | X | X | 0 | ✅ |
| Messages (total) | X | X | 0 | ✅ |
| Labels/Tags | X | X | 0 | ✅ |
| Responses/Snippets | X | X | 0 | ✅ |
Field-Level Validation
For a random sample of 50–100 records:
- Verify contact names, emails, and phone numbers match
- Verify conversation message counts match
- Verify message bodies are complete (not truncated previews)
- Verify attachments are accessible and downloadable
- Verify internal notes remained internal (comments) and external emails remained external (messages)
- Verify labels are correctly applied
- Verify contact → organization associations are intact
- Verify KObject-derived custom infos contain expected values
UAT Process
- Agent spot-check: Have 3–5 agents search for known customers and verify their conversation history renders correctly in Missive.
- Search validation: Confirm Missive's search returns expected results for key customer emails and label filters.
- Rule testing: Verify newly created Missive rules trigger correctly on test conversations sent through native channels (not the custom import channel).
- Response templates: Confirm all migrated snippets/responses are accessible and formatted correctly.
- Multi-part thread check: For any conversations that were split due to the 400-message cap, verify all parts are findable and the linking metadata is correct.
For a comprehensive QA checklist, see Post-Migration QA Tests.
Rollback Planning
Missive has no native rollback mechanism (no bulk delete API, no "undo import"). Before production migration:
- Export a full backup of your Kustomer data via API and store raw JSON files as your source of truth
- Keep your Kustomer subscription active for at least 30 days post-migration
- Document the ID mapping table so Missive records can be traced to Kustomer originals
- Define the exact trigger that sends mail flow back to the old stack if QA fails (e.g., reconnect email accounts to Kustomer, revert DNS MX records if applicable)
- If rollback is needed, the Missive test data must be manually cleaned or the org reset — plan for this scenario
Post-Migration Tasks
Rebuild Automations
Kustomer's Business Rules and Workflows cannot be exported via API. Document them manually before migration, then rebuild as Missive Rules. Key differences:
- Missive rules trigger on incoming/outgoing messages, label changes, and contact membership — not on custom object events or conversation field changes.
- Missive rules support single condition/action pairs. Kustomer's multi-step, branching workflows must be decomposed into discrete rules or offloaded to an external automation platform (Zapier, Make, n8n).
- Missive supports webhook actions in rules, enabling integration with external automation platforms for complex logic.
- Round-robin and random assignment are supported natively in Missive teams.
Channel Cutover
- Connect email accounts (Gmail, Outlook, IMAP) to Missive
- Set up SMS via Twilio or SignalWire integration
- Connect WhatsApp Business, Facebook Messenger, and Instagram channels
- Set up Missive Live Chat on your website
- Verify all channels route to the correct teams
- Disconnect channels from Kustomer (after QA sign-off)
Agent Training
Missive's UX is fundamentally different from Kustomer. Key training areas:
- Shared inbox model vs. Kustomer's customer timeline model — agents now navigate by conversation, not by customer
- Comments (internal) vs. messages (external) — the distinction replaces Kustomer's "Note" type
- Labels as the primary categorization tool (replacing Kustomer tags and custom field filters)
- Rules for automation (simpler than Kustomer workflows; set expectations accordingly)
- Keyboard shortcuts — Missive is keyboard-driven for power users (e.g.,
Eto close,Rto reply,Lto label) - Difference between live mailboxes and migrated historical threads — historical threads from the custom channel won't trigger rules and may render slightly differently
Update Integrations
Reconnect Shopify, Jira, Stripe, or other integrations to Missive. If KObjects were sourcing data from these systems, reconfigure Missive's iFrame sidebar integrations to surface that data contextually. Missive's iFrame integration supports passing the current contact's email or conversation ID to the embedded app for dynamic lookups.
Monitor Post-Launch
Watch for the following in the first 48–72 hours post-launch:
- API error logs from any ongoing sync processes
- Split threads that should be unified (check conversations near the 400-message boundary)
- Incorrect assignees (verify agent ID mapping held)
- Missing attachments (check for expired Kustomer URLs that weren't downloaded during extraction)
- Rule misfires or missed triggers on native channels
- Agent-reported formatting issues in migrated message bodies
Best Practices
- Backup first. Export all Kustomer data via API before starting. Keep raw JSON files and downloaded attachments as your source of truth.
- Run a test migration. Migrate 100 customers and 500 conversations to a separate Missive organization (not your production org). Validate before scaling.
- Delta migrations. Do a full historical load 5–7 days before go-live. On cutover day, only migrate the "delta" — tickets created or updated since the historical load. This minimizes cutover downtime.
- Validate incrementally. Check record counts after each phase (contacts, conversations, labels), not just at the end.
- Preserve the ID map. Store the Kustomer ID → Missive ID mapping permanently in a database. You'll need it for debugging, post-migration data requests, and any compliance inquiries.
- Build idempotent scripts. Use
external_idfields where supported. If the script crashes at record 47,000, you should be able to rerun from that checkpoint without creating duplicates. - Don't cancel Kustomer prematurely. Keep your subscription active until you've validated the migration and confirmed no data gaps. Kustomer's data retention after cancellation is contract-dependent and not publicly guaranteed — once your data is deleted, it's gone.
- Handle rate limits gracefully. Implement exponential backoff on 429 responses (1s → 2s → 4s → 8s, max 60s). Log every failed request for retry. Use a dead-letter queue for records that fail after 3 attempts.
- Reformat HTML carefully. Convert
<p>tags to<div>with<br>for Missive's rendering. Test 10 representative email bodies in Missive's UI before bulk import — especially emails with tables, inline images, or rich formatting. - Segment long threads. Split conversations approaching the 400-message cap during load, leaving at least 20 messages of headroom for future replies.
Summary
A Kustomer-to-Missive migration is a net simplification of your support stack. You're trading Kustomer's CRM depth for Missive's collaborative speed. The technical challenge isn't complexity — it's compression. You need to decide what data survives the transition and what gets archived or externalized.
If your team primarily uses Kustomer for conversation management and doesn't rely heavily on KObjects or complex workflows, the migration is straightforward with the API-based approach. If you have deep KObject schemas, sophisticated workflow logic, or SLA-dependent operations, plan extra time for the transformation layer and accept that some Kustomer capabilities won't have direct equivalents in Missive.
The critical path items: extract via API (not CSV) to get full message bodies, download all attachments before URLs expire, segment long threads below the 400-message cap, and budget realistic time for the Missive loading bottleneck (~1 request/second sustained).
About this guide: Written by ClonePartner, a managed helpdesk migration service. We handle Kustomer-to-Missive migrations end-to-end, including KObject mapping, attachment handling, and rate-limit orchestration. If you want to evaluate whether a managed approach fits your timeline and budget, book a 30-minute scoping call — no obligation.
Frequently Asked Questions
- Can I migrate conversation history from Kustomer to Missive via CSV?
- No. Kustomer's CSV exports only include message previews, not full message bodies. You must extract conversations through Kustomer's REST API and load them into Missive using the custom channel Messages API. Missive's native CSV import only supports contacts, not conversation history.
- What happens to Kustomer KObjects when migrating to Missive?
- Missive has no custom object model equivalent to KObjects. Your options are: flatten key KObject fields into Missive contact custom fields (infos with kind: custom), store KObject data in contact notes as structured text, keep the data in an external system and surface it via Missive's iFrame integrations, or archive to a separate database.
- How long does a Kustomer to Missive migration take?
- It depends on data volume. Missive's API sustains roughly 1 request per second (300 per minute, 900 per 15 minutes). A migration of 10K conversations with 5 messages each takes approximately 14 hours of continuous API calls. Datasets of 50K+ conversations can take multiple days of 24/7 execution. Add 1–2 weeks for planning, testing, and validation.
- What Missive limit breaks long Kustomer conversation threads?
- Missive caps conversations at 400 messages and automatically starts a new thread after that limit. Long Kustomer conversations should be segmented during load using deterministic thread references (e.g., kustomer-conv-123-part-1) with a safety buffer below the 400-message cap for future replies.
- How do I migrate Kustomer conversations older than two years?
- Use Kustomer's Archive Search plus API extraction. The standard Search endpoint only returns records updated within the past two years and caps queries at 100 pages. Archive Search is required for older historical data.



