Import millions of contacts from a CSV in a single upload, with an API, AI column mapping, and full SDK coverage.
For many, the entry point for Contacts is bulk uploading data from a previous provider or a third-party tool like a CRM.
While we've long supported CSV imports, the experience was limited. Today, we're announcing a new CSV importer that's faster, more flexible, and includes full API coverage.
We've rebuilt the dashboard experience from the ground up.
The dashboard uses AI to automatically match your CSV columns to existing properties and suggest new custom properties, so you spend less time wiring up fields by hand.
You can now create and track imports programmatically with the Contacts Import API using the API or your preferred SDK.
import { readFile } from 'node:fs/promises';import { Resend } from 'resend';const resend = new Resend('re_xxxxxxxxx');const file = new Blob([await readFile('contacts.csv')], {type: 'text/csv',});const { data, error } = await resend.contacts.imports.create({file,columnMap: {email: 'Email',firstName: 'First Name',lastName: 'Last Name',properties: {plan: {column: 'Plan',type: 'string',},},},onConflict: 'upsert',segments: [{ id: '78e7a5c6-9a91-4c63-9d1f-3b9c0b5b9ab6' }],});
We've also added full support for imports to our MCP and CLI.
When importing Contacts via the dashboard or API, we will create Contact Properties for you automatically if they don't yet exist for your team.
In the dashboard, you can reject suggestions or provide your own.
In the API, provide your properties in the column map and new Contact Properties will be created on import.
const { data, error } = await resend.contacts.imports.create({file,columnMap: {email: 'Email',properties: {plan: {column: 'Plan',type: 'string',},},},});
Choose what happens when a Contact already exists via the API.
Set on_conflict to skip to leave existing contacts untouched, or upsert to update them, which makes bulk updates as easy as a re-import.
const { data, error } = await resend.contacts.imports.create({file,columnMap: {email: 'Email',firstName: 'First Name',},onConflict: 'upsert',});
Imports run in the background. Fetch any import programmatically to watch its status and get a live breakdown of what was created, updated, skipped, or failed.
import { Resend } from "resend";const resend = new Resend("re_xxxxxxxxx");const { data } = await resend.contacts.imports.get("479e3145-dd38-476b-932c-529ceb705947",);
The response includes a status along with specific counts for more details.
{"object": "contact_import","id": "479e3145-dd38-476b-932c-529ceb705947","status": "completed","created_at": "2026-05-15 18:32:37.823+00","completed_at": "2026-05-15 18:33:42.916+00","counts": {"total": 1200,"created": 800,"updated": 300,"skipped": 75,"failed": 25}}
We've continued to invest in our Contacts experience, adding Contact Properties, Segments, and Topics to enable control, personalization, and better organization.
We trust this enhanced experience for bulk importing makes it easier to trust and use Resend. View the docs for more details.