Skip to content

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.

Wahab Wahab · · 28 min read
Kustomer to Missive Migration: The 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

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_id deduplication, and the ability to query Missive for existing records — none of which are trivial.
Info

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:

  1. Flatten into contact notes. Serialize KObject data as structured text in the contact notes field. 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).
  2. Map key fields to custom infos. Use Missive's infos array with kind: "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.
  3. 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.
  4. 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.

Warning

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_book ID 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.com to 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) and page [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/klasses to discover schemas, then GET /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.
Info

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_book ID in every POST /v1/contacts call. 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

  1. Create your Missive organization and invite all team members
  2. Set up a custom channel for historical message import — choose Email type if you need email-like headers (From, To, Subject) and attachment rendering
  3. Create a shared contact book for imported contacts (note the contact book ID)
  4. Create shared labels that map to your Kustomer conversation tags
  5. Create teams matching your Kustomer team structure
  6. Generate a Missive API token (Preferences → API → Create a new token)
  7. 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 customers

For 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 flag

Step 3: Transform Data

Key transformations:

  • Split names: Kustomer stores name as a single field. Split into first_name and last_name for Missive. Handle edge cases: single-word names (put in first_name, leave last_name empty), names with multiple spaces (split on first space only).
  • Map emails/phones: Kustomer's emails array → Missive's infos array with kind: "email".
  • Map companies: Kustomer company associations → Missive memberships with kind: "organization".
  • Map tags: Kustomer tags → Missive contact group memberships or conversation shared labels.
  • Flatten KObjects: Extract key fields into infos with kind: "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 status column (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_id
Warning

For 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 resp

Step 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
Warning

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

  1. Agent spot-check: Have 3–5 agents search for known customers and verify their conversation history renders correctly in Missive.
  2. Search validation: Confirm Missive's search returns expected results for key customer emails and label filters.
  3. Rule testing: Verify newly created Missive rules trigger correctly on test conversations sent through native channels (not the custom import channel).
  4. Response templates: Confirm all migrated snippets/responses are accessible and formatted correctly.
  5. 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

  1. Connect email accounts (Gmail, Outlook, IMAP) to Missive
  2. Set up SMS via Twilio or SignalWire integration
  3. Connect WhatsApp Business, Facebook Messenger, and Instagram channels
  4. Set up Missive Live Chat on your website
  5. Verify all channels route to the correct teams
  6. 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., E to close, R to reply, L to 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

  1. Backup first. Export all Kustomer data via API before starting. Keep raw JSON files and downloaded attachments as your source of truth.
  2. Run a test migration. Migrate 100 customers and 500 conversations to a separate Missive organization (not your production org). Validate before scaling.
  3. 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.
  4. Validate incrementally. Check record counts after each phase (contacts, conversations, labels), not just at the end.
  5. 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.
  6. Build idempotent scripts. Use external_id fields where supported. If the script crashes at record 47,000, you should be able to rerun from that checkpoint without creating duplicates.
  7. 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.
  8. 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.
  9. 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.
  10. 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).

Info

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.

More from our Blog

Missive Migration Checklist
Checklist/Missive

Missive Migration Checklist

Planning a move to Missive? Use this comprehensive Missive migration checklist to protect your data, map workflows accurately, and ensure a seamless go-live without downtime.

Tejas Mondeeri Tejas Mondeeri · · 11 min read
Help Desk Data Migration Playbook: What Data to Move and What to Leave Behind
Help Desk

Help Desk Data Migration Playbook: What Data to Move and What to Leave Behind

This definitive playbook answers the single most critical question: "What data do we actually need to move?". This strategic guide helps you declutter and decide what's precious and what's junk. We provide a clear breakdown of the non-negotiable Tier 1 data, like tickets , knowledge bases , and user profiles, versus the Tier 2 data that provides rich context, like automations and organizations. Use this as your strategic checklist to avoid common mistakes and ensure a flawless, functional new help desk.

Raaj Raaj · · 7 min read