All updates

Import Contacts from CSV

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.

  • 300% increased file size
  • 10x import speed improvement
  • Full API or SDK support
  • Intelligent column matching

New dashboard experience

We've rebuilt the dashboard experience from the ground up.

  1. Upload a CSV as large as 200MB.
  2. Columns are automatically mapped to properties.
  3. Confirm your import.

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.

Programmatic imports

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.

Automatic contact properties

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',
},
},
},
});

Update or skip existing contacts

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',
});

Import process tracking

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
}
}

Get started

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.