Skip to main content

Overview

Exports let you download your contacts as a CSV file. You can export all contacts or filter by tags. The export runs as a background job so it doesn’t block the UI, and uses streaming to handle large datasets without running out of memory.

Starting an Export

Create an export via the UI or the API:
// POST /api/exports
{
  "tagIds": ["uuid-1", "uuid-2"],
  "filename": "vip-contacts"
}
  • tagIds is optional — omit it to export all contacts
  • filename is optional — a default name with a timestamp is generated if not provided
  • The .csv extension is appended automatically if missing
The API creates an export record with status pending and fires an Inngest background job to handle the actual processing.

How the Export Job Works

The export job (process-export) runs through these steps:
  1. resolve-tag-filter — if tag IDs were provided, fetches the matching contact IDs. If zero contacts match, short-circuits with an empty CSV.
  2. resolve-export-metadata — resolves custom field keys (via SELECT DISTINCT jsonb_object_keys) and total row count in parallel. This avoids a two-pass scan over the data.
  3. stream-export — fetches contacts in cursor-based batches of 1,000 and pipes them through a streaming CSV generator directly to Vercel Blob. Only one batch is in memory at a time.
  4. send-notification — sends an email notification if the workspace has notifications enabled.
Each step is wrapped in step.run() for retry isolation — if the blob upload fails, only that step retries.

CSV Format

The exported CSV includes these columns:
ColumnSource
emailContact email
first_nameContact first name
last_nameContact last name
phoneContact phone
companyContact company
notesContact notes
is_unsubscribedtrue or false
created_atISO 8601 timestamp
custom fieldsOne column per unique custom field key across all exported contacts
Custom field columns are discovered dynamically — the system queries for all distinct jsonb_object_keys across the exported contacts and adds them as additional columns.

Downloading

Once the export completes, the CSV is stored in Vercel Blob. The download endpoint streams the blob directly to the client without buffering the entire file in memory:
GET /api/exports/:id/download
The response includes a Content-Disposition header with the sanitized filename.

Tracking Status

The frontend polls the export status endpoint to show progress. Export records include:
  • statuspending, processing, completed, or failed
  • total_rows / processed_rows — for progress calculation
  • error_message — populated if the export fails

Error Handling

ScenarioBehavior
Tag filter matches zero contactsEmpty CSV generated, export marked as completed
Blob upload failsStep retries (up to 2 retries)
Entire job failsExport marked as failed with error message
User email not foundNotification email skipped silently