Skip to content

Zammad to SurveySparrow Ticket Migration: Technical Guide

Technical guide for migrating tickets from Zammad to SurveySparrow. Covers API extraction, field mapping, attachment limits, rate limits, and comment threading.

Roopi Roopi · · 22 min read
Zammad to SurveySparrow Ticket Migration: 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

Zammad to SurveySparrow Ticket Migration: Technical Guide

Last verified against Zammad 6.x REST API and SurveySparrow API v3, July 2025.

Migrating from Zammad to SurveySparrow Ticket Management is an API reconstruction job, not a wizard-driven export/import. Zammad's article-based helpdesk architecture must be translated into SurveySparrow's flatter, feedback-first ticketing model — and no native migration path exists between the two. Every ticket, user, and conversation thread must be extracted from Zammad, transformed to match SurveySparrow's schema, and loaded through SurveySparrow's V3 API. (docs.zammad.org)

Zammad's migration tooling handles inbound moves from Freshdesk, Kayako, OTRS, and Zendesk into Zammad. SurveySparrow's migration app targets surveys, not support ticket history. Tools like n8n can wire the two systems together for live automation, but that is not historical migration with replay ordering, attachment handling, and delta cutover logic.

This guide covers the data model gap, API constraints on both sides, field mapping, extraction and loading strategies, contact deduplication, error handling, post-migration validation, and the failure modes that derail teams mid-migration.

Warning

No native import tool exists for this direction. Zammad has no export-to-SurveySparrow feature. SurveySparrow has no Zammad importer. SurveySparrow supports ticket export in Excel and JSON format via Settings → Ticket Management → Export Data — but these are export features, not import. Plan for custom API-based scripting from day one.

For related migrations from Zammad, see our Zammad to Zendesk Migration Guide, Zammad to Help Scout Migration Guide, and Zammad to Tidio Migration Guide. For inbound SurveySparrow migrations, see the HubSpot Service Hub to SurveySparrow Ticket Migration Guide. For outbound SurveySparrow migrations, see the SurveySparrow to Missive Migration Guide. For the broader cutover pattern, see our Zero-Downtime Help Desk Data Migration guide.

Zammad vs. SurveySparrow: Data Model Comparison

Understanding the structural gap between these two platforms is the most important step before writing any migration code.

Zammad uses a highly relational model. A Ticket is a container. The actual conversation lives in Articles — typed as email, phone, web, note, SMS, chat, or social messages — each with its own sender, recipient, CC, content type, internal/external visibility, timestamp, and attachments. Tickets belong to Users (customers) and Groups (agent teams), and Users can belong to Organizations. (docs.zammad.org)

SurveySparrow Ticket Management (also referred to as SparrowDesk in some docs and UI surfaces) uses a flatter model. A ticket has subject, description, priority, status, assignee, team, custom fields, and a flat comment thread. Tickets point to a requester Contact. (support.surveysparrow.com)

Concept Zammad SurveySparrow Ticket Management
Core record Ticket (with articles) Ticket (with comments)
Messages/Replies Articles — typed (email, note, phone, web, social), with from, to, cc, content_type, internal flag Comments — flat text thread on a ticket
Customers Users (customer role) linked to Organizations Contacts (requester linked by requester_id or email)
Organizations First-class object; users belong to organizations No native Organizations object
Agent assignment owner_id (agent), Group-based routing assignee_id (agent), Team-based routing
Groups Groups with granular permissions Teams
Custom fields Object attributes on tickets, users, organizations (booleans, dates, integers, selects, text, tree selects) Custom ticket fields (dropdown, multiselect, date, text, dependent)
Tags Tag system on tickets, users, organizations No native tag system on tickets
Ticket hierarchy Ticket linking (parent, child, normal) Related Tickets (parent/child)
Attachments Any file type, stored per article PDF, PNG, JPEG, MP3, CSV, WAV only; 15 MB max per file
Priorities Configurable (default: 1 low, 2 normal, 3 high) Configurable (numeric IDs)
States Configurable (new, open, pending reminder, pending close, closed, etc.) Configurable statuses (numeric IDs)
SLAs Built-in SLA engine with escalation SLA tracking with first-response and resolution due dates
Timestamps created_at, updated_at on tickets and articles created_at, updated_at on tickets (server-set on creation)

