The Broker Times · Shop product

The Fixed Expiry Engine - Zapier setup guide

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.

Before you open Zapier

  1. Create or map the fields in 05-templates/CRM-Field-Map.csv.
  2. Read your CRM section in CRM-SCENARIOS.md.
  3. Add yourself as a test client with a fixed-expiry date 90, 60 or 30 days out.
  4. Have a ChatGPT / OpenAI connection in Zapier and a mailbox you actually read.

Zap B first (outreach)

You will catch test records from Zap A. Build B, turn it on, then build A.

B1. Trigger - Webhooks by Zapier

  1. Create Zap → search Webhooks by Zapier.
  2. Trigger event: Catch Hook.
  3. Continue. Copy the Custom Webhook URL. You will paste it into Zap A.
  4. Do not load a sample yet. Leave this tab open.

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.

B2. Filter - consent + window + stamp

  1. Add a step → Filter.
  2. You will add three OR groups (Zapier: "or" between filter groups, "and" inside a group). If your Zapier plan only allows one group, use a Formatter step first to set window and then one AND group.

Group 1 (90-day window) - all must be true

FieldRuleValue
emailExists
email_optoutDoes not containtrue
existing_client(Text) Exactly matchestrue
tbt_fixed_expiry_stateDoes not containopt_out
days_to_expiryGreater than or equal to83
days_to_expiryLess than or equal to97
tbt_last_window_sentDoes not contain90

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:

  1. Above the Filter, add Formatter by ZapierDate / TimeAdd/Subtract Time is the wrong tool. Use Formatter → Numbers only after you have a number.
  2. Easier path: Formatter → Date / Time → Format. Input 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.

B3. ChatGPT - draft the pack

  1. Add ChatGPT (or OpenAI) → Conversation / Generate Message (the action that takes a system prompt and a user prompt).
  2. Connect your OpenAI account.
  3. Model: gpt-4o-mini unless you have a reason to change it.
  4. Paste the system prompt from 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

  1. User prompt, map the hook fields:
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]
  1. Add a Formatter → Text → Split or a Code step to 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.

B4. Email you (default on)

  1. Add GmailSend Email (or Outlook, if that is the mailbox you read).
  2. To: [YOUR BROKER EMAIL] or the broker_email field from the hook.
  3. Subject: map broker_email_subject.
  4. Body: map broker_email_html. Add a line under it: Default is notify you only. The client was not emailed.
  5. From: your business domain mailbox.

This step has no extra filter. It always runs when the consent filter passed.

B5. Email the client (default off)

  1. Add another Gmail Send Email.
  2. Click the step → ...Add a filter (or put a Filter step immediately above it).
  3. Filter: auto_email_client (Text) Exactly matches true.
  4. To: map email. Subject / body: the client fields from ChatGPT.
  5. Leave this filter in place. Zap A will send 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.

