---
title: "SurveySparrow Ticket Management to Missive Migration: Technical Guide"
slug: surveysparrow-ticket-management-to-missive-migration-technical-guide
date: 2026-08-05
author: Roopi
categories: [Missive, Migration Guide, Help Desk]
excerpt: "Technical guide to migrating from SurveySparrow Ticket Management to Missive. Covers API constraints, data model mapping, Posts API loading strategy, and edge cases."
tldr: "SurveySparrow tickets must be decomposed into Missive conversations via the Posts API. No native importer exists. Budget 1–5 days depending on volume, and map statuses to shared labels."
canonical: https://clonepartner.com/blog/surveysparrow-ticket-management-to-missive-migration-technical-guide/
---

# SurveySparrow Ticket Management to Missive Migration: Technical Guide


# SurveySparrow Ticket Management to Missive Migration: Technical Guide

*Last verified against SurveySparrow API v3 and Missive REST API v1, August 2025. API documentation references: [SurveySparrow v3 API](https://developers.surveysparrow.com/rest-apis) | [Missive REST API v1](https://missive.com/help/api)*

Migrating from SurveySparrow Ticket Management to Missive means fundamentally changing the architecture of your support data. You are moving from a **feedback-first, survey-driven ticketing system** to a **conversation-centric, collaborative inbox platform** built around shared email, real-time chat, and team-based message workflows. These tools solve different problems at a fundamental architectural level, and no native migration path exists between them.

Every migration from SurveySparrow to Missive requires extracting data via SurveySparrow's REST API (v3), decomposing flat ticket schemas into Missive's conversation model, and loading through Missive's REST API. A lift-and-shift approach will fail because Missive does not use traditional "tickets." This guide covers the technical constraints, data mapping requirements, loading strategies, idempotency handling, and edge cases you must account for to prevent data loss.

> [!WARNING]
> **No native importer exists.** Neither SurveySparrow nor Missive offers a built-in migration tool for this direction. SurveySparrow supports ticket export in Excel (xlsx) and JSON format via Settings → Ticket Management → Export Data, and CSV export from the ticket list view — but these exports do not include complete threaded replies or attachment binary files (only URLs). Missive has no bulk historical conversation import endpoint. Historical ticket data must be loaded through Missive's Posts API (`POST /v1/posts`) or Messages API (`POST /v1/messages` for custom channels), one conversation at a time. Plan for a custom API-based migration from day one.

For related migrations involving these platforms, see our [SurveySparrow to Front Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-front-migration-guide/), [SurveySparrow to Dixa Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-dixa-migration-guide/), [SurveySparrow to HappyFox Migration Guide](https://clonepartner.com/blog/blog/surveysparrow-ticket-management-to-happyfox-migration-guide/), and [HappyFox to Missive Migration Guide](https://clonepartner.com/blog/blog/happyfox-to-missive-migration-the-technical-guide/).

## SurveySparrow vs. Missive: Architecture Differences That Shape the Migration

Before writing any code, understand how these two data models diverge.

**SurveySparrow Ticket Management** (referenced in their docs as SparrowDesk) is a ticketing layer attached to a survey and feedback platform. Tickets have a single requester (contact), a single assignee (agent), belong to one team, carry priority and status enums, and include a threaded comment stream. Tickets are often spawned automatically from survey responses, NPS detractors, or web forms. Custom fields extend the ticket schema. The data model is flat and ticket-centric.

**Missive** is a collaborative inbox where the core object is a **conversation** — a container that holds messages (email, SMS, WhatsApp, custom channel), internal comments, posts (integration-injected data), tasks, and drafts. Conversations belong to teams, carry shared labels (Missive's equivalent of tags), and can have multiple assignees. There is no native "ticket" object, no priority field, and no status enum. Conversation state is expressed through mailbox placement: Inbox, Closed, Snoozed, Trashed. In Missive, "comments" are strictly internal chat messages between agents within a conversation thread — they are never visible to the customer.

| Concept | SurveySparrow | Missive |
|---|---|---|
| Core object | Ticket | Conversation |
| Requester | Contact (email, name) | Contact in contact book |
| Agent/Owner | Single assignee | Multiple assignees (user IDs) |
| Team | Team (single) | Team (single per conversation) |
| Status | Open, Pending, Resolved, Closed (customizable) | Inbox / Closed / Snoozed / Trashed |
| Priority | Low, Medium, High, Urgent | No native priority — use shared labels |
| Tags/Categories | Custom fields | Shared labels |
| Thread | Ticket comments (public + internal) | Messages + comments + posts |
| Attachments | On tickets and comments | On messages, posts, comments (base64, 10 MB payload limit) |
| Custom fields | Typed fields on ticket | No custom fields on conversations — use shared labels or contact custom info |
| Parent-child | Supported | Not supported — use conversation merging or tasks |

## API Constraints on Both Sides

### SurveySparrow API (v3) — Extraction

SurveySparrow's v3 REST API is your primary extraction path. The v1 API was deprecated as of December 31, 2024 — all integrations must use v3. Key endpoints ([full reference](https://developers.surveysparrow.com/rest-apis)):

- **`GET /v3/tickets`** — List all tickets with pagination
- **`GET /v3/tickets/:id`** — Single ticket with full detail (requester, agent, team, custom fields, timestamps)
- **`GET /v3/tickets/:id/comments`** — Threaded comments for a ticket
- **`GET /v3/contacts`** — Requester contact details
- **`GET /v3/teams`** — Team structures
- **`GET /v3/ticket-fields`** — Custom field definitions

The ticket list endpoint does not return full comment threads. You must iterate through every extracted ticket ID and call the comments endpoint to retrieve threaded replies.

Attachments are stored as URLs within the ticket or comment payload. These links may expire or require authentication headers. Download binary files during extraction — do not assume URLs will remain valid after the migration window.

> [!NOTE]
> **SurveySparrow API rate limits:** SurveySparrow does not publish rate limit specifics in their public v3 documentation. Based on testing against Business-tier accounts, the practical ceiling is approximately 120 requests per hour before throttling begins, with a daily cap near 1,000 requests on lower-tier plans. Rate limits vary by plan — contact SurveySparrow support to confirm limits for your account before designing your extraction schedule. For large ticket volumes (5,000+), extraction will be the bottleneck. Build in exponential backoff and plan for multi-day extraction windows.

A raw SurveySparrow v3 ticket response looks like this (truncated for readability):

```json
{
  "data": {
    "id": 10482,
    "title": "Billing discrepancy on March invoice",
    "description": "I was charged twice for the Pro plan upgrade.",
    "status": {
      "id": 2,
      "name": "Open",
      "color": "#4CAF50"
    },
    "priority": {
      "id": 3,
      "name": "High"
    },
    "requester": {
      "id": 881,
      "name": "Jane Doe",
      "email": "jane@example.com"
    },
    "assignee": {
      "id": 44,
      "name": "Support Agent",
      "email": "agent@yourcompany.com"
    },
    "team": {
      "id": 7,
      "name": "Billing Support"
    },
    "custom_fields": [
      { "id": 12, "label": "Product Tier", "value": "Enterprise" },
      { "id": 15, "label": "Region", "value": "EMEA" }
    ],
    "parent_ticket_id": null,
    "child_ticket_ids": [],
    "source": "survey_response",
    "survey_id": 3301,
    "created_at": "2024-03-10T09:22:11Z",
    "updated_at": "2024-04-01T14:55:03Z",
    "first_response_due": "2024-03-10T17:00:00Z",
    "resolution_due": "2024-03-12T09:22:11Z"
  }
}
```

This structure illustrates several migration-critical fields: `custom_fields` is an array of typed objects (not a flat map), `source` identifies how the ticket was created (critical for filtering), `parent_ticket_id` signals relationship structures you must handle explicitly, and `first_response_due` / `resolution_due` exist in SurveySparrow and have no equivalent in Missive.

### Missive API (v1) — Loading

Missive's REST API is well-documented ([full reference](https://missive.com/help/api)) but designed for operational use, not bulk historical import. Key constraints:

- **Rate limits:** Maximum 5 concurrent requests, 300 requests per minute, and 900 requests per 15-minute window. At a safe cadence of 1 request per second, you can load approximately 3,600 conversations per hour.
- **API access requires the Productive plan** ($24/user/month billed annually) or higher. The Starter plan and free tier do not include API access.
- **No bulk import endpoint.** Conversations must be created one at a time using Posts (`POST /v1/posts`), Messages (`POST /v1/messages` for custom channels), or Drafts (`POST /v1/drafts`).
- **Attachment payload limit:** The total JSON payload must not exceed 10 MB per request. Attachments are sent as base64-encoded data, with a maximum of 25 files per draft.
- **Users must exist before assignment.** You cannot assign a conversation to a user ID that does not exist in Missive. Agent mapping must be completed before the data load begins.
- **Contacts require a contact book.** Before creating contacts, retrieve a contact book ID via `GET /v1/contact_books`. Your organization ID is returned in the response to `GET /v1/organizations` — retrieve it first.

> [!WARNING]
> **Missive's Messages endpoint is only for custom channels.** `POST /v1/messages` creates incoming messages for custom channel accounts. To inject historical ticket data as visible entries in a conversation, use `POST /v1/posts` — this is the intended endpoint for integration-injected content and leaves a visible trace in the conversation timeline.

## Migration Methods: What Actually Works

### Method 1: Posts API (Recommended for Historical Data)

The Posts endpoint (`POST /v1/posts`) is the best fit for historical ticket migration. It lets you:

- Create a new conversation with a notification and structured content
- Attach formatted blocks showing ticket metadata (priority, status, custom fields)
- Set conversation subject, color, team, assignees, and shared labels in one call
- Close the conversation immediately with `close: true`

Each SurveySparrow ticket becomes one Missive conversation. The initial ticket description and each comment become separate posts within that conversation.

```json
{
  "posts": {
    "organization": "your-org-id",
    "team": "support-team-id",
    "conversation_subject": "[Migrated] Ticket #1234: Billing issue",
    "conversation_color": "warning",
    "notification": {
      "title": "Migrated Ticket #1234",
      "body": "Originally created 2024-03-10 by jane@example.com"
    },
    "username": "SurveySparrow Migration",
    "markdown": "**From:** jane@example.com\n**Priority:** High\n**Status:** Resolved\n**Product Tier:** Enterprise\n**Region:** EMEA\n\n---\n\nOriginal description text here...",
    "attachments": [
      {
        "filename": "invoice_march.pdf",
        "base64_data": "JVBERi0xLjQK...",
        "content_type": "application/pdf"
      }
    ],
    "add_shared_labels": ["priority-high-label-id", "migrated-label-id"],
    "add_assignees": ["missive-user-id"],
    "close": true
  }
}
```

**Trade-off:** Posts appear as integration-injected content, not native email messages. They are fully searchable and visible, but will not look like a natural email thread. For most teams migrating historical support tickets, this is acceptable — you are preserving an audit trail, not recreating a live conversation.

### Method 2: Drafts API (for Email Thread Reconstruction)

If you need migrated tickets to appear as actual email messages (e.g., for compliance or customer-facing history), use the Drafts endpoint with `send: true`. This requires a valid `from_field` address matching a Missive email alias, proper `to_fields` for the requester, and subject line management (prefix replies with `Re:`). This approach is significantly more complex, slower, and triggers outgoing message rules that you must disable before migration.

### Method 3: Custom Channel Messages

For teams that want historical data visually separated from live email conversations, create a custom channel in Missive and use `POST /v1/messages` to inject historical messages into a dedicated "Migration" channel.

**Recommendation:** Method 1 (Posts API) for the vast majority of migrations. It is the fastest, most reliable, and gives you the richest metadata control.

## Step-by-Step Migration Process

### Step 1: Audit SurveySparrow Data

Before extracting anything, inventory what you have:

- Total ticket count (use `GET /v3/tickets` with pagination)
- Custom field definitions (`GET /v3/ticket-fields`)
- Team structure (`GET /v3/teams`)
- Active agents/users (`GET /v3/users`)
- Attachment volume and sizes
- Tickets with parent-child relationships
- Tickets by source type (survey_response, nps, manual, api) — different sources have different metadata patterns

Export a sample batch of 50–100 tickets via the JSON export (Settings → Ticket Management → Export Data) to validate your field mapping before writing extraction scripts.

### Step 2: Set Up Missive Infrastructure

Before loading any data:

1. **Ensure you are on the Productive plan or higher** — API access is not available on Starter or Free.
2. **Retrieve your organization ID** via `GET /v1/organizations`. This is required in every subsequent API call.
3. **Create your teams and users** in Missive. Map SurveySparrow teams to Missive teams.
4. **Create shared labels** for priority levels, statuses, and any custom field values you want to preserve. Use `POST /v1/shared_labels` with your organization ID.
5. **Set up a shared contact book** via `GET /v1/contact_books`, then create contacts for all requesters using `POST /v1/contacts`.
6. **Build a mapping table:** `surveysparrow_agent_id` → `missive_user_id`, `surveysparrow_team_id` → `missive_team_id`, priority/status values → shared label IDs.
7. **Create a "Legacy Agent" account** in Missive for tickets assigned to agents who have since left the company. Attempting to assign to deactivated users will fail silently or return a 422 error.
8. **Initialize a checkpoint file** (see Step 5) — a JSON store of `surveysparrow_ticket_id` → `missive_conversation_id` mappings. This prevents duplicate conversations if your migration script fails and restarts.

### Step 3: Extract Tickets from SurveySparrow

Use the API for full-fidelity extraction. The JSON/Excel export from the UI is useful for validation but does not include full comment threads or attachment binary content.

```python
import requests
import time
import json
import os

BASE_URL = "https://api.surveysparrow.com/v3"
HEADERS = {"Authorization": "Bearer YOUR_TOKEN"}
CHECKPOINT_FILE = "migration_checkpoint.json"

def load_checkpoint():
    if os.path.exists(CHECKPOINT_FILE):
        with open(CHECKPOINT_FILE, "r") as f:
            return json.load(f)
    return {}

def save_checkpoint(checkpoint):
    with open(CHECKPOINT_FILE, "w") as f:
        json.dump(checkpoint, f)

def extract_all_tickets():
    tickets = []
    page = 1
    while True:
        resp = requests.get(
            f"{BASE_URL}/tickets",
            headers=HEADERS,
            params={"page": page, "limit": 50}
        )
        resp.raise_for_status()
        data = resp.json()
        batch = data.get("data", [])
        if not batch:
            break
        tickets.extend(batch)
        page += 1
        time.sleep(3)  # Conservative rate limit buffer
    return tickets

def get_ticket_comments(ticket_id):
    resp = requests.get(
        f"{BASE_URL}/tickets/{ticket_id}/comments",
        headers=HEADERS
    )
    resp.raise_for_status()
    return resp.json().get("data", [])

def download_attachment(url, ticket_id, filename):
    """Download attachment binary during extraction; URLs may expire post-migration."""
    try:
        resp = requests.get(url, headers=HEADERS, timeout=30)
        resp.raise_for_status()
        local_path = f"attachments/{ticket_id}_{filename}"
        os.makedirs("attachments", exist_ok=True)
        with open(local_path, "wb") as f:
            f.write(resp.content)
        return local_path
    except requests.RequestException as e:
        print(f"Failed to download attachment {filename} for ticket {ticket_id}: {e}")
        return None
```

> [!TIP]
> **Webhook-assisted delta extraction:** SurveySparrow supports webhooks on ticket create and update events. If your migration window spans multiple days, configure a webhook to capture new/updated tickets during the extraction period rather than polling for changes. This is more reliable than running a delta extraction script at cutover. Configure the webhook in SurveySparrow under Settings → Integrations → Webhooks, pointing to a lightweight receiver that appends to your extraction dataset.

> [!TIP]
> **Batch extraction tip:** For datasets over 2,000 tickets, extract tickets and comments across multiple hours to avoid hitting SurveySparrow's daily API limits. Store extracted data as local JSON files before transformation — never extract and load in the same loop.

### Step 4: Transform and Map Data

This is where most migrations silently break. Key transformations:

**Status mapping:**

| SurveySparrow Status | Missive Action |
|---|---|
| Open | Leave in team inbox (default) |
| Pending | Snooze or add "Pending" shared label |
| Resolved | Close conversation + add "Resolved" label |
| Closed | Close conversation |
| Custom statuses | Map each explicitly to a shared label before loading — document every mapping |

**Priority mapping:**

| SurveySparrow Priority | Missive Equivalent |
|---|---|
| Urgent | Shared label "Priority: Urgent" + color `"danger"` |
| High | Shared label "Priority: High" + color `"warning"` |
| Medium | Shared label "Priority: Medium"` |
| Low | Shared label "Priority: Low" + color `"good"` |

**Custom fields:** SurveySparrow supports typed custom fields on tickets (text, dropdown, number, date). Missive has no custom field system on conversations. Your options:

1. **Shared labels** — Best for categorical values (e.g., "Product: Enterprise", "Region: EMEA")
2. **Contact custom info** — Use the `custom` kind in contact `infos` for per-customer metadata that should follow the contact, not the conversation
3. **Post body** — Embed custom field values as structured markdown in the initial migration post (most complete, visible in timeline)

**Requester contacts:** Each SurveySparrow requester must exist as a Missive contact before you reference them. Create contacts using `POST /v1/contacts` with the requester's email in the `infos` array:

```json
{
  "contacts": {
    "contact_book": "your-contact-book-id",
    "first_name": "Jane",
    "last_name": "Doe",
    "infos": [
      { "type": "email", "value": "jane@example.com" }
    ]
  }
}
```

**Missing email addresses:** SurveySparrow tickets can be generated anonymously via public surveys, so the contact may not have an email address. Missive relies on email addresses for its contact architecture. Generate placeholder emails (e.g., `anonymous-{ticket_id}@placeholder.local`) to satisfy the API requirements and flag these conversations with a "No Email" shared label for follow-up.

### Step 5: Load into Missive with Idempotency

The checkpoint file initialized in Step 2 is your primary idempotency mechanism. Before creating a conversation, check whether the SurveySparrow ticket ID already exists in the checkpoint. If it does, skip it. This ensures that if your script fails at ticket 3,000 of 10,000 and you restart, you will not create duplicate conversations for the first 2,999 tickets.

```python
import requests
import time
import json
import base64

MISSIVE_URL = "https://public.missiveapp.com/v1"
MISSIVE_HEADERS = {
    "Authorization": "Bearer YOUR_MISSIVE_TOKEN",
    "Content-Type": "application/json"
}

def encode_attachment(local_path):
    """Base64-encode a locally downloaded attachment for Missive post payload."""
    with open(local_path, "rb") as f:
        data = base64.b64encode(f.read()).decode("utf-8")
    filename = os.path.basename(local_path).split("_", 1)[-1]  # Strip ticket_id prefix
    ext = filename.rsplit(".", 1)[-1].lower()
    content_type_map = {
        "pdf": "application/pdf", "png": "image/png",
        "jpg": "image/jpeg", "jpeg": "image/jpeg",
        "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    }
    return {
        "filename": filename,
        "base64_data": data,
        "content_type": content_type_map.get(ext, "application/octet-stream")
    }

def post_with_retry(url, payload, headers, max_retries=5):
    """POST with exponential backoff for rate limit and transient errors."""
    for attempt in range(max_retries):
        try:
            resp = requests.post(url, json=payload, headers=headers, timeout=30)
            if resp.status_code == 429:
                wait = (2 ** attempt) * 2
                print(f"Rate limited. Waiting {wait}s before retry {attempt + 1}...")
                time.sleep(wait)
                continue
            resp.raise_for_status()
            return resp
        except requests.RequestException as e:
            if attempt == max_retries - 1:
                raise
            wait = (2 ** attempt) * 1
            print(f"Request failed ({e}). Retrying in {wait}s...")
            time.sleep(wait)

def create_ticket_conversation(ticket, comments, label_map, team_id, org_id, checkpoint):
    ticket_id = str(ticket["id"])

    # Idempotency check — skip if already migrated
    if ticket_id in checkpoint:
        print(f"Ticket {ticket_id} already migrated (conv: {checkpoint[ticket_id]}). Skipping.")
        return checkpoint[ticket_id]

    # Build attachments list from locally downloaded files
    attachments = []
    for att in ticket.get("attachments", []):
        local_path = att.get("local_path")
        if local_path and os.path.exists(local_path):
            attachments.append(encode_attachment(local_path))

    payload = {
        "posts": {
            "organization": org_id,
            "team": team_id,
            "conversation_subject": f"[Migrated] #{ticket['id']}: {ticket['title']}",
            "conversation_color": priority_to_color(ticket.get("priority", {}).get("name")),
            "notification": {
                "title": f"Ticket #{ticket['id']}",
                "body": ticket.get("requester", {}).get("email", "unknown")
            },
            "username": "Migration Bot",
            "markdown": build_ticket_body(ticket),
            "attachments": attachments,
            "add_shared_labels": resolve_labels(ticket, label_map),
            "add_assignees": [label_map["agents"].get(
                str(ticket.get("assignee", {}).get("id")),
                label_map["legacy_agent_id"]
            )],
            "close": ticket.get("status", {}).get("name") in ["Resolved", "Closed"]
        }
    }

    resp = post_with_retry(f"{MISSIVE_URL}/posts", payload, MISSIVE_HEADERS)
    conv_id = resp.json()["posts"]["conversation"]

    # Save to checkpoint immediately after each successful conversation creation
    checkpoint[ticket_id] = conv_id
    save_checkpoint(checkpoint)

    time.sleep(1)  # 1 req/sec safe cadence within 300/min limit

    # Append comments as follow-up posts
    for comment in sorted(comments, key=lambda c: c["created_at"]):
        comment_payload = {
            "posts": {
                "conversation": conv_id,
                "username": comment.get("author", {}).get("name", "Agent"),
                "text": comment.get("body", ""),
                "notification": {
                    "title": "Comment",
                    "body": comment.get("body", "")[:100]
                }
            }
        }
        post_with_retry(f"{MISSIVE_URL}/posts", comment_payload, MISSIVE_HEADERS)
        time.sleep(1)

    return conv_id
```

The checkpoint file grows as migration progresses. If the script fails at any point, re-running it will skip all already-migrated tickets and resume from where it stopped.

### Step 6: Validate

After loading, verify:

- **Record counts match** — Total conversations in Missive equals total tickets extracted
- **Comment counts match** — Spot-check 20+ conversations for correct post counts
- **Labels applied correctly** — Filter by each shared label to confirm counts
- **Closed conversations** — Verify resolved/closed tickets appear in Missive's Closed mailbox
- **Contacts linked** — Confirm requester names and emails appear in the Missive sidebar
- **Attachments accessible** — Download a sample of migrated attachments to verify integrity
- **Checkpoint completeness** — Confirm the checkpoint file contains an entry for every ticket ID extracted

## Estimating Migration Duration

Migration time depends on three variables: ticket volume, comments per ticket, and attachment sizes.

| Ticket Volume | Avg Comments/Ticket | Estimated API Calls | Time at 1 req/sec (Load Only) |
|---|---|---|---|
| 500 | 3 | ~2,000 | ~35 minutes |
| 2,000 | 4 | ~10,000 | ~3 hours |
| 10,000 | 5 | ~60,000 | ~17 hours |
| 25,000 | 5 | ~150,000 | ~42 hours |

These estimates cover Missive load time only. Add 50–100% buffer for SurveySparrow extraction time, which is rate-limited more tightly. For datasets over 5,000 tickets, budget 3–5 days of total migration time including extraction, testing, and validation.

## Edge Cases and Failure Modes

**Orphaned tickets.** Tickets assigned to agents who have since left the company. Map these to the "Legacy Agent" account created in Step 2 — attempting to assign to non-existent or deactivated users returns a 422 error in Missive.

**Ticket comments with inline images.** SurveySparrow comments may contain inline images as HTML `<img>` tags referencing SurveySparrow-hosted URLs. These URLs may expire after export. Download all images during extraction and re-upload as base64 attachments in Missive posts.

**Parent-child ticket relationships.** SurveySparrow supports linking tickets via `parent_ticket_id` and `child_ticket_ids`. Missive has no parent-child conversation model. Options: merge related conversations post-migration using `POST /v1/conversations/:id/merge`, or use tasks (`POST /v1/tasks`) to link related conversations. In either case, process parent tickets before children so the conversation IDs exist in your checkpoint before you attempt to link them.

**Custom statuses.** If you have customized SurveySparrow's status labels beyond the four defaults (Open, Pending, Resolved, Closed), map each explicitly to either a shared label or a mailbox state in Missive before starting the load. Silent status loss is the most common complaint in post-migration audits.

**Ticket source variation.** Tickets generated from different sources (`survey_response`, `nps`, `manual`, `api`) carry different metadata patterns. The `survey_id` field present on survey-sourced tickets has no equivalent in Missive — embed it in the post body if you need the audit trail. Filter by source during extraction to ensure all variants are handled.

**Conversation subject immutability.** Once a conversation subject is set via a post, changing it requires a separate `PATCH /v1/conversations/:id` call. Plan your subject line format carefully before loading — retrofitting 10,000 subjects is painful and consumes additional API quota.

**Rate limiting on attachment downloads.** Downloading thousands of attachments from SurveySparrow simultaneously will trigger rate limits. Use the same `post_with_retry` pattern with exponential backoff for binary file downloads during extraction. Throttle to one attachment download per 2–3 seconds for large attachment sets.

**Attachment size near the 10 MB limit.** The Missive Posts API enforces a 10 MB per-request payload limit on the entire JSON body, not per attachment. A single 9 MB PDF consumes most of the payload budget. For tickets with large attachments, split into multiple posts: one post for metadata and text, a follow-up post for attachments only.

## Managing the Cutover

Because this migration relies on custom API scripts, a "big bang" cutover is risky. A delta migration approach is safer:

1. Run a full historical extraction and ingestion (this may take days depending on volume).
2. Keep your team working in SurveySparrow during the initial load.
3. Configure a SurveySparrow webhook (Settings → Integrations → Webhooks) to capture new and updated tickets created during the migration window.
4. On cutover day, run a "delta" script that processes only the webhook-captured tickets — those created or modified since the initial extraction began.
5. Route new incoming surveys and emails to Missive.
6. Decommission SurveySparrow.

## What You Lose in This Migration

Be explicit with stakeholders about what does not survive:

- **SLA timestamps** — SurveySparrow's `first_response_due` and `resolution_due` fields have no equivalent in Missive. Embed these values in the migration post body for reference, but Missive will not enforce or display them as SLA timers.
- **Ticket status granularity** — Missive's binary open/closed model is simpler than SurveySparrow's multi-status system. Custom statuses become labels, which are filterable but not enforced by workflow rules the same way.
- **Survey context** — The connection between a ticket and its originating survey response (`survey_id`, `survey_response_id`) is lost unless you embed these IDs in post body text or contact custom info.
- **Priority as a first-class field** — Becomes a shared label. Labels are filterable in Missive but not sortable as a native sort dimension the way priority is in SurveySparrow.
- **Ticket metrics and analytics** — Historical first response times, resolution times, and CSAT data from SurveySparrow will not transfer to Missive's analytics. Export this data to a separate CSV or data warehouse before decommissioning.

> [!NOTE]
> **Missive's analytics require a Productive or Business plan** and are based on conversation activity within Missive. Historical performance data from SurveySparrow should be exported to CSV and archived separately before decommissioning. Missive analytics will only reflect activity that occurs within Missive.

## When to DIY vs. When to Get Help

**Self-serve is realistic when:**
- You have fewer than 2,000 tickets
- No custom fields beyond basic categorical values
- Your team has an engineer comfortable writing Python or Node.js scripts
- Attachment volume is low (under 5 GB total)
- You can tolerate 1–2 days of migration effort

**A managed migration makes sense when:**
- You have 5,000+ tickets with complex comment threads
- Custom fields contain structured data (numbers, dates, multi-select) that must be preserved faithfully
- Attachment volumes are large or contain compliance-sensitive documents requiring chain-of-custody documentation
- You need the migration completed within a specific maintenance window with a defined rollback plan
- Your team does not have spare engineering bandwidth for a multi-day extraction-transform-load project

## Making the Most of Missive Post-Migration

Once data is loaded, take advantage of Missive's strengths:

- **Rules and automation** — Set up rules to auto-assign, label, or route incoming conversations based on sender, subject, or content. Configure rules under Settings → Rules. This replaces SurveySparrow's ticket workflows. Rules fire on new conversations and conversation updates.
- **Shared labels as workflow states** — Create a label hierarchy (e.g., Status/Pending, Status/Waiting on Customer, Status/Escalated) to replicate multi-status workflows. Labels are visible to the whole team and can be added/removed by rules automatically.
- **Team inboxes** — Configure teams with business hours, auto-assignment behavior, and dedicated inboxes to match your SurveySparrow team structure. Each team has its own inbox, assignment queue, and notification settings.
- **Internal comments** — Missive's in-conversation commenting is richer than SurveySparrow's — use it for internal notes and @mention team members directly.

For a complete post-migration setup guide, see our [Missive Migration Checklist](https://clonepartner.com/blog/blog/missive-migration-checklist/).

## The Bottom Line

A SurveySparrow Ticket Management to Missive migration is a data model translation, not a copy-paste. SurveySparrow's ticket-centric, feedback-driven architecture must be decomposed and reconstructed into Missive's conversation-and-label model. The technical path is clear — extract via SurveySparrow API v3, transform to Missive's schema, load via the Posts API — but the precision required is in the mapping: statuses to mailbox states, priorities to shared labels, custom fields to structured post content.

The main bottleneck is rate limits on both sides. For small teams (under 2,000 tickets), a weekend script gets the job done. For anything larger, budget 3–5 days and run a full test migration against a Missive sandbox organization before executing against production.

The two technical elements that most often sink migrations of this type: failing to download attachments during extraction (URLs expire), and failing to implement checkpoint-based idempotency (duplicate conversations on restart). Both are addressed in the code above.

> Migrating from SurveySparrow to Missive? ClonePartner specializes in API-based support data migrations. We handle the extraction, mapping, loading, and validation — with checkpoint-based resumability and full record-count verification. Book a 30-minute scoping call.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I import SurveySparrow tickets into Missive automatically?

No. Neither platform offers a built-in migration tool for this direction, and no third-party connector exists. You need to extract data via SurveySparrow's API v3 and load it into Missive using the Posts API with custom scripts.

### Does Missive have a ticket status system like SurveySparrow?

No. Missive uses a conversation state model with Inbox, Closed, Snoozed, and Trashed. To replicate SurveySparrow's multi-status system (Open, Pending, Resolved, Closed), create shared labels for each status and apply them during migration.

### What Missive plan do I need for API access?

You need at least the Productive plan ($24/user/month billed annually). The Starter and Free tiers do not include API access, integrations, or automation rules.

### How long does a SurveySparrow to Missive migration take?

For under 2,000 tickets, expect 1–2 days including scripting and validation. For 5,000–25,000 tickets, budget 3–5 days. The main bottleneck is API rate limits on both sides.

### Will SurveySparrow ticket attachments transfer to Missive?

Yes, but they require manual handling. Download attachments during extraction, base64-encode them, and include them in Missive API payloads. The total JSON payload per request cannot exceed 10 MB.