The biggest structural loss: Zammad articles carry rich metadata (sender, recipient, CC, content type, channel type, internal visibility) that has no direct equivalent in SurveySparrow's flat comment model. You will need to serialize this metadata into comment text or accept the data loss.

Info

Be honest about the trade-off. SurveySparrow Ticket Management is not a full helpdesk replacement for Zammad. It lacks Zammad's multi-channel article types, knowledge base, SLA engine depth, organization hierarchy, and the flexibility of a self-hosted open-source system. Its strength is connecting surveys, NPS, and CSAT feedback directly to ticketing workflows. Make sure this migration genuinely fits your operational needs before investing in the build.

Extraction: Getting Data Out of Zammad

Zammad's REST API is the primary extraction path. For self-hosted instances, direct database access is an option but introduces version-coupling risk.

API Endpoints

Resource Endpoint Notes
Tickets GET /api/v1/tickets?page={n}&per_page={n} Paginated; up to 100 per page by default
Ticket search GET /api/v1/tickets/search?query=<filter>&expand=true&with_total_count=true Supports with_total_count for auditable pagination (docs.zammad.org)
Ticket articles GET /api/v1/ticket_articles/by_ticket/{ticket_id} All articles for a ticket
Attachments GET /api/v1/ticket_attachment/{ticket_id}/{article_id}/{attachment_id} Binary download per attachment
Users GET /api/v1/users/{id} or GET /api/v1/users?expand=true Customers and agents
Organizations GET /api/v1/organizations If you need org context
Groups GET /api/v1/groups For mapping to SurveySparrow teams (docs.zammad.org)
Tags GET /api/v1/tags?object=Ticket&o_id={ticket_id} Per-ticket tag retrieval
Priorities GET /api/v1/ticket_priorities Reference data
States GET /api/v1/ticket_states Reference data (docs.zammad.org)

Authentication and Token Scope

Zammad supports HTTP Basic Auth, API token (Authorization: Token token={your_token}), and OAuth2. For migration scripts, API token auth is simplest. Use an admin-level token. Zammad API tokens inherit the permissions of the user they belong to — a non-admin token may silently skip tickets in groups the token owner cannot access, producing a subtly incomplete extraction with no error signal. There is no API response to distinguish "no tickets in this group" from "this token cannot see this group." Validate completeness by comparing total ticket counts from the Zammad admin panel against your extracted set.

Pagination and Limits

Zammad's list endpoints use page and per_page query parameters. The default page size is up to 100 records. A configurable server-side limit (commonly 500) caps per_page regardless of what you request — if you ask for per_page=1000 but the server limit is 500, you get 500. Zammad does not return a total count header on standard list endpoints. You must iterate until results are exhausted, or use the search endpoint with with_total_count=true for explicit counting.

Appending ?expand=true to ticket requests forces the API to return fully hydrated user and group objects alongside the ticket array, reducing follow-up calls.

# Fetch page 1, 100 tickets per page, with expanded relations
curl -H "Authorization: Token token=YOUR_TOKEN" \
  "https://your-zammad.example.com/api/v1/tickets?page=1&per_page=100&expand=true"

Paginate until you receive an empty array.

Extracting Articles and Attachments

For each ticket, fetch articles separately via GET /api/v1/ticket_articles/by_ticket/{ticket_id}?expand=true. This is where the bulk of your API calls live — 10,000 tickets averaging 5 articles each means 10,000 article-fetch calls on top of ticket pagination.

Each article object includes an attachments array with metadata (id, filename, size, content type). Downloading the actual file content requires an additional call per attachment. Attachment-heavy tickets can easily 3–5x your API call count. Plan your rate budget accordingly.

Zammad stores article attachment metadata in this structure:

{
  "id": 42,
  "article_id": 17,
  "filename": "invoice.pdf",
  "size": "204800",
  "preferences": {
    "Mime-Type": "application/pdf",
    "Content-ID": "abc123@zammad"
  }
}

The Content-ID value in preferences is the CID reference used in inline images (see Inline Images section below). Capture it during extraction — you will need it during transform.

Tip

For self-hosted Zammad: If you control the server, consider direct database export (PostgreSQL/MySQL) plus filesystem access to /opt/zammad/storage/ for attachments. This bypasses API pagination limits and is dramatically faster for large datasets. Verify the storage backend your instance uses — Zammad supports local filesystem and S3.

