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:tagIdsis optional — omit it to export all contactsfilenameis optional — a default name with a timestamp is generated if not provided- The
.csvextension is appended automatically if missing
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:
- resolve-tag-filter — if tag IDs were provided, fetches the matching contact IDs. If zero contacts match, short-circuits with an empty CSV.
-
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. - 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.
- send-notification — sends an email notification if the workspace has notifications enabled.
step.run() for retry isolation — if the blob upload fails, only that step retries.
CSV Format
The exported CSV includes these columns:| Column | Source |
|---|---|
email | Contact email |
first_name | Contact first name |
last_name | Contact last name |
phone | Contact phone |
company | Contact company |
notes | Contact notes |
is_unsubscribed | true or false |
created_at | ISO 8601 timestamp |
| custom fields | One column per unique custom field key across all exported contacts |
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:Content-Disposition header with the sanitized filename.
Tracking Status
The frontend polls the export status endpoint to show progress. Export records include:status—pending,processing,completed, orfailedtotal_rows/processed_rows— for progress calculationerror_message— populated if the export fails
Error Handling
| Scenario | Behavior |
|---|---|
| Tag filter matches zero contacts | Empty CSV generated, export marked as completed |
| Blob upload fails | Step retries (up to 2 retries) |
| Entire job fails | Export marked as failed with error message |
| User email not found | Notification email skipped silently |