Appearance
Responded Detection
The Responded field in Airtable (fldjDZdTmKt46zMid) is the single flag that stops follow-up emails. There are two ways it gets set to true:
- Brand replies to an agent email → Companion Zap 1
- Brand books a Cal.com call → Cal.com handler Zap
1. Brand Reply Detection (Companion Zap 1)
Zap ID: 356311797
Name: "Close CRM inbound emails → Airtable Mark as Responded"
Why Close CRM, not Gmail?
The main inbound Zap sends replies from the agent's email (charlie@, annie@, henry@). So brand replies land in those individual inboxes — not in intake@. Gmail watching intake@ would miss them entirely.
All three agents have their Gmail accounts synced to Close CRM. When a brand replies to Charlie's email, Close logs it as an inbound email activity on that lead within minutes. This gives us a single trigger point that covers all agents.
How It Works
Step 1 — Close CRM Trigger: New Activity
Fires whenever Close logs a new activity on any lead.
Step 2 — Filter
Two conditions must both be true:
lead__status_label=Inbound Follow-Up— only care about leads we're actively following up onemail___type=Email— only email activities (not notes, calls, or other activity types)
Step 3 — Code by Zapier
This is the core step. It receives sender_email (wired from Step 1's email__envelope__from[]email) and:
- Lowercases + trims the email address
- Queries Airtable for all records where
email= sender email - Filters to only records where
Respondedis not alreadytrue - PATCHes all matching records to set
Responded = true - Returns
{ updated: N, email: "..." }or{ updated: 0, reason: "..." }for logging
Why patch all records, not just one?
A brand may have emailed multiple creators on the roster. Each creator generates a separate Airtable record — all with the same sender email. If Famesters emailed 3 creators, there are 3 records. When Famesters replies to any one of those agents, we want to stop all follow-ups for Famesters — not just the one for the creator they replied about. The code step patches every matching record in one shot.
Code
javascript
const AIRTABLE_TOKEN = "patoqV2H6c3eSvmnl..."; // full token in Zapier
const AIRTABLE_BASE = "appt783peNrkiYMHB";
const AIRTABLE_TABLE = "tblRfRTEd6kfW9slT";
const AT_RESPONDED = "fldjDZdTmKt46zMid";
const senderEmail = (inputData.sender_email || "").toLowerCase().trim();
if (!senderEmail) return { updated: 0, reason: "no sender email" };
const formula = encodeURIComponent(`{email}="${senderEmail}"`);
const searchRes = await fetch(
`https://api.airtable.com/v0/${AIRTABLE_BASE}/${AIRTABLE_TABLE}?filterByFormula=${formula}`,
{ headers: { "Authorization": `Bearer ${AIRTABLE_TOKEN}` } }
);
if (!searchRes.ok) return { updated: 0, reason: "airtable search failed: " + searchRes.status };
const data = await searchRes.json();
const records = (data.records || []).filter(r => !r.fields["Responded"]);
if (!records.length) return { updated: 0, reason: "no unresponded records found" };
await Promise.all(records.map(rec =>
fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE}/${AIRTABLE_TABLE}/${rec.id}`, {
method: "PATCH",
headers: { "Authorization": `Bearer ${AIRTABLE_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ fields: { [AT_RESPONDED]: true } }),
})
));
return { updated: records.length, email: senderEmail };Input Data Wiring
| Key | Value (from Step 1) |
|---|---|
sender_email | email__envelope__from[]email |
Tested
promo@famesters.net— 2 records updated (charlie@ + annie@) ✅- Idempotency: re-running on already-responded records returns
updated: 0✅
2. Booking Detection (Cal.com Handler)
Not a separate Zap — baked directly into the existing Cal.com → Close handler Zap as a function at the end of handleCreated().
How It Works
The Cal.com webhook payload always contains the booker's email address (bookerEmail — the non-CA attendee). After the main booking logic runs (create/find Close lead, log activity, post Slack), the handler calls:
javascript
await markAirtableBooked(bookerEmail);This function:
- Searches Airtable for all records where
email= bookerEmail - PATCHes all matches:
Responded = true,Has Booked = true - Returns the count of updated records (or 0 if none found)
- Always fails silently — a failure here should never break the booking flow
If the booker has no Airtable record (they came through a cold outbound channel, not our inbound flow), the function finds nothing and returns 0. No error, no impact.
Why Both Fields?
Responded = true— stops the follow-up sequenceHas Booked = true— separate signal for reporting/analytics (know when someone went from inbound → booked)
Tested
robin@intelops.technology— Responded = true, Has Booked = true ✅
Notes & Edge Cases
Email case sensitivity: The code lowercases all emails before searching. Airtable's filterByFormula is case-insensitive, so this is belt-and-suspenders.
Different reply address: If a brand replies from a different address than they originally used (e.g., initial email from info@brand.com, reply from jane@brand.com), the match will fail. This is uncommon but possible with larger companies. Currently no mitigation — acceptable risk.
Close sync delay: If Close is slow to sync an agent email (unusual but possible), a follow-up could go out before the reply is detected. Maximum exposure: one extra follow-up. Low impact.
Already Responded: The code filters out already-Responded records before patching. Running the same email twice will correctly return updated: 0.
Manual Override
If a brand responds outside the normal flow (e.g., replies from a new address, calls instead of emails, or reaches out via LinkedIn), the automatic detection won't fire. To stop follow-ups immediately:
- Find the record in Airtable Inbound Messages (filter by sender email)
- Check the
Respondedcheckbox manually
This is the fastest and most reliable fallback. The follow-up cron checks Responded at the start of every run — setting it manually takes effect on the next scheduled run (Mon/Wed/Fri 8:45 AM CT).