Loading: Getting Data Into SurveySparrow

SurveySparrow's V3 API provides the loading endpoints.

Operation Endpoint Method Notes
Create contact POST /v3/contacts JSON Must exist before ticket creation if using requester_id (developers.surveysparrow.com)
Search contacts GET /v3/contacts?email={email} JSON Use for deduplication before creation
Create ticket POST /v3/tickets multipart/form-data Subject (≤200 chars), priority, status required (developers.surveysparrow.com)
Batch create tickets POST /v3/tickets/batch JSON Returns 202 + token; no attachment support (developers.surveysparrow.com)
Check batch status GET /v3/tickets/batch/status/{token} JSON Poll for completion
Create comment POST /v3/tickets/{id}/comments JSON Threaded comments on an existing ticket
List ticket fields GET /v3/ticket-fields JSON Returns field definitions including internal_name (developers.surveysparrow.com)
Create team POST /v3/teams JSON Map from Zammad groups

Comment Creation Schema

The comment endpoint is the most frequently called operation in the entire migration. The request body for POST /v3/tickets/{id}/comments:

{
  "body": "Comment text here",
  "private": false
}

Set "private": true to map Zammad's internal: true articles to agent-only notes. The body field accepts plain text; HTML rendering behavior varies by SurveySparrow plan and client — test this with your target account before assuming HTML is safe to pass through.

There is no author or created_at field in the comment create schema. Both are set server-side. Serialize original author identity and timestamp into the body field (see Article → Comment Mapping below).

Rate Limits

Warning

Rate limit numbers are not officially published by SurveySparrow for all plan tiers. Community reports indicate a baseline of approximately 120 calls per hour and 1,000 calls per day on lower-tier plans, but these numbers are unverified by official documentation and may be wrong for your account. Do not use them for migration planning without confirmation. Before starting any migration, contact SurveySparrow support with your estimated call volume and request written confirmation of your account's actual limits and any available temporary increases.

For a migration of any meaningful size, implement exponential backoff in your loading scripts to handle 429 Too Many Requests responses. See the Error Handling section for retry logic.

The Batch Endpoint Trade-Off

The POST /v3/tickets/batch endpoint accepts an array of ticket objects and returns a 202 Accepted response with a status token. This is significantly more efficient than individual creates. The catch: the batch endpoint does not support attachments, and comment history must still be posted through the comment endpoint ticket-by-ticket. (developers.surveysparrow.com)

Most teams use batch creation for ticket shells with clean metadata, then replay comments and upload files individually.

The batch payload structure:

{
  "tickets": [
    {
      "subject": "Ticket subject here",
      "description": "First message body",
      "email": "customer@example.com",
      "priority": 2,
      "status": 1,
      "assignee_id": 456,
      "team_id": 12,
      "custom_fields": {
        "zammad_ticket_id": "12345",
        "zammad_ticket_number": "67890"
      }
    }
  ]
}

Contact Deduplication

What happens when you POST a contact with an email that already exists? SurveySparrow's POST /v3/contacts creates a duplicate rather than returning the existing record. To avoid duplicate contacts, search before you create:

GET /v3/contacts?email=customer@example.com

If the response contains results, extract the existing contact's id. If the response is empty, create the contact and capture the returned id. Store the mapping of source email → SurveySparrow contact ID in a local lookup table. This makes reruns safe: on a second run, the search returns the contact you created on the first run, and you skip the create call.

def get_or_create_contact(email, name):
    """Returns SurveySparrow contact ID, creating if absent."""
    response = get(f"/v3/contacts?email={email}")
    contacts = response.get("data", [])
    if contacts:
        return contacts[0]["id"]
    create_response = post("/v3/contacts", json={"email": email, "name": name})
    return create_response["data"]["id"]

Attachment Restrictions

SurveySparrow only accepts these file types: PDF, PNG, JPEG, MP3, CSV, WAV. Maximum file size is 15 MB per file. Zammad places no such restriction — any file type, any size. (developers.surveysparrow.com)

If your Zammad tickets contain DOC, XLSX, ZIP, EML, or files larger than 15 MB, they will not import. Run a pre-migration audit to identify affected attachments.

Attachment handling decision tree:

