Skip to main content
Your sales team is working a deal. Your marketing email goes to that person anyway. Fixing that means Brew needs to know what your CRM knows. If you have both connected to an MCP assistant, you can sync the fields you filter on, with no code. This page covers a one-directional sync, CRM into Brew. It works with any MCP-connected CRM, including Attio and HubSpot. Using Attio? See Sync Brew and Attio Over MCP for its list-entry model and the engagement writeback loop.
Every marketing recipient must have opted in to hear from you. Importing or syncing contact data into Brew is not permission to email it. See What You Can Send.
This syncs CRM context onto contacts who already opted in. It is not a way to import a prospecting list. A CRM contact from a sales conversation has not consented.

Before You Start

You need:
  • A Brew brand connected to your assistant, with opted-in contacts already in it
  • A CRM connected to the same assistant, with read access to contacts
  • Contacts that exist in both systems under the same email address
Matching is on email. A contact in one system and not the other won’t sync.

1. Check What Brew Already Has

Some fields are native. Don’t create custom fields that duplicate them. Ask your assistant:
List the custom fields in my Brew brand.

2. Create the Fields You’re Missing

Name each field after its source so nobody has to guess where the data came from.
Create these custom fields in Brew: crm_lifecycle_stage (string), crm_owner (string), crm_last_contacted (date), crm_record_id (string).
Brew normalizes names to camelCase. You declare the underscored name and get the camelCase one back:
The returned fieldName is what your audience filters use, not the name you typed. Field types are string, number, date, or bool. Declare anything date-like as date so comparisons work.

3. Pick Which CRM Properties to Sync

Only sync what you’ll actually filter on. Four fields cover most exclusion cases: Verify the internal property names before you build anything. Display labels and internal names differ in most CRMs.
Read the property definitions for contacts in [CRM] and show me the internal names and stored values for [your properties].
Some CRMs store dropdown values as internal codes or numeric IDs rather than the label you see. A filter written against the label matches nothing, so check the real stored values first.

4. Run the Sync

Get [your four properties] from [CRM] for every contact in [list or segment]. Update each matching Brew contact with those values. Tell me how many you updated and list any you couldn’t match.
Always ask for the count and the unmatched list. An assistant working a long list can skip rows without error. The count is your only proof it finished. Ask for the report in this shape so gaps are obvious:
Run a full sync across the whole group once before you trust any filter. After that, re-runs only need to cover what changed.

5. Build the Audience

Add filter conditions in the Brew UI, or ask your assistant:
Create a Brew audience called “Q3 nurture” where hubspotLifecycleStage is not opportunity, hubspotOwner is empty, and hubspotLastContacted is before 30 days ago.
Use the camelCase field names from step 2. Available operators: Audiences are saved filters, not frozen snapshots. They re-evaluate at send time against whatever was last synced. Creating one returns the match count immediately:
If that count is zero and you expected more, check your filter first. The usual cause is a value written against a display label instead of the stored one. See Create Audiences for what else you can filter on.

6. Verify

Change one thing in your CRM and re-run the sync. Check that the audience count moves by the number you expect, not just that it moved. Use two or three test contacts and confirm before you rely on it for a real send.

From MCP

Your Brew connection is bound to one brand, picked when you connected. The CRM side may be workspace-scoped depending on the provider, so confirm you are pointed at the right brand before writing. Brew tools accept an optional brand_id. Omit it to use the bound brand, since supplying a different one fails with BRAND_SCOPE_MISMATCH rather than writing to the wrong place. See Authentication and Scoping.
  • list_custom_fields shows what already exists. Fields with isCore: true are built in and can’t be created or deleted. Call this before creating anything.
  • create_custom_field takes fieldName and fieldType. Creating a field that already exists is safe, it returns the existing definition. Field names can’t contain whitespace, path separators, or quotes.
  • update_contact takes an email and a fields map, one contact per call. There’s no batch write, so work through a large list in chunks and report the running count.
  • create_contact upserts by email and returns created: true|false. Use it only for contacts who opted in through your own signup or checkout, not to pull a CRM list into Brew.
Pass an idempotency_key, a fresh UUID per intended action, so a retry replays the original result instead of acting twice. The same key with a different payload returns a 409. See Idempotency. If a CRM property has no value for a contact, leave the Brew field unset. Don’t write an empty string. An unset field reads as “not synced yet”, while a blank one falsely reads as “synced, and empty.” The full tool catalog is in MCP Tools.

When the Sync Fails

What This Does and Doesn’t Do

Syncs when asked, not automatically. Nothing fires when a record changes in your CRM. Run the sync immediately before a send. A daily sync leaves up to 24 hours of drift. That’s long enough for a rep to open a deal at 9am and your 4pm send to email them anyway. One direction. This pulls CRM data into Brew. To write engagement back to the CRM, see Sync Brew and Attio Over MCP. Matches on email only. If your CRM stores multiple addresses per contact, decide which one is authoritative before syncing. Otherwise one person becomes two Brew contacts. Stores values, not flags. Write crmLifecycleStage = "customer", not isCustomer = true. Keeping the real value means un-suppressing costs nothing and new filters don’t need a re-sync. Empty fields may be invisible. Some CRMs omit unset properties from API responses entirely rather than returning null. An absent value is ambiguous. It can mean “never happened” or “this CRM isn’t tracking it.” Check that a real sample is populated before you filter on it. A recency filter built on an empty field won’t error. It will silently fail to suppress, and you’ll hear about it from a recipient.

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:

Search Documentation

Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.

ChatGPT/Claude Integration

Click “Open in ChatGPT” at the top right of any page to explore it further with ChatGPT or Claude.