AI Lead Qualifier and Booking Agent
Module 25 · ai · intermediate
intake.js
// Find or create the lead and add the incoming message. One lead per email address.
const s = $('Settings (edit me)').first().json;
const which = $('New Lead API').isExecuted ? 'lead' : $('Reply API').isExecuted ? 'reply' : 'book';
const req = $(which === 'lead' ? 'New Lead API' : which === 'reply' ? 'Reply API' : 'Book API').first().json;
const b = req.body ?? {};
const db = $getWorkflowStaticData('global');
db.leads ??= {}; db.holds ??= {}; db.bookings ??= {};
const now = b.now || new Date().toISOString(); // tests inject the clock
const fail = (code, error) => [{ json: { ok: false, code, error } }];
let lead;
if (which === 'lead') {
const email = String(b.email ?? '').trim().toLowerCase();
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return fail(400, 'a valid email is required');
lead = Object.values(db.leads).find((l) => l.email === email);
if (!lead) {
const id = 'L-' + Math.random().toString(36).slice(2, 8).toUpperCase();
lead = db.leads[id] = { id, email, name: String(b.name ?? '').trim(), company: String(b.company ?? '').trim(), phone: String(b.phone ?? ''),
source: String(b.source ?? 'website'), channel: b.channel === 'email' ? 'email' : 'chat', created_at: now,
answers: {}, asked: null, status: 'qualifying', score: 0, reasons: [], messages: [], offered: [], booking: null };
}
// Structured answers from a form count as answers already given
for (const k of ['need', 'budget', 'timeline', 'decision_maker']) if (b[k] !== undefined && b[k] !== '') lead.answers[k] = b[k];
} else {
lead = db.leads[String(b.lead_id ?? '')];
if (!lead) return fail(404, 'unknown lead_id');
}
const text = String(which === 'book' ? '' : b.message ?? b.text ?? '').trim().slice(0, 2000);
if (text) lead.messages.push({ from: 'lead', text, at: now });
lead.updated_at = now;
return [{ json: {
ok: true, which, lead_id: lead.id, text, slot_id: which === 'book' ? String(b.slot_id ?? '') : null, now,
calendar_mode: b.calendar_mode ?? s.calendar_mode, busy_override: Array.isArray(b.busy) ? b.busy : null,
asked: lead.asked, answers: lead.answers, offered: lead.offered, status: lead.status,
use_ai: s.use_ai && b.ai !== false && Boolean(text) && ['qualifying', 'offered'].includes(lead.status),
} }];