Attachment type Action
PDF, PNG, JPEG, MP3, CSV, WAV and ≤15 MB Upload directly via ticket create or comment
XLSX Convert to CSV, then upload
DOC/DOCX Convert to PDF, then upload
ZIP, EML, and other unsupported types Upload to external storage (S3, Google Drive); append signed download URL to comment body
Any file >15 MB (regardless of type) Upload to external storage; append signed download URL to comment body
Files where conversion is not feasible Log in structured exception file; append note to ticket description

Do not leave this decision until cutover weekend. Run the audit in Phase 1 and resolve the strategy before writing loading code.

Field Mapping: Zammad → SurveySparrow

Ticket Core Fields

Zammad Field SurveySparrow Field Transform Notes
title subject Truncate to 200 characters; store full title in description if needed
First article body description Convert HTML to plain text or sanitized HTML as needed
customer_id → User email email or requester_id Create/deduplicate contact first, then reference
owner_id → Agent assignee_id Map Zammad agent IDs to SurveySparrow user IDs
group_id → Group name team_id Map Zammad groups to SurveySparrow teams; fetch target teams with type=TICKET filter
priority_id priority Map numeric IDs; Zammad default: 1=low, 2=normal, 3=high
state_id status Map states; Zammad has more states than SurveySparrow
tags Custom field Serialize into a custom dropdown/multiselect field or append to description
organization_id Custom field Flatten into contact properties and/or a custom ticket field (admin-docs.zammad.org)
created_at Server-set — cannot override Prepend to description or store in custom field
Tip

Use hidden source fields. SurveySparrow ticket fields expose a stable internal_name that does not change even if the field label is renamed. Use it to store zammad_ticket_id, zammad_ticket_number, and other reconciliation keys for idempotent reruns and post-migration validation. Retrieve field definitions including internal_name via GET /v3/ticket-fields. (developers.surveysparrow.com)

Article → Comment Mapping

Each Zammad article after the first becomes a SurveySparrow comment. You need to preserve:

  • Who said it — Zammad articles have from, sender_id (Customer, Agent, System), and created_by_id. SurveySparrow's comment create schema has no author field. Serialize the original author into the comment body.
  • When — Article created_at timestamps. SurveySparrow sets created_at server-side on comments; you cannot backdate them. Prepend the original timestamp.
  • Visibility — Zammad's internal: true marks agent-only notes. Map to "private": true in the SurveySparrow comment body. If you cannot verify private comment support on your plan, prefix with [INTERNAL NOTE] to prevent accidental customer exposure.
  • Content type — Zammad articles can be text/html or text/plain. Strip or convert HTML using html2text (Python) or cheerio (Node.js) rather than regex.
  • Channel type — A twitter direct-message or telegram personal-message article becomes a plain comment unless you preserve the channel type in serialized metadata. (docs.zammad.org)

A practical comment body template:

[2023-10-14 09:32 UTC] [Customer: jane.doe@example.com] [Channel: email]
Original message text goes here...

For internal notes:

[2023-10-14 10:15 UTC] [Agent: john.smith@company.com] [INTERNAL NOTE]
Internal note text goes here...

State Mapping

Zammad's default states are richer than SurveySparrow's. Both systems use configurable, instance-specific numeric IDs — do not hardcode mappings across systems. Fetch both sides first, then build mappings by business label and workflow meaning. (docs.zammad.org)

Zammad State Suggested SurveySparrow Status
new Open
open Open
pending reminder Open (note in description)
pending close Open (note in description)
closed Closed
merged Closed (note in description)

SurveySparrow's configurable statuses let you create custom values. If you need Pending as a distinct status, create it in SurveySparrow before migration.

Migration Architecture

Decouple extraction, transformation, and loading into separate phases. Do not attempt all three in a single synchronous script.

Phase 1: Audit and Prepare the Target

Before moving data, inventory both systems:

  • Count total tickets, articles per ticket, attachment count and types, custom fields in use, active tags in Zammad
  • Run a query against your Zammad attachment store to identify file types and sizes that exceed SurveySparrow's restrictions
  • Create teams (mapped from Zammad groups), custom ticket fields, priority values, status values, and agent accounts in SurveySparrow
  • Identify tickets with subjects over 200 characters
  • Map every Zammad agent who owns tickets to a corresponding SurveySparrow user
  • Contact SurveySparrow support with your estimated total API call volume and request written confirmation of your rate limits and any available increase

