The Broker Times · Shop product
Zapier cannot import a workflow file. This is the same engine, click by click, for all 12 CRMs.
Zapier cannot import a Make blueprint. You build the same engine here, click by click. Nothing is left to guess.
Two Zaps, same split as Make:
Only the first and last steps change per CRM. The middle of Zap B is identical for Salesforce, HubSpot, Pipedrive, Zoho CRM, Salestrekker, Connective, LMG MyCRM, Effi, Finsure Infynity, ActivePipe, BrokerEngine and AFG Suite360.
Default: notify you and draft. Do not turn on a client email until you have tested the loop on yourself.
05-templates/CRM-Field-Map.csv.CRM-SCENARIOS.md.You will catch test records from Zap A. Build B, turn it on, then build A.
If you would rather not use a webhook, Zap A can be a single Zap that does everything. The webhook split is how you keep the middle identical when you add a second CRM later. Recommended.
window and then one AND group.Group 1 (90-day window) - all must be true
| Field | Rule | Value |
|---|---|---|
email | Exists | |
email_optout | Does not contain | true |
existing_client | (Text) Exactly matches | true |
tbt_fixed_expiry_state | Does not contain | opt_out |
days_to_expiry | Greater than or equal to | 83 |
days_to_expiry | Less than or equal to | 97 |
tbt_last_window_sent | Does not contain | 90 |
Group 2 (60-day): same first four rows, then days 53-67, tbt_last_window_sent does not contain 60.
Group 3 (30-day): same first four rows, then days 23-37, tbt_last_window_sent does not contain 30.
days_to_expiry is not on the hook until you add a Formatter. Add it now:
fixed_expiry_date. Then add a second Formatter Utilities or a Code by Zapier step (JavaScript) that returns the whole-day difference:const end = new Date(inputData.fixed_expiry_date);
const today = new Date();
const days = Math.round((end - today) / 86400000);
output = [{ days_to_expiry: days }];
Input fixed_expiry_date from the hook. Map days_to_expiry into the Filter.
Existing clients only. If existing_client is not the string true, the filter drops the record. That is the product.
gpt-4o-mini unless you have a reason to change it.03-skill/chatgpt.md (the Rules block plus the output shape). Ask it to return only a JSON object with keys:window, broker_task_title, broker_task_body, broker_email_subject, broker_email_html, client_email_subject, client_email_html, crm_note, conversation_openers
First name: (map first_name)
Last name: (map last_name)
Email: (map email)
Fixed expiry date: (map fixed_expiry_date)
Days until expiry: (map days_to_expiry)
Lender: (map lender)
Product: (map product_name)
Broker first name: [YOUR FIRST NAME]
Broker business name: [YOUR BUSINESS NAME]
JSON.parse the model output if the ChatGPT app does not already return fields. You need broker_email_html, broker_task_title, crm_note and window as separate values.Do not add a line that quotes a rate or a dollar saving. If you edit the prompt, re-read 04-compliance/02-What-the-AI-Must-Never-Say.md.
[YOUR BROKER EMAIL] or the broker_email field from the hook.broker_email_subject.broker_email_html. Add a line under it: Default is notify you only. The client was not emailed.This step has no extra filter. It always runs when the consent filter passed.
auto_email_client (Text) Exactly matches true.email. Subject / body: the client fields from ChatGPT.auto_email_client as false until you flip it.If you cannot add a per-step filter, do not build this step at all until the pre-launch checklist is done.
Add Paths by Zapier only if you want one Zap to serve several CRMs. Simpler: one Zap B per CRM, same middle, different last three steps. Most brokers should do the simpler thing.
The last three steps, in order:
tbt_fixed_expiry_state = touched_90 or _60 or _30, tbt_last_fixed_expiry_touch = today, tbt_last_window_sent = 90 or 60 or 30).Pick the block below that matches your CRM. Then skip the others.
broker_task_title. Description = broker_task_body. WhoId / Name ID = contact_id.crm_note. Link it to the Contact.contact_id.- tbt_fixed_expiry_state__c = touched_ plus window - tbt_last_fixed_expiry_touch__c = today - tbt_last_window_sent__c = window
POST /crm/v3/objects/notes with hs_note_body = crm_note and association type 202 to the contact.contact_id.- tbt_fixed_expiry_state = touched_ plus window - tbt_last_fixed_expiry_touch = today - tbt_last_window_sent = window
broker_task_title. Note = broker_task_body. Person = contact_id.crm_note. Person = contact_id.- state hash = touched_ plus window - touch hash = today - window hash = window
broker_task_title. Description = broker_task_body. Contact = contact_id.crm_note. Parent = the contact.- tbt_fixed_expiry_state = touched_ plus window - tbt_last_fixed_expiry_touch = today - tbt_last_window_sent = window
Zapier has no native app for these in this pack. Your aggregator must grant API access.
contact_id using broker_task_title / broker_task_body.crm_note.If they have not granted access, do not build these POSTs. Use the Google Sheets last steps below and export from that CRM.
crm_note into a Notes column.- tbt_fixed_expiry_state = touched_ plus window - tbt_last_fixed_expiry_touch = today - tbt_last_window_sent = window
Put ActivePipe or BrokerEngine or AFG Suite360 in source_crm on the sheet so you can see which export a row came from. The middle of Zap B does not change.
Create a second Zap. The trigger is the only part that is CRM-specific. The last step of Zap A is always Webhooks by Zapier → POST to Zap B's Catch Hook URL, with the canonical JSON.
Canonical JSON (every CRM):
{
"crm": "hubspot",
"contact_id": "",
"first_name": "",
"last_name": "",
"email": "",
"mobile": "",
"fixed_expiry_date": "2026-11-20",
"lender": "",
"product_name": "",
"existing_client": "true",
"email_optout": "false",
"tbt_fixed_expiry_state": "queued",
"tbt_last_fixed_expiry_touch": "",
"tbt_last_window_sent": "",
"broker_owner": "",
"broker_email": "[YOUR BROKER EMAIL]",
"broker_name": "[YOUR FIRST NAME]",
"business_name": "[YOUR BUSINESS NAME]",
"auto_email_client": "false"
}
Change crm to salesforce, pipedrive, zoho, salestrekker, connective, lmg_mycrm, effi, finsure_infynity, ActivePipe, BrokerEngine or AFG Suite360 as needed.
Leave auto_email_client as "false".
SELECT Id, FirstName, LastName, Email, MobilePhone, HasOptedOutOfEmail, OwnerId,
tbt_fixed_expiry_date__c, tbt_fixed_expiry_state__c, tbt_last_fixed_expiry_touch__c,
tbt_last_window_sent__c, tbt_existing_client__c, Lender__c, Loan_Product__c
FROM Contact
WHERE tbt_fixed_expiry_date__c = NEXT_N_DAYS:95
AND tbt_fixed_expiry_state__c != 'opt_out'
LIMIT 25
tbt_existing_client__c is true (or your Account rule).tbt_fixed_expiry_date.tbt_fixed_expiry_date exists, lifecyclestage Exactly matches customer, hs_email_optout is not true, tbt_fixed_expiry_state does not contain opt_out.existing_client = true only when the lifecycle filter passed.If Find Contacts cannot filter by date, still Filter in Zapier: date is within the next 95 days. Blueprint 02 / Zap B will drop anyone outside the 90 / 60 / 30 bands.
opt_out.tbt_fixed_expiry_date is not empty.Email exists, Email_Opt_Out is not true, state is not opt_out.If the aggregator has not granted access, do not guess the URL. Use the sheet watch below.
05-templates/Google-Sheet-Bridge-template.csv into Google Sheets. Tab name Fixed expiry.existing_client = TRUE. source_crm = the CRM name.existing_client contains true, email_optout does not contain true, tbt_fixed_expiry_state does not contain opt_out, fixed_expiry_date exists.crm = the source_crm cell.Re-export when dates change. Stamp the same row in Zap B so they are not hit twice.
Sample-fixed-expiry-clients.csv, TEST-001, and put your real mailbox on broker_email and, if you like, on email).tbt_last_window_sent on the test row). Confirm the stamp.opt_out on the test row. Confirm it stops you.In Zap A's JSON, change "auto_email_client": "false" to "true". Test on yourself first. Honour UNSUBSCRIBE the same day. There is no reply-triage Zap in this pack.
[YOUR FIRST NAME][YOUR BUSINESS NAME][YOUR BROKER EMAIL]YOUR_*_HASHauto_email_client filter.