Open the Audience tab in the sidebar, then click Add contacts in the top right to choose a method.
Moving from another platform? The migration guides cover contacts, consent, and suppressions for Mailchimp, Klaviyo, Postmark, Brevo, Customer.io, and Loops.
CSV Upload
The fastest way to import a lot of contacts at once. Brew handles deduplication and partial updates automatically, and can validate every address as it’s imported if you opt in.1
Prepare your CSV
Required column:
Email, the contact’s email address
First NameLast Name
Subscribed.trueorfalse. Defaults totrueif missing.- Any custom property you’ve created in Brew
YYYY-MM-DD. See date formats for everything Brew reads.2
Open the import screen
Audience → Add contacts → CSV Upload.
3
Upload your CSV
Drag and drop, or click to browse. Wait for it to process.
4
Map columns to Brew properties
For each CSV column, confirm the mapping is right. Unrecognized columns become new custom properties, named in camelCase: a
push source column becomes pushSource. Use that name in merge tags and filters.For a date column, the row shows the order its dates are read in. When every date could be either (03/04/2026 is 3 April or 4 March), pick the reading that matches your file. A column with a day over 12 anywhere is detected for you.5
Review and import
Tick the consent checkbox to confirm these contacts agreed to receive
your marketing email. Brew records consent once for the whole file,
dated the day you import.To deliverability-check every address as it’s ingested, tick the
validation checkbox (2 credits per
address). Then click Complete Import. Contacts process in the
background, so you can navigate away.
Updating Existing Contacts
CSV upload also updates contacts that already exist.Matching
Matching
Brew matches on email address. If the email exists, the row updates the existing contact. If not, a new contact is created.
Partial updates
Partial updates
Empty cells don’t overwrite existing data. Only cells with values update the corresponding property.
Subscription status
Subscription status
- If the
Subscribedcolumn is missing, new contacts are subscribed by default and existing contacts keep their status. - To import unsubscribed contacts, include
Subscribedwithfalse(or a status exported from another email platform, such asunsubscribedorcleaned). This also unsubscribes contacts that already exist, so the import tells you how many before you confirm. - The importer never re-subscribes contacts who have previously unsubscribed.
Common Issues
Duplicate contacts
Duplicate contacts
If your CSV has duplicate emails, Brew processes them in order: the last row wins. Deduplicate before uploading to avoid surprises.
Invalid email formats
Invalid email formats
Emails must be in a valid format (such as
name@domain.com). Invalid emails are flagged at the review step and skipped.Encoding issues
Encoding issues
Make sure your CSV is UTF-8 encoded. Garbled characters in the column-mapping screen mean the file isn’t UTF-8, so re-export it.
Dates in the wrong month
Dates in the wrong month
A date like
03/04/2026 can be read day first or month first. Check the order shown on the date column at the mapping step, or convert dates to YYYY-MM-DD before uploading.When importing over the API, the CLI or MCP (import_contacts_csv), pass dateOrder (day_first or month_first). See date formats.Large files
Large files
Very large CSVs can take several minutes. Split huge files into smaller batches if you want easier tracking.
Import History
Past imports appear below the upload area on the CSV Upload page, with the date, count, and any errors. The list includes every import: app uploads, and batches sent over the API, SDK, CLI, MCP, or Brew chat. A batch appears under itsfileName; the CLI sends the file’s name. Unnamed batches appear as API, MCP, or Brew chat imports.
Each contact an import writes keeps its importId until a later import touches it. Named batches also set csvFileName. View Contacts opens the audience filtered to that import.
Delete import removes the contacts it created. Contacts it only updated remain.
Add Manually
For a few contacts at a time, go to Audience → Add contacts → Add manually and enter the email and optional properties. The form has the same validation checkbox as the CSV import. Tick it and the address is checked as it’s saved.Sync from Integrations
Connecting an event-source integration keeps contacts flowing into Brew automatically. Every webhook the provider sends does two things at once:- It fires every Live automation whose trigger matches the event.
- It upserts the contact into your audience with provider-namespaced custom fields.
Fields show on the contact in camelCase, such as
clerkUserId. Contact search also accepts other spellings, such as clerk_user_id. A saved audience filter needs the exact key: another spelling saves without a warning but matches no one.
Custom field definitions are auto-created on first write. New fields show up filterable in the audience UI with no extra setup.
API
Programmatically create, update, or delete contacts from your backend. There are two ways to import a batch, both recorded in Import History and both returning the batch’simportId (a preview records nothing and returns none):
Both imports create missing contacts (subscribed unless a row opts out) and update existing ones, unless
onConflict is "skip". Pass fileName to name the batch.
For recurring syncs that shouldn’t appear in Import History, use POST /v1/contacts with a contacts array (upsertMany(), brew-cli contacts upsert-many). All of these take an optional validate: true to check each address as it’s ingested.
- Limits. Each call takes up to 1,000 contacts or CSV rows.
- Counts.
summary.totalcounts the submitted JSON contacts or parsed CSV data rows. It equalsinserted + updated + failed + skipped. - Partial results. Inspect
errorsandwarningsbefore retrying. Each failed row has its own error with a 1-basedrow:contacts[row - 1]for JSON, or the CSV data row, counting neither the header nor blank lines. Fix and retry only failed rows; successful writes have already landed. - Skipped rows. With
onConflict: "skip", each existing contact counts as skipped. In a CSV, missing or invalid addresses and earlier duplicates are counted as skipped too.CSV_ROW_SKIPPEDwarnings name the first 100 skipped rows; one additional warning counts the rest. Column-wide and aggregate warnings have no row index. - Duplicates. A CSV processes the last row for each repeated address; inspect its result before assuming it was saved. A JSON batch fails every row of a repeated address, so send each contact once.
- Consent. Send
consentwith a new subscribed contact to record how they opted in: asource(api,form, orimport) and, optionally, when. The app’s import checkbox recordsapp. - Managed columns. A column Brew manages, such as
Created AtorValidation Status, isn’t imported. The response names it in aCSV_COLUMN_IGNOREDwarning.
import_contacts and import_contacts_csv create and update contacts like their API routes; update_contact updates one existing contact only.
This CSV import answers 207: row 3 has a value its field rejects, and row 2 has no email.
Preview an Import, or Add New Contacts Only
Both imports take two optional modes:dryRun: truereturns the response the import would return and writes nothing: no contacts, no new fields (fieldsCreatedlists the ones it would create), no Import History batch. The response carriesdryRun: trueand noimportId;validateisn’t run and nothing is charged. A real run on the same data right after returns the same counts, so checkerrorsandwarningson the preview, then import. A preview can’t foresee a failure that only happens while writing, such as a concurrent change.onConflict: "skip"creates missing contacts and leaves every existing contact unchanged, an opt-out row included. Each existing contact counts insummary.skipped, with aCONTACT_EXISTS_SKIPPEDwarning naming itsrow; when that row asked to unsubscribe, the warning says its opt-out was not applied. To apply opt-outs, import them with the default mode. Withvalidate: true, inline checks are still metered per submitted row, skipped ones included; a call that leaves nothing to check isn’t charged. The default,"upsert", updates existing contacts.
View API documentation
Endpoints, authentication, and code examples
Need Help?
Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:- Self-Service Tools
- Talk to Our Team
Search Documentation
Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.
Ask ChatGPT or Claude
Click here to open the Copy page menu at the top of this page, then send the page to ChatGPT, Claude, or your editor.