Phase 2: Extract from Zammad

  1. Enumerate all tickets — Paginate through GET /api/v1/tickets?page={n}&per_page=100&expand=true
  2. Validate completeness — Compare extracted ticket count against the Zammad admin panel's total ticket count. A mismatch indicates a token scope gap or pagination error.
  3. Fetch articles per ticketGET /api/v1/ticket_articles/by_ticket/{ticket_id}?expand=true
  4. Capture CID metadata — For each article's attachments, record the Content-ID from preferences alongside the attachment ID. You will need this to rewrite inline image references during transform.
  5. Download attachments — For each article with attachments, fetch binary content via the attachment endpoint
  6. Extract reference data — Groups, priorities, states, tags, users, organizations
  7. Store locally — Write to structured JSON files or a staging database. Never transform in-memory during extraction.

Phase 3: Transform and Sanitize

This is where most migrations fail. Your middleware must:

  1. Build ID mapping tables — Zammad user ID → SurveySparrow contact ID, Zammad group → SurveySparrow team, priority and state mappings
  2. Create and deduplicate contacts — Use the GET /v3/contacts?email={email} search before each POST /v3/contacts to avoid duplicates. Store the resulting SurveySparrow Contact IDs mapped against original Zammad User IDs.
  3. Sanitize HTML — Zammad articles often contain complex HTML from inbound emails. Strip tracking pixels, malformed tags, and unsupported markup.
  4. Rewrite CID inline images — See the Inline Images section below for the full implementation path.
  5. Handle unsupported attachments — Follow the attachment decision tree above.
  6. Transform ticket fields — Map fields per the mapping tables. First article body becomes description. Subsequent articles become comments with serialized metadata.

Phase 4: Load into SurveySparrow

  1. Batch-create ticket shells — Use POST /v3/tickets/batch for throughput on tickets without attachments
  2. Poll batch statusGET /v3/tickets/batch/status/{token} until complete
  3. Map batch results — Record which Zammad ticket ID maps to which SurveySparrow ticket ID
  4. Add comments — For each ticket with multiple articles, POST /v3/tickets/{id}/comments for articles 2+, in strict chronological order
  5. Add attachments — For tickets with supported attachments, upload via individual API calls with multipart/form-data

If a comment fails to post due to an API timeout, your script must pause, retry, and log the failure without skipping. Skipping breaks the conversation timeline.

# Pseudocode: Core migration loop
for zammad_ticket in extracted_tickets:
    ss_ticket = {
        "subject": zammad_ticket["title"][:200],
        "description": build_description(zammad_ticket, articles[0]),
        "email": get_customer_email(zammad_ticket["customer_id"]),
        "priority": map_priority(zammad_ticket["priority_id"]),
        "status": map_state(zammad_ticket["state_id"]),
        "assignee_id": agent_map.get(zammad_ticket["owner_id"]),
        "team_id": group_map.get(zammad_ticket["group_id"]),
        "custom_fields": {
            "zammad_ticket_id": str(zammad_ticket["id"]),
            "zammad_ticket_number": str(zammad_ticket["number"])
        }
    }
    batch_queue.append(ss_ticket)
 
# Batch create
response = post("/v3/tickets/batch", json={"tickets": batch_queue})
status_token = response["token"]
 
# Poll until complete
while not batch_complete(status_token):
    sleep(5)
 
# Load batch results: map Zammad IDs → SurveySparrow IDs
for result in get_batch_results(status_token):
    id_map[result["custom_fields"]["zammad_ticket_id"]] = result["id"]
 
# Add comments for multi-article tickets
for ticket_id, articles in ticket_articles_map.items():
    ss_ticket_id = id_map[str(ticket_id)]
    for article in sorted(articles[1:], key=lambda a: a["created_at"]):
        comment_body = format_comment(article)
        is_internal = article.get("internal", False)
        post(
            f"/v3/tickets/{ss_ticket_id}/comments",
            json={"body": comment_body, "private": is_internal}
        )

Phase 5: Delta Migration and Cutover