B6. Paths for the write-back

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:

  1. Create a task.
  2. Write a note.
  3. Stamp the record (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.


Last three steps, per CRM

Salesforce (native)

  1. Salesforce → Create Record (Task). Subject = broker_task_title. Description = broker_task_body. WhoId / Name ID = contact_id.
  2. Salesforce → Create Record (ContentNote or Note, whichever your org uses). Body = crm_note. Link it to the Contact.
  3. Salesforce → Update Record (Contact). Id = contact_id.

- tbt_fixed_expiry_state__c = touched_ plus window - tbt_last_fixed_expiry_touch__c = today - tbt_last_window_sent__c = window

HubSpot (native)

  1. HubSpot → Create Task. Title / notes from the ChatGPT fields. Associate to the contact id.
  2. HubSpot → Create Engagement (Note), or API Request POST /crm/v3/objects/notes with hs_note_body = crm_note and association type 202 to the contact.
  3. HubSpot → Update Contact. Record ID = contact_id.

- tbt_fixed_expiry_state = touched_ plus window - tbt_last_fixed_expiry_touch = today - tbt_last_window_sent = window

Pipedrive (native)

  1. Pipedrive → Create Activity. Type: Task. Subject = broker_task_title. Note = broker_task_body. Person = contact_id.
  2. Pipedrive → Create Note. Content = crm_note. Person = contact_id.
  3. Pipedrive → Update Person. Map your custom-field hashes:

- state hash = touched_ plus window - touch hash = today - window hash = window

Zoho CRM (native)

  1. Zoho CRM → Create Task. Subject = broker_task_title. Description = broker_task_body. Contact = contact_id.
  2. Zoho CRM → Create Module Entry (Notes). Note Content = crm_note. Parent = the contact.
  3. Zoho CRM → Update Module Entry (Contacts).

- tbt_fixed_expiry_state = touched_ plus window - tbt_last_fixed_expiry_touch = today - tbt_last_window_sent = window

Salestrekker, Connective, LMG MyCRM, Effi, Finsure Infynity (API)

Zapier has no native app for these in this pack. Your aggregator must grant API access.

  1. Webhooks by Zapier → POST. URL and headers from the aggregator. Body: create a task on contact_id using broker_task_title / broker_task_body.
  2. Webhooks by Zapier → POST. Create a note with crm_note.
  3. Webhooks by Zapier → PUT or PATCH. Stamp the three fields. Names are in the field map. Confirm them with the aggregator before you guess.

If they have not granted access, do not build these POSTs. Use the Google Sheets last steps below and export from that CRM.

ActivePipe, BrokerEngine, AFG Suite360 (Google Sheets bridge)

  1. Skip a native task, or Google Sheets → Create Spreadsheet Row on a Tasks tab if you want a visible list.
  2. Google Sheets → Update Spreadsheet Row. Row ID from Zap A. Write crm_note into a Notes column.
  3. Same Update (or a second one) on the same row:

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


Zap A - Watch (first step only)

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".

Salesforce watch

  1. Trigger: Salesforce → New or Updated Record (Contact), or Salesforce → SOQL Query on a schedule if you have it.
  2. Preferred: Schedule by Zapier (every day 9:00) + Salesforce → Find Records with SOQL:
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
  1. Filter: Email exists, tbt_existing_client__c is true (or your Account rule).
  2. Webhooks → POST the canonical JSON. Map Salesforce field names from the field map.

HubSpot watch

  1. Schedule by Zapier every day 9:00.
  2. HubSpot → Find Contacts (or Recent Contacts, then Filter). Properties: the list in the field map, including tbt_fixed_expiry_date.
  3. Filter: tbt_fixed_expiry_date exists, lifecyclestage Exactly matches customer, hs_email_optout is not true, tbt_fixed_expiry_state does not contain opt_out.
  4. Webhooks → POST the canonical JSON. 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.

Pipedrive watch

  1. Schedule by Zapier every day 9:00.
  2. Pipedrive → Find Persons or Get Persons. You will need the custom-field hashes from Settings → Data fields.
  3. Filter: expiry hash exists, email exists, state hash does not contain opt_out.
  4. Webhooks → POST. Map hashes to the canonical keys.

Zoho CRM watch

  1. Schedule by Zapier every day 9:00.
  2. Zoho CRM → Find Module Entries (Contacts) with criteria tbt_fixed_expiry_date is not empty.
  3. Filter: Email exists, Email_Opt_Out is not true, state is not opt_out.
  4. Webhooks → POST.

Salestrekker / Connective / LMG MyCRM / Effi / Finsure Infynity watch

  1. Schedule by Zapier every day 9:00.
  2. Webhooks by Zapier → GET (or POST) the search URL the aggregator gave you. Auth header stored in Zapier, not in a shared screenshot.
  3. Filter existing clients with an expiry date.
  4. Webhooks → POST the canonical JSON to Zap B.

If the aggregator has not granted access, do not guess the URL. Use the sheet watch below.

ActivePipe / BrokerEngine / AFG Suite360 watch (and fallback)

  1. Import 05-templates/Google-Sheet-Bridge-template.csv into Google Sheets. Tab name Fixed expiry.
  2. Export contacts that have a fixed-expiry date. existing_client = TRUE. source_crm = the CRM name.
  3. Trigger: Google Sheets → New or Updated Spreadsheet Row, or Schedule + Google Sheets → Lookup Spreadsheet Rows.
  4. Filter: existing_client contains true, email_optout does not contain true, tbt_fixed_expiry_state does not contain opt_out, fixed_expiry_date exists.
  5. Webhooks → POST. crm = the source_crm cell.

Re-export when dates change. Stamp the same row in Zap B so they are not hit twice.


Click-by-click: first-time publish order

  1. Build Zap B through the ChatGPT step and the broker email. Turn Zap B on.
  2. In Zap A's webhook step, send a test payload of yourself (use Sample-fixed-expiry-clients.csv, TEST-001, and put your real mailbox on broker_email and, if you like, on email).
  3. Confirm Zap B runs. Confirm you got the broker email. Confirm you did not get a client email.
  4. Add the write-back steps. Run again on a fresh window (or clear tbt_last_window_sent on the test row). Confirm the stamp.
  5. Run a third time. Confirm Zap B's Filter stops you.
  6. Set opt_out on the test row. Confirm it stops you.
  7. Only then point Zap A at the live set, limit 25, 9:00 weekdays.
  8. Read every draft for a week. Broker judgement stays human.

Flip the client-email switch

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.

Placeholders you must replace

Do not