diff --git a/src/app/api/whatsapp/webhook/route.ts b/src/app/api/whatsapp/webhook/route.ts index f0569fa..13a3181 100644 --- a/src/app/api/whatsapp/webhook/route.ts +++ b/src/app/api/whatsapp/webhook/route.ts @@ -383,6 +383,17 @@ async function processMessage( ? 'image' // stickers are images : 'text' // reaction, unknown → text fallback + // Determine whether this is the contact's very first inbound message + // BEFORE we insert, so the count is accurate. Covers the case where + // the contact row already exists (manual add / CSV import) but they've + // never messaged us before — which new_contact_created wouldn't catch. + const { count: priorCustomerMsgCount } = await supabaseAdmin() + .from('messages') + .select('id', { count: 'exact', head: true }) + .eq('conversation_id', conversation.id) + .eq('sender_type', 'customer') + const isFirstInboundMessage = (priorCustomerMsgCount ?? 0) === 0 + const { error: msgError } = await supabaseAdmin().from('messages').insert({ conversation_id: conversation.id, sender_type: 'customer', @@ -425,10 +436,20 @@ async function processMessage( // Fire-and-forget: a slow or failing automation must not block the // webhook's 200 OK response to Meta. const inboundText = contentText ?? message.text?.body ?? '' - const automationTriggers: ('new_contact_created' | 'new_message_received' | 'keyword_match')[] = - contactOutcome.wasCreated - ? ['new_contact_created', 'new_message_received', 'keyword_match'] - : ['new_message_received', 'keyword_match'] + const automationTriggers: ( + | 'new_contact_created' + | 'first_inbound_message' + | 'new_message_received' + | 'keyword_match' + )[] = ['new_message_received', 'keyword_match'] + // new_contact_created fires only when the webhook just auto-created the + // contact row. first_inbound_message fires whenever this is the contact's + // first-ever customer-sent message — a superset that also catches + // manually-imported contacts sending for the first time. We dispatch both + // so users can pick whichever semantic they want; an automation that + // listens to only one trigger runs only when that trigger matches. + if (contactOutcome.wasCreated) automationTriggers.unshift('new_contact_created') + if (isFirstInboundMessage) automationTriggers.unshift('first_inbound_message') for (const triggerType of automationTriggers) { runAutomationsForTrigger({ userId, diff --git a/src/components/automations/automation-builder.tsx b/src/components/automations/automation-builder.tsx index 7371e01..04d3a08 100644 --- a/src/components/automations/automation-builder.tsx +++ b/src/components/automations/automation-builder.tsx @@ -106,8 +106,13 @@ const ADDABLE_STEPS: AutomationStepType[] = [ const TRIGGER_OPTIONS: { value: AutomationTriggerType; label: string; hint: string }[] = [ { value: "new_message_received", label: "New Message Received", hint: "Any incoming message" }, + { + value: "first_inbound_message", + label: "First Message from Contact", + hint: "First time this contact ever messages you (works for manually-added contacts too)", + }, { value: "keyword_match", label: "Keyword Match", hint: "Message contains specific keyword(s)" }, - { value: "new_contact_created", label: "New Contact Created", hint: "When a contact is auto-created" }, + { value: "new_contact_created", label: "New Contact Created", hint: "When a contact is auto-created from an incoming message" }, { value: "conversation_assigned", label: "Conversation Assigned", hint: "When assigned to an agent" }, { value: "tag_added", label: "Tag Added", hint: "When a tag is added to a contact" }, { value: "time_based", label: "Time-Based", hint: "On a recurring schedule" }, diff --git a/src/lib/automations/trigger-meta.ts b/src/lib/automations/trigger-meta.ts index 2b05466..b733dd4 100644 --- a/src/lib/automations/trigger-meta.ts +++ b/src/lib/automations/trigger-meta.ts @@ -11,6 +11,10 @@ export const TRIGGER_META: Record = { label: 'New Message', pillClass: 'border-blue-500/30 bg-blue-500/10 text-blue-300', }, + first_inbound_message: { + label: 'First Message from Contact', + pillClass: 'border-teal-500/30 bg-teal-500/10 text-teal-300', + }, keyword_match: { label: 'Keyword Match', pillClass: 'border-purple-500/30 bg-purple-500/10 text-purple-300', diff --git a/src/types/index.ts b/src/types/index.ts index e60c9b9..30a688d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -214,6 +214,7 @@ export interface BroadcastRecipient { export type AutomationTriggerType = | 'new_message_received' + | 'first_inbound_message' | 'keyword_match' | 'new_contact_created' | 'conversation_assigned'