When to use webhooks vs. polling for the delta phase:

  • Use Zammad webhooks when your team can deploy and maintain a receiving endpoint and your migration window is short (days, not weeks). Webhooks fire on triggers and deliver ticket, article, user, organization, and group context in near real-time. Attachments are delivered as authenticated download links, not binary content — your webhook receiver must follow the link to download the file before the link expires. (admin-docs.zammad.org)
  • Use polling when you cannot run a persistent endpoint or when the migration runs over weeks. Poll GET /api/v1/tickets/search?query=updated_at:>{last_run_timestamp}&expand=true on a schedule. Polling is simpler to implement, easier to pause and resume, and does not require infrastructure beyond your migration script.

For most migrations, polling is the right default. Use webhooks only if near-real-time delta capture is a firm requirement.

Cutover sequence:

  1. Run the main historical import while Zammad stays live
  2. Capture late updates via scheduled polling or webhook queue
  3. Freeze admin changes before the last delta so mappings do not shift underneath you
  4. Replay the final delta, switch inbound email, forms, and routing
  5. Keep Zammad read-only for a short validation window
  6. Verify — see the Validation section below

Error Handling

SurveySparrow API responses use standard HTTP status codes. Treatment by code:

HTTP Status Meaning Action
200 OK / 201 Created / 202 Accepted Success Record ID mapping; continue
400 Bad Request Malformed request payload Do not retry. Log the payload, fix the data problem (e.g., subject over 200 chars, missing required field), reprocess.
401 Unauthorized Invalid or expired API token Stop migration. Reauthorize.
403 Forbidden Token lacks permission for this operation Stop migration. Check plan tier and token scopes.
404 Not Found Referenced resource (ticket, contact, team) does not exist Do not retry. Check ID mapping table; the resource may not have been created yet. Fix ordering.
422 Unprocessable Entity Payload is valid JSON but fails business validation Do not retry. Log the full response body — SurveySparrow returns validation error details. Fix the data.
429 Too Many Requests Rate limit exceeded Retry with exponential backoff. Respect Retry-After header if present; otherwise wait 60 seconds, then 120, then 240.
500 Internal Server Error Server-side error Retry up to 3 times with a 30-second delay. If it persists, log and skip; investigate after the batch completes.
502 Bad Gateway / 504 Gateway Timeout Infrastructure timeout Retry with exponential backoff, same as 429.

The critical distinction: 400 and 422 errors will fail on every retry. Do not build a retry loop that keeps hammering a malformed payload — it burns your rate limit budget without progress. Route these to a separate error queue for human review.

Implement a structured error log with at minimum: timestamp, operation type, Zammad source ID, HTTP status, response body, and retry count.

Post-Migration Validation

Do not rely on "it finished without errors" as your validation signal. Run explicit checks after each phase.

Ticket Count Reconciliation

Compare record counts at the top level first:

# What you should validate
zammad_total = fetch_zammad_ticket_count()          # From admin panel or search endpoint
ss_imported = fetch_ss_tickets_with_source_field()  # GET /v3/tickets filtered by zammad_ticket_id present
 
if zammad_total != ss_imported:
    missing = identify_missing_tickets(zammad_ids, ss_ids)
    # missing is your re-run list

Article/Comment Count Per Ticket

For a random sample of 50–100 tickets, compare article count in Zammad against comment count in SurveySparrow:

for sample_ticket_id in random_sample:
    zammad_article_count = len(fetch_zammad_articles(sample_ticket_id))
    ss_comment_count = len(fetch_ss_comments(ss_id_map[sample_ticket_id]))
    expected_ss_comments = zammad_article_count - 1  # first article → description
    assert ss_comment_count == expected_ss_comments, f"Ticket {sample_ticket_id}: expected {expected_ss_comments}, got {ss_comment_count}"

Field-Level Spot Checks

For each migration-critical field, verify at least 20 tickets:

  • subject matches (or is correctly truncated with full text in description)
  • status maps to the correct SurveySparrow status
  • priority maps correctly
  • assignee_id points to the right agent
  • team_id points to the correct team
  • zammad_ticket_id custom field contains the correct source ID
  • requester_id points to the correct contact

Attachment Audit

From your exception log, verify every unsupported attachment has either a download URL in the comment body or a documented exception entry. Do not consider the migration complete if attachment exceptions are unresolved.

Export-Based Final Check

SurveySparrow supports ticket export in XLSX and JSON format via Settings → Ticket Management → Export Data. Export all tickets after migration and run a row count against your Zammad extraction count. This provides an independent validation path outside the API. (support.surveysparrow.com)

