{
  "name": "06 — Booking to Brief (Telegram)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "booking",
        "responseMode": "onReceived",
        "options": {},
        "responseData": "noData"
      },
      "id": "wf06-0001",
      "name": "Cal.com Booking Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const payload = $json.body || $json;\n// Cal.com BOOKING_CREATED payload structure\nconst booking = payload.payload || payload;\nconst attendees = booking.attendees || [];\nconst attendee = attendees[0] || {};\nconst startTime = booking.startTime || booking.startDate || null;\nconst uid = booking.uid || null;\nconst title = booking.title || 'Discovery Call';\n\nreturn [{ json: {\n  attendee_email: attendee.email || '',\n  attendee_name: attendee.name || '',\n  meeting_time: startTime,\n  cal_uid: uid,\n  title,\n  cal_link: `https://cal.example.com/booking/${uid}`,\n  raw_payload: payload\n} }];"
      },
      "id": "wf06-0002",
      "name": "Parse Cal.com Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT l.id, l.company_name, l.domain, l.research_artifact, lc.id as contact_id, lc.full_name, lc.role_title, lc.email FROM leads.leads l JOIN leads.lead_contacts lc ON lc.lead_id=l.id WHERE lc.email ILIKE $1 LIMIT 1",
        "options": {
          "queryReplacement": "={{ [$json.attendee_email] }}"
        }
      },
      "id": "wf06-0003",
      "name": "Fetch Lead by Email",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        680,
        300
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT es.subject, es.body, es.touch_number, es.sent_at,\n       er.raw_body as reply_body, er.classification\nFROM leads.email_sends es\nLEFT JOIN leads.email_replies er ON er.send_id = es.id\nWHERE es.lead_id = $1\nORDER BY es.touch_number",
        "options": {
          "queryReplacement": "={{ [$('Fetch Lead by Email').item.json.id || 0] }}"
        }
      },
      "id": "wf06-0004",
      "name": "Fetch Email Thread",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        900,
        300
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "const booking = $('Parse Cal.com Payload').item.json;\nconst lead = $('Fetch Lead by Email').item.json || {};\nconst threadItems = $('Fetch Email Thread').all ? $('Fetch Email Thread').all().map(i=>i.json) : [];\nconst ra = lead.research_artifact || {};\n\nconst hasLead = !!(lead.id);\nconst leadLine = hasLead\n  ? `Lead on file: ${lead.full_name} (${lead.role_title}) at ${lead.company_name || lead.domain}`\n  : `Not in leads DB — likely booked from referral or direct link.`;\n\nconst threadLine = threadItems.filter(t => t.touch_number).length > 0\n  ? threadItems.filter(t => t.touch_number).map(t => `- Touch ${t.touch_number}: \"${t.subject}\" ${t.reply_body ? '→ Replied: '+t.reply_body.slice(0,80) : ''}`).join('\\n')\n  : '- No outreach emails on record.';\n\nconst painLine = ra.the_specific_workflow\n  ? `Pain identified: ${ra.the_specific_workflow} (~${ra.time_cost_estimate || 'unknown time cost'})`\n  : 'Pain: unknown — ask them how they handle follow-ups and booking confirmations.';\n\nconst systemPrompt = `Write a short pre-call brief for a 30-minute discovery call. Plain text, bullet points only.\n\nBOOKING\n- Attendee: ${booking.attendee_name} <${booking.attendee_email}>\n- Time: ${booking.meeting_time}\n- ${leadLine}\n\nOUTREACH HISTORY\n${threadLine}\n\nCONTEXT\n${painLine}\n\nWrite exactly 4 bullets:\n- What their business likely looks like operationally\n- The pain you pitched (or should probe for if unknown)\n- Your best opening question\n- One signal that would make this a strong deal\n\nMax 150 words total. No headers, no markdown formatting.`;\n\nreturn [{ json: { ...booking, lead, groq_system: systemPrompt, groq_user: 'Write the brief now.' } }];"
      },
      "id": "wf06-0005",
      "name": "Build Brief Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'llama-3.3-70b-versatile', messages: [{ role: 'system', content: $json.groq_system }, { role: 'user', content: $json.groq_user }], temperature: 0.2, max_tokens: 1500 }) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "wf06-0006",
      "name": "Generate Brief (70B)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const prev = $('Build Brief Prompt').item.json;\nconst brief_md = $json.choices?.[0]?.message?.content || 'Brief generation failed.';\nreturn [{ json: { ...prev, brief_md } }];"
      },
      "id": "wf06-0007",
      "name": "Extract Brief",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        300
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
        "text": "={{ $json.brief_md }}",
        "additionalFields": {
          "disable_web_page_preview": true
        }
      },
      "id": "wf06-0008",
      "name": "Send Brief to Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1780,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO leads.bookings (lead_id, contact_id, meeting_time, cal_uid, brief_md, brief_sent_at, outcome)\nVALUES ($1, $2, $3::timestamptz, $4, $5, NOW(), 'pending')\nON CONFLICT (cal_uid) DO UPDATE SET brief_md = $5, brief_sent_at = NOW()",
        "options": {
          "queryReplacement": "={{ [$('Fetch Lead by Email').item.json.id || null, $('Fetch Lead by Email').item.json.contact_id || null, $('Extract Brief').item.json.meeting_time, $('Extract Brief').item.json.cal_uid, $('Extract Brief').item.json.brief_md] }}"
        }
      },
      "id": "wf06-0009",
      "name": "Insert Booking",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2000,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE leads.leads SET state='booked', updated_at=NOW() WHERE id=$1 AND $1 IS NOT NULL",
        "options": {
          "queryReplacement": "={{ [$('Fetch Lead by Email').item.json.id || null] }}"
        }
      },
      "id": "wf06-0010",
      "name": "Update Lead: Booked",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2220,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const meetingTime = new Date($('Extract Brief').item.json.meeting_time);\nconst reminderTime = new Date(meetingTime.getTime() - 60 * 60 * 1000);\nconst now = new Date();\nconst waitSec = Math.max(0, Math.floor((reminderTime.getTime() - now.getTime()) / 1000));\nreturn [{ json: { ...$('Extract Brief').item.json, wait_seconds: waitSec, reminder_time: reminderTime.toISOString() } }];"
      },
      "id": "wf06-0011",
      "name": "Compute Reminder Wait",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2440,
        300
      ]
    },
    {
      "parameters": {
        "amount": "={{ $json.wait_seconds }}",
        "unit": "seconds"
      },
      "id": "wf06-0012",
      "name": "Wait Until 1h Before Meeting",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2660,
        300
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
        "text": "=⏰ *1-HOUR REMINDER*\n\nYour discovery call with *{{ $('Extract Brief').item.json.lead.full_name }}* from *{{ $('Extract Brief').item.json.lead.company_name }}* starts in 1 hour\\.\n\n📅 {{ $json.booking.meeting_time }}\n🔗 Reschedule: {{ $json.booking.cal_link }}\n📌 Lead ID: \\`#{{ $('Extract Brief').item.json.lead.id }}\\`",
        "additionalFields": {
          "parse_mode": "MarkdownV2"
        }
      },
      "id": "wf06-0013",
      "name": "Send Reminder",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2880,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE leads.bookings SET reminder_sent_at = NOW() WHERE cal_uid = $1",
        "options": {
          "queryReplacement": "={{ [$('Extract Brief').item.json.cal_uid] }}"
        }
      },
      "id": "wf06-0014",
      "name": "Mark Reminder Sent",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        3100,
        300
      ]
    }
  ],
  "connections": {
    "Cal.com Booking Webhook": {
      "main": [
        [
          {
            "node": "Parse Cal.com Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Cal.com Payload": {
      "main": [
        [
          {
            "node": "Fetch Lead by Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Lead by Email": {
      "main": [
        [
          {
            "node": "Fetch Email Thread",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Email Thread": {
      "main": [
        [
          {
            "node": "Build Brief Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Brief Prompt": {
      "main": [
        [
          {
            "node": "Generate Brief (70B)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Brief (70B)": {
      "main": [
        [
          {
            "node": "Extract Brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Brief": {
      "main": [
        [
          {
            "node": "Send Brief to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Brief to Telegram": {
      "main": [
        [
          {
            "node": "Insert Booking",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Booking": {
      "main": [
        [
          {
            "node": "Update Lead: Booked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Lead: Booked": {
      "main": [
        [
          {
            "node": "Compute Reminder Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute Reminder Wait": {
      "main": [
        [
          {
            "node": "Wait Until 1h Before Meeting",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Until 1h Before Meeting": {
      "main": [
        [
          {
            "node": "Send Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reminder": {
      "main": [
        [
          {
            "node": "Mark Reminder Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
