Skip to content

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:

  1. Brand replies to an agent email → Companion Zap 1
  2. 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 on
  • email___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:

  1. Lowercases + trims the email address
  2. Queries Airtable for all records where email = sender email
  3. Filters to only records where Responded is not already true
  4. PATCHes all matching records to set Responded = true
  5. 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

KeyValue (from Step 1)
sender_emailemail__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:

  1. Searches Airtable for all records where email = bookerEmail
  2. PATCHes all matches: Responded = true, Has Booked = true
  3. Returns the count of updated records (or 0 if none found)
  4. 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 sequence
  • Has 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:

  1. Find the record in Airtable Inbound Messages (filter by sender email)
  2. Check the Responded checkbox 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).