Edge Cases and Failure Modes

Timestamp Preservation

SurveySparrow sets created_at and updated_at server-side when tickets and comments are created. You cannot import historical records with their original timestamps through the documented public API. This is a hard limitation. Reporting and SLA calculations in SurveySparrow will reflect migration-time dates, not historical ones. The workaround — prepending original timestamps into the description or storing them in a custom field — preserves the metadata for human readers but does not fix system-level reporting. (developers.surveysparrow.com)

Subject Length

SurveySparrow enforces a 200-character limit on ticket subjects. Zammad has no such limit. Long titles will be rejected with a 400 or 422 error. Truncate proactively to 200 characters and store the full original title in the description field.

Inline Images (CID)

Zammad handles inline images in emails using Content-ID (CID) tags. Article bodies contain HTML like <img src="cid:abc123@zammad">. The mapping between CID and the actual attachment lives in the article's attachments array, specifically in preferences ["Content-ID"].

Full implementation path:

  1. During extraction, build a lookup: {cid_value: attachment_id} for each article
  2. During transform, parse the article HTML and find all <img src="cid:..."> tags
  3. For each CID match, fetch the corresponding binary attachment from Zammad
  4. Upload the attachment to SurveySparrow (if the file type is supported) or to external storage
  5. Rewrite the src attribute to the new URL
  6. If no match is found for a CID reference, replace the tag with [Image: attachment not migrated]

If you push raw CID-referenced HTML to SurveySparrow without rewriting, images appear as broken references. This is silent data corruption — no API error will tell you it happened.

Internal Notes

Zammad articles marked internal: true are agent-only. Map these to SurveySparrow comments with "private": true. If your plan or API version does not support the private flag on comments, you have three options:

  1. Skip internal notes entirely — Safest from a customer-visibility standpoint; loses historical agent context
  2. Import with [INTERNAL NOTE] prefix — Preserves content; risk that the note becomes customer-visible if private comments are not supported
  3. Store in a separate custom text field on the ticket — Works only if internal notes are short and infrequent

Most teams choose option 2 and verify comment visibility settings before going live.

Merged Tickets

Zammad supports ticket merging. Merged tickets have a merged state and a link to the target ticket via ticket_article type ticket merge. SurveySparrow has no merge concept. Import the merged ticket as a standalone closed ticket with a note in the description referencing the target ticket's Zammad ID, or skip it entirely. If you skip, ensure the target ticket's article history includes the merged content by fetching the merged ticket's articles and appending them to the target.

HTML Content

Zammad articles frequently contain HTML (content_type: text/html). Test how SurveySparrow renders HTML in ticket descriptions and comments on your specific account and plan — rendering support is not uniform across plans and clients. Use a purpose-built library (html2text in Python, cheerio in Node.js) rather than regex to strip or convert HTML.

Tags

Zammad's tag system has no SurveySparrow equivalent on tickets. If tags carry operational meaning (product categories, escalation flags), create a custom dropdown or multiselect field in SurveySparrow before migration and map tag values there. If tags are informal, append them to the description. Do not silently drop tags without documenting which tickets had them.

API Payload Size and Timeouts

If a Zammad ticket has 150+ replies, posting all comments in a tight sequence can cause 502 or 504 errors. Process comments in batches of 20–30, respect rate limits, and use the error handling table above to distinguish retriable timeouts from data errors.

Time and Cost Estimation

Based on patterns across similar migrations:

Dataset Size Estimated Timeline Primary Bottleneck
< 1,000 tickets, few attachments 2–4 days Script development and testing
1,000–10,000 tickets 1–2 weeks SurveySparrow rate limits; comment loading
10,000–50,000 tickets 2–4 weeks Rate limits; attachment handling; QA
50,000+ tickets 4–8 weeks Rate limit negotiation; phased loading; validation

The biggest time sink is comment loading. If your average ticket has 6 articles, a 10,000-ticket migration means ~50,000 comment API calls. At a hypothetical baseline of 120 calls/hour (unverified — confirm your actual limit before planning), that is over 17 days of comment loading alone. At 1,000 calls/hour (a reasonable enterprise limit), the same volume takes ~2 days. The difference between these two scenarios is entirely determined by your negotiated rate limit — confirm it before scoping.

Pre-Migration Checklist

  • Audit Zammad data — Total tickets, articles per ticket, attachment count and types, custom fields in use, active tags
  • Identify token scope gaps — Confirm your extraction token is admin-level; validate extracted count against admin panel total
  • Confirm SurveySparrow plan — Verify API access is enabled; confirm actual rate limits in writing from SurveySparrow support
  • Create SurveySparrow structure — Teams, custom fields (including zammad_ticket_id and zammad_ticket_number), status values, priority values before migration
  • Map agents — Every Zammad agent who owns tickets needs a corresponding SurveySparrow user
  • Build contact deduplication logic — Implement GET /v3/contacts?email={email} search before every contact create
  • Decide on attachment strategy — Audit unsupported formats using the decision tree; resolve convert, external-store, or skip before writing loading code
  • Decide on internal note handling — Skip, prefix [INTERNAL NOTE], or private comment; verify private comment support on your plan
  • Decide on CID inline image handling — Build the CID lookup and rewrite pipeline, or document which tickets will have broken images
  • Request rate limit increase — Contact SurveySparrow support with your estimated total call volume
  • Run a pilot — Migrate 50–100 tickets first, validate every field against the validation methodology above, then proceed
  • Plan cutover — Choose webhook or polling for delta; decide on freeze window; keep Zammad read-only for validation period

When to Skip This Migration

Not every migration should happen. Reconsider if:

  • You need timestamp-accurate historical reporting. SurveySparrow cannot preserve original created_at dates. If compliance or audit requirements mandate accurate historical timestamps on records, this platform will not satisfy them.
  • You rely on multi-channel article types. Zammad's email, phone, Twitter, Facebook, and Telegram article types carry channel-specific metadata that SurveySparrow's flat comment model cannot represent.
  • Your attachment ecosystem is diverse. If tickets frequently contain DOC, XLSX, ZIP, EML, or files over 15 MB, you will lose meaningful attachment coverage without significant conversion work.
  • You have 100,000+ tickets and need them all in SurveySparrow. Without a substantial rate limit increase, very large migrations become multi-month projects. Evaluate whether historical data truly needs to live in SurveySparrow or if an archive-and-start-fresh approach is more practical.
  • Your team depends on Zammad's SLA engine for reporting. SurveySparrow's SLA tracking calculates from the migration-time created_at, not the original ticket date. Historical SLA compliance reports will be wrong.

What ClonePartner Handles

For this migration path, we handle the full pipeline: extraction scripting against your Zammad instance (API or direct DB for self-hosted), field mapping and transform logic, SurveySparrow rate limit negotiation, attachment conversion and external hosting, CID inline image rewriting, comment threading with metadata preservation, delta cutover, and post-migration validation with ticket-level diff reports.

Frequently Asked Questions

Is there a native migration tool from Zammad to SurveySparrow?
No. Zammad's migration tools handle inbound moves from other platforms into Zammad. SurveySparrow's migration app targets surveys, not ticket history. You must extract data via Zammad's REST API and load it through SurveySparrow's V3 Tickets API using custom scripts.
Can I preserve original ticket timestamps when migrating to SurveySparrow?
No. SurveySparrow sets created_at and updated_at server-side when tickets and comments are created via API. You cannot backdate records through the documented public API. The workaround is to prepend original Zammad timestamps into the ticket description or store them in a custom field.
What file types does SurveySparrow accept for ticket attachments?
SurveySparrow only accepts PDF, PNG, JPEG, MP3, CSV, and WAV files, with a maximum size of 15 MB per file. Zammad has no such restriction. Unsupported files must be converted, stored externally with a download link, or documented as exceptions.
How do Zammad ticket articles map to SurveySparrow?
The first Zammad article typically becomes the SurveySparrow ticket description. Subsequent articles are loaded as comments. Rich metadata like sender, CC, channel type, and internal visibility must be serialized into the comment body since SurveySparrow's flat comment model has no direct equivalent for these fields.
What are SurveySparrow's API rate limits for ticket migration?
Community reports indicate a baseline of 120 API calls per hour and 1,000 per day on lower-tier plans. Business and Enterprise plans have higher limits. For any migration over a few hundred tickets, contact SurveySparrow support to request a temporary rate limit increase before starting.

More from our Blog