Nodes:
name: Twilio Trigger
type: n8n-nodes-base.twilioTrigger
parameters: updates: com.twilio.messaging.inbound-message.received
name: OpenAI Chat Model1
type: @n8n/n8n-nodes-langchain.lmChatOpenAi
name: Find Follow-Up Candidates
type: n8n-nodes-base.airtable
parameters: base: __rl: yes
mode: list
value: appO2nHiT9XPuGrjN
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN
cachedResultName: Twilio-Scheduling-Agent
table: __rl: yes
mode: list
value: tblokH7uw63RpIlQ0
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN/tblokH7uw63RpIlQ0
cachedResultName: Lead Tracker
operation: search
filterByFormula: =AND(
  {appointment_id} = '',
  {status} != 'STOP',
  {followup_count} < 3,
  DATETIME_DIFF(TODAY(), {last_followup_at}, 'days') >= 3
)
name: Send Follow Up Message
type: n8n-nodes-base.twilio
parameters: to: ={{ $('Find Follow-Up Candidates').item.json.session_id }}
from: ={{ $('Find Follow-Up Candidates').item.json.twilio_service_number }}
message: ={{ $('Generate Follow Up Message').item.json.text }}
Reply STOP to stop recieving these messages.
name: Sticky Note
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 408.6631332343324
height: 515.2449997772154
content: ## Step 6. Filter Open Enquiries from Airtable

### 💡Criteria For Follow Up Candidates
* No Scheduled Appointment
* No Request to STOP
* No Previous Follow-up in Past 3 days
* Follow-up is less than 3 times
name: Update Follow-Up Count and Date
type: n8n-nodes-base.airtable
parameters: base: __rl: yes
mode: list
value: appO2nHiT9XPuGrjN
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN
cachedResultName: Twilio-Scheduling-Agent
table: __rl: yes
mode: list
value: tblokH7uw63RpIlQ0
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN/tblokH7uw63RpIlQ0
cachedResultName: Lead Tracker
columns: value: session_id: ={{ $('Find Follow-Up Candidates').item.json.session_id }}
followup_count: ={{ ($('Find Follow-Up Candidates').item.json.followup_count ?? 0) + 1 }}
last_followup_at: ={{ $now.toISO() }}
schema: type: string
display: yes
removed: yes
readOnly: yes
required: no
displayName: id
defaultMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: session_id
defaultMatch: no
canBeUsedToMatch: yes
type: options
display: yes
options: name: ACTIVE
value: ACTIVE
name: STOP
value: STOP
removed: yes
readOnly: no
required: no
displayName: status
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: customer_name
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: customer_summary
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: chat_messages
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: yes
readOnly: no
required: no
displayName: scheduled_at
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: appointment_id
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: yes
readOnly: no
required: no
displayName: last_message_at
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: no
readOnly: no
required: no
displayName: last_followup_at
defaultMatch: no
canBeUsedToMatch: yes
type: number
display: yes
removed: no
readOnly: no
required: no
displayName: followup_count
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: assignee
defaultMatch: no
canBeUsedToMatch: yes
mappingMode: defineBelow
matchingColumns: session_id
operation: update
name: Create/Update Session
type: n8n-nodes-base.airtable
parameters: base: __rl: yes
mode: list
value: appO2nHiT9XPuGrjN
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN
cachedResultName: Twilio-Scheduling-Agent
table: __rl: yes
mode: list
value: tblokH7uw63RpIlQ0
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN/tblokH7uw63RpIlQ0
cachedResultName: Lead Tracker
columns: value: session_id: ={{ $('Twilio Trigger').item.json.From }}
scheduled_at: ={{
$('Appointment Scheduling Agent').item.json.output.has_appointment_scheduled
  ? $('Appointment Scheduling Agent').item.json.output.appointment.scheduled_at
  : (
    $('Get Existing Chat Session').item.json.isNotEmpty()
    ? $('Get Existing Chat Session').item.json.scheduled_at
    : $now.toISO()
  )
}}
chat_messages: ={{
JSON.stringify(
  ($('Get Existing Chat Session').item.json.chat_messages ? JSON.parse($('Get Existing Chat Session').item.json.chat_messages) : [])
  .concat(
    { "role": "human", "message": $('Twilio Trigger').item.json.Body },
    { "role": "assistant", "message": $('Appointment Scheduling Agent').item.json.output.reply }
  )
)
}}
customer_name: ={{
  !$('Get Existing Chat Session').item.json.customer_name &&
  $('Appointment Scheduling Agent').item.json.output.customer_name
    ? $('Appointment Scheduling Agent').item.json.output.customer_name
    : ($('Get Existing Chat Session').item.json.customer_name ?? '')
}}
appointment_id: ={{
$('Appointment Scheduling Agent').item.json.output.has_appointment_scheduled
  ? $('Appointment Scheduling Agent').item.json.output.appointment.appointment_id
  : (
    $('Get Existing Chat Session').item.json.isNotEmpty()
    ? $('Get Existing Chat Session').item.json.appointment_id
    : ''
  )
}}
followup_count: ={{
  !$('Get Existing Chat Session').item.json.followup_count
  ? 0
  : $('Get Existing Chat Session').item.json.followup_count
}}
last_message_at: ={{ $now.toISO() }}
customer_summary: ={{
  !$('Get Existing Chat Session').item.json.appointment_id
  && $('Appointment Scheduling Agent').item.json.output.has_appointment_scheduled
    ? $json.output.enquiry_summary
    : $('Get Existing Chat Session').item.json.customer_summary
}}
last_followup_at: ={{
  !$('Get Existing Chat Session').item.json.last_followup_at
  ? $now.toISO()
  : $('Get Existing Chat Session').item.json.last_followup_at
}}
twilio_service_number: ={{ $('Twilio Trigger').item.json.To }}
schema: type: string
display: yes
removed: yes
readOnly: yes
required: no
displayName: id
defaultMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: session_id
defaultMatch: no
canBeUsedToMatch: yes
type: options
display: yes
options: name: ACTIVE
value: ACTIVE
name: STOP
value: STOP
removed: yes
readOnly: no
required: no
displayName: status
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: customer_name
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: customer_summary
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: chat_messages
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: no
readOnly: no
required: no
displayName: scheduled_at
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: appointment_id
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: no
readOnly: no
required: no
displayName: last_message_at
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: no
readOnly: no
required: no
displayName: last_followup_at
defaultMatch: no
canBeUsedToMatch: yes
type: number
display: yes
removed: no
readOnly: no
required: no
displayName: followup_count
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: assignee
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: twilio_service_number
defaultMatch: no
canBeUsedToMatch: yes
mappingMode: defineBelow
matchingColumns: session_id
operation: update
name: Get Existing Chat Session
type: n8n-nodes-base.airtable
parameters: base: __rl: yes
mode: list
value: appO2nHiT9XPuGrjN
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN
cachedResultName: Twilio-Scheduling-Agent
limit: 1
table: __rl: yes
mode: list
value: tblokH7uw63RpIlQ0
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN/tblokH7uw63RpIlQ0
cachedResultName: Lead Tracker
operation: search
returnAll: no
filterByFormula: ={session_id}="{{ $('Twilio Trigger').item.json.From }}"
alwaysOutputData: yes
name: Every 24hrs
type: n8n-nodes-base.scheduleTrigger
name: Send Reply
type: n8n-nodes-base.twilio
parameters: to: ={{ $('Twilio Trigger').item.json.From }}
from: ={{ $('Twilio Trigger').item.json.To }}
message: ={{ $('Appointment Scheduling Agent').item.json.output.reply }}
name: Send Confirmation
type: n8n-nodes-base.twilio
parameters: to: ={{ $('Twilio Trigger').item.json.From }}
from: ={{ $('Twilio Trigger').item.json.To }}
message: Thank you. You won't receive any more messages from us!
name: User Request STOP
type: n8n-nodes-base.airtable
parameters: base: __rl: yes
mode: list
value: appO2nHiT9XPuGrjN
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN
cachedResultName: Twilio-Scheduling-Agent
table: __rl: yes
mode: list
value: tblokH7uw63RpIlQ0
cachedResultUrl: https://airtable.com/appO2nHiT9XPuGrjN/tblokH7uw63RpIlQ0
cachedResultName: Lead Tracker
columns: value: status: STOP
session_id: ={{ $('Twilio Trigger').item.json.From }}
schema: type: string
display: yes
removed: yes
readOnly: yes
required: no
displayName: id
defaultMatch: yes
type: string
display: yes
removed: no
readOnly: no
required: no
displayName: session_id
defaultMatch: no
canBeUsedToMatch: yes
type: options
display: yes
options: name: ACTIVE
value: ACTIVE
name: STOP
value: STOP
removed: no
readOnly: no
required: no
displayName: status
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: chat_messages
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: yes
readOnly: no
required: no
displayName: scheduled_at
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: yes
readOnly: no
required: no
displayName: last_message_at
defaultMatch: no
canBeUsedToMatch: yes
type: dateTime
display: yes
removed: yes
readOnly: no
required: no
displayName: last_followup_at
defaultMatch: no
canBeUsedToMatch: yes
type: number
display: yes
removed: yes
readOnly: no
required: no
displayName: followup_count
defaultMatch: no
canBeUsedToMatch: yes
type: string
display: yes
removed: yes
readOnly: no
required: no
displayName: assignee
defaultMatch: no
canBeUsedToMatch: yes
mappingMode: defineBelow
matchingColumns: session_id
operation: update
name: Check For Command  Words
type: n8n-nodes-base.switch
parameters: rules: values: outputKey: STOP
conditions: options: caseSensitive: yes
typeValidation: strict
combinator: and
conditions: operator: type: string
operation: contains
leftValue: ={{ $json.Body }}
rightValue: STOP
renameOutput: yes
options: fallbackOutput: extra
name: Structured Output Parser
type: @n8n/n8n-nodes-langchain.outputParserStructured
parameters: jsonSchemaExample: {
    "reply": "",
    "customer_name": "",
    "enquiry_summary": "",
	"has_appointment_scheduled": false,
    "appointment": {
      "appointment_id": "",
      "scheduled_at": ""
    }
}
name: Auto-fixing Output Parser
type: @n8n/n8n-nodes-langchain.outputParserAutofixing
name: OpenAI Chat Model2
type: @n8n/n8n-nodes-langchain.lmChatOpenAi
name: Sticky Note3
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 1011.8938194478603
height: 917.533068142247
content: ## Step 3. Appointment Scheduling With AI
[Learn about using AI Agents](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent)

Using an AI Agent is a powerful way to simplify and enhance workflows using the latest in AI technology. Our appointment scheduling agent is equipped to converse with the customer and all the necessary tools to schedule, re-schedule and cancel appointments.

Using the **HTTP Tool** node, it's easy to connect to third party API services to perform actions. In this workflow, we're calling the Cal.com API to handle scheduling events.
name: Sticky Note4
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 504.0066355303578
height: 557.8466102697549
content: ## Step 2. Check for Existing Chat History
[Read more about using Airtable](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.airtable)

We're using Airtable for customer session management and to capture chat history. Airtable is an ideal choice because it acts as a persistent database with a flexible API which could prove essential for further extension.

We'll pull any previous chat history and pass this to our agent to continue the conversation.
name: Sticky Note5
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 523.6927529886705
height: 479.4432905734608
content: ## Step 1. Wait For Customer SMS
[Read more about Twilio trigger](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.twiliotrigger)

For this workflow, we'll use the twilio SMS trigger to receive enquiries from customers looking to book a PC or laptop repair.

Since we'll be working with SMS, we'll have a check to see if the customer wishes to STOP any further follow-up messages. This is an optional step that we'll get to later.
name: Sticky Note6
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 563.7797724327219
height: 358.6710117357418
content: ## Step 9. Cancelling Follow-Up Messages 

If the customer messages the bot with the word STOP, we'll update our customer record in Airtable which will prevent further follow-ups from being trigger. A confirmation message is sent after to the customer.
name: Sticky Note7
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 521.5259177258192
height: 558.7093446159199
content: ## Step 4. Updating Airtable and Responding to the Customer 
[Read more about using Twilio](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.twilio)

Once the agent formulates a response, we can update our appointment table accordingly ensuring the conversation at any stage is captured.

If no appointment is scheduled, we can move onto the second half of this workflow which covers following up with prospective customers and their enquiries.
name: Sticky Note8
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 509.931737588259
height: 433.74984757777247
content: ## Step 5. Following Up With Open Enquiries
[Read more about using scheduled trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.scheduletrigger)

The second half of this workflow deals with identifying customers who have engaged our chatbot but have not yet confirmed an appointment. We intend to send a follow-up message asking if the enquiry is still valid and encourage an appointment to be made with the customer.
name: Sticky Note1
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 567.1169284476533
height: 601.5572296901626
content: ## Step 7. Generating a Follow-Up Message
[Read more about Basic LLM Chain](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm)

With our session and chat history retrieved from Airtable, we can simple ask our AI to generate a nicely worded follow-up message to re-engage the customer.

Where the logic is linear, the Basic LLM chain is suitable for many workflows. An agent is not always required!
name: Generate Follow Up Message
type: @n8n/n8n-nodes-langchain.chainLlm
parameters: text: =
messages: messageValues: =You are an appointment scheduling assistant for PC and Laptop Repairs for a company called "PC Parts Ltd". You shall refer to yourself as the "service team". You had a conversation with a customer on {{ $json.last_message_at }} but the enquiry did not end with an appointment being scheduled.
{{ $json.last_followup_at ? `You last sent a follow-up message on ${$json.last_followup_at}` : '' }}.

You task is to ask if the prospective customer would like to continue with the enquiry using the following information gather to construct a relevant follow-up message. Try to entice the user to continue the conversation and ultimately schedule an appointment.

## About the customer
name: {{ $json.customer_name ?? '<unknown>' }}
enquiry summary: {{ $json.customer_summary ?? '<uknown>' }}

# Existing conversation
Here are the chat logs of the existing conversation:
{{ $json.chat_messages }}
promptType: define
name: Sticky Note2
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 496.0833287715134
height: 526.084030034264
content: ## Step 8. Update Follow-Up Properties and Send Message
[Read more about using Twilio](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.twilio/)

Finally, we'll update our follow-up activity as part of the customer record in Airtable. Keeping track of the number of times we follow-up helps prevent spamming the customer unnecessarily.

The follow-up message is sent via Twilio and includes instruction to disable further follow-up messages using the keyword STOP.
name: Sticky Note9
type: n8n-nodes-base.stickyNote
parameters: width: 437.0019498737189
height: 511.67220311821393
content: ## Try It Out!

### This workflow implements an appointment scheduling chatbot which is powered by an AI tools agent.
* Workflow is triggered by Customer enquires sent via SMS
* Customer session management and chat history are captured in Airtable to enable the SMS conversation.
* An AI Agent is equipped to answer any questions as well as schedule, re-schedule and cancel appointments on behalf of the customer.
* The agent's reply is sent back to the customer via SMS.
* Additional a follow-up system is implemented to re-engage customers who haven't scheduled an appointment.

 
### Need Help?
Join the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!

Happy Hacking!
name: OpenAI Chat Model3
type: @n8n/n8n-nodes-langchain.lmChatOpenAi
parameters: model: gpt-4o-mini
name: Get Availability
type: @n8n/n8n-nodes-langchain.toolHttpRequest
parameters: url: https://api.cal.com/v2/slots/available
sendQuery: yes
authentication: genericCredentialType
genericAuthType: httpHeaderAuth
parametersQuery: values: name: eventTypeId
value: ={{ 648297 }}
valueProvider: fieldValue
name: startTime
value: {startTime}
valueProvider: fieldValue
name: endTime
value: {endTime}
valueProvider: fieldValue
toolDescription: Call this tool to get the appointment availability. Dates can be variable but times are fixed - startTime must always be 9am and endTime must be 7pm. Strictly use ISO format for dates eg. "2024-01-01T09:00:00-00:00". Input schema example: ```{ "startTime": "...", "endTime": "..."}```
placeholderDefinitions: values: name: startTime
type: string
description: start of daterange in ISO format. eg. 2024-01-01T09:00:00-00:00
name: endTime
type: string
description: end of daterange in ISO format. eg. 2024-01-01T09:00:00-00:00
name: Get Existing Booking
type: @n8n/n8n-nodes-langchain.toolHttpRequest
parameters: url: https://api.cal.com/v2/bookings/{bookingUid}
sendHeaders: yes
authentication: predefinedCredentialType
toolDescription: Call this tool to get an existing booking using a booking "uid".
parametersHeaders: values: name: cal-api-version
value: 2024-08-13
valueProvider: fieldValue
nodeCredentialType: calApi
placeholderDefinitions: values: name: bookingUid
type: string
description: the uid of the booking (note: this is not the same as the id of the booking)
name: Find Existing Booking
type: @n8n/n8n-nodes-langchain.toolHttpRequest
parameters: url: https://api.cal.com/v2/bookings
jsonQuery: status: upcoming
attendeeEmail: {attendee_email}
afterStart: {date}
sendQuery: yes
sendHeaders: yes
specifyQuery: json
authentication: genericCredentialType
genericAuthType: httpHeaderAuth
toolDescription: Call this tool to search for an existing bookings with the user's email address and date. Use the "uid" field in the results as the primary booking identifier, ignore the "id" field.
parametersHeaders: values: name: cal-api-version
value: 2024-08-13
valueProvider: fieldValue
placeholderDefinitions: values: name: attendee_email
type: string
description: email address of attendee
name: date
description: Filter bookings with start after this date string. The time is always fixed at 9am.
name: Reschedule Booking
type: @n8n/n8n-nodes-langchain.toolHttpRequest
parameters: url: https://api.cal.com/v2/bookings/{bookingUid}/reschedule
method: POST
jsonBody: start: {start}
reschedulingReason: {reschedulingReason}
sendBody: yes
sendHeaders: yes
specifyBody: json
authentication: genericCredentialType
genericAuthType: httpHeaderAuth
toolDescription: Call this tool to reschedule a user's booking using a booking "uid".
parametersHeaders: values: name: cal-api-version
value: 2024-08-13
valueProvider: fieldValue
placeholderDefinitions: values: name: bookingUid
type: string
description: the uid of the booking. Note this is not the same as the id of the booking.
name: start
type: string
description: start datetime of the appointment, for example: "2024-05-30T12:00:00.000Z"
name: reschedulingReason
type: string
description: Reason for rescheduling the booking. If not given, value is "Declined to give reason."
name: Cancel Booking
type: @n8n/n8n-nodes-langchain.toolHttpRequest
parameters: url: https://api.cal.com/v2/bookings/{bookingUid}/cancel
method: POST
jsonBody: cancellationReason: {cancellationReason}
sendBody: yes
sendHeaders: yes
specifyBody: json
authentication: genericCredentialType
genericAuthType: httpHeaderAuth
toolDescription: Call this tool to cancel a user's existing booking using a booking "uid".
parametersHeaders: values: name: cal-api-version
value: 2024-08-13
valueProvider: fieldValue
placeholderDefinitions: values: name: bookingUid
type: string
description: the uid of the booking. Note this is not the same as the id of the booking.
name: cancellationReason
type: string
description: Reason for cancelling the appointment
name: Create a Booking
type: @n8n/n8n-nodes-langchain.toolHttpRequest
parameters: url: https://api.cal.com/v2/bookings
method: POST
jsonBody: {
  "eventTypeId": 648297,
  "start": "{start}",
  "attendee": {
     "name": "{attendee_name}",
     "email": "{attendee_email}",
     "timeZone": "{attendee_timezone}"
  },
  "bookingFieldsResponses": {
    "title": "{summary_of_enquiry}"
  }
}
sendBody: yes
sendHeaders: yes
specifyBody: json
authentication: genericCredentialType
genericAuthType: httpHeaderAuth
toolDescription: Call this tool to create a booking. Strictly use ISO format for dates eg. "2024-01-01T09:00:00-00:00" for API compatibility.
parametersHeaders: values: name: Content-Type
value: application/json
valueProvider: fieldValue
name: cal-api-version
value: 2024-08-13
valueProvider: fieldValue
placeholderDefinitions: values: name: start
type: string
description: The start time of the booking in ISO format. eg. "2024-01-01T09:00:00Z"
name: attendee_name
type: string
description: Name of the attendee
name: attendee_email
type: string
description: email of the attendee
name: attendee_timezone
type: string
description: If timezone is unknown, assume Europe/London.
name: summary_of_enquiry
type: string
description: short summary of the enquiry or purpose of the meeting
name: Sticky Note10
type: n8n-nodes-base.stickyNote
parameters: color: 7
width: 261.1134437946252
height: 168.99242033383513
content: ![alt](https://upload.wikimedia.org/wikipedia/commons/a/a5/Cal.com%2C_Inc._Logo.svg#100x80)
You'll need to set a custom Header Auth Credential for Cal.com API v2. See the following doc for more info: https://cal.com/docs/api-reference/v2/introduction
name: Sticky Note12
type: n8n-nodes-base.stickyNote
parameters: width: 301.851426117099
height: 360.9218237282627
content: 












### 🚨 Change EventTypeID Here!
* EventTypeID must be a number.
* Your event type dictates the allowed duration of the booking.
* If Event Type set to 30mins and the agent attempts to book 60mins, this will fail so make sure the agent knows how long to set the booking for!
name: Appointment Scheduling Agent1
type: @n8n/n8n-nodes-langchain.agent
parameters: options: systemMessage: =You are an appointment scheduling helper for a company called "PC Parts Ltd". Customers will message you enquirying for PC or laptop repairs and your job is to schedule a repair session for the user.This role is strictly to help schedule appointments so:
* you may answer questions relating to the company, "PC Parts Ltd".
* you may not answer questions relating to competitors of "PC Parts Ltd".
* you may answer questions relating to general PC or laptop repair from a non-technical perspective.
* you may not help to customer diagnose or assist in troubleshoot or debugging thei r PC or laptop issues. If the customer does ask, defer them to book an appointment where a suitable professional from PC Parts Ltd can help.
* If an appointment is scheduled for the user then the conversation is completed and you should not continue to ask the user to schedule an appointment.
* If an appointment is scheduled for the user, the user may ask for the following actions: ask about details of the existing appointment, reschedule the existing appointment or cancel an existing appointment.
* If an appointment is scheduled for the user, the user cannot schedule another appointment until the existing appointment is cancelled.

## About the company
PC Parts Ltd is based in London, UK. They offer to repair low-end to high-end PC and Laptop consumer and small business machines. They also offer custom built machines such as for gaming. There is currently a summer sale going on for 20% selected machines for repairs. The company does not repair other electronic devices such as phones, tablets or monitors.

## About the appointments
Always start your conversation by politely asking if the user wants to book a new appointment or enquire about an existing one. The date and time now is {{ $now.toISO() }}. All dates should be given in the ISO format. Each appointment should have a start and end date and time relative to today's date in the future and should be scheduled for 30 minutes.

## To book an appointment
* Before booking an appointment, ask if the user has an existing appointment.
* Ensure you have the user's email address, full name and proposed date, preferred start time before booking an appointment.
* Always check the calendar availability of the user's proposed date and time. If there is no availability, suggest the next available appointment slot.
* If the appointment booking is successful, notify the user that an email confirmation will be sent to their provided email address.
* If the appointment booking is unsuccessful, notify the user that you are unable to complete their request at the moment and to try again later.

## To find an existing appointment
* Ask the user for their email address and the date of the existing booking
* Use the user's email and date to search for the existing booking.
* If the user's email and date do not match the results or no results are returned, then the existing booking is not found.
* If the existing booking is not found, notify the user and suggest a new booking should be made.
* When the existing booking is found, ensure you tell them the booking's UID field.

# To reschedule or cancel an existing appointment
* First find the existing appointment so that you may obtain the existing appointment's booking UID.
* Display this booking UID to the user.
* Use this booking UID to reschedule or cancel an existing appointment.
* If an existing appointment ID is not found or given, then notify the user that it is not possible to complete their request at this time and they should contact via email.
* when user wants to cancel an appointment, ask for a reason for the cancellation and suggest rescheduling as an alternative. Confirm with user before cancelling an appointment.

## About the user
* The customer's session_id is "{{ $('Twilio Trigger').item.json.From }}"
{{
$json.chat_messages 
  ? '* This is a returning prospective customer.' 
  : '* This is a new customer. Ask for the details of their enquiry.'
}}
{{
$json.appointment_id 
  ? `* The customer has already scheduled an appointment at ${$json.scheduled_at} and their appointment_id is ${$json.appointment_id}`
  : '* This customer has not scheduled an appointment yet.'
}}

## Existing Conversation
{{
$json.chat_messages
  ? 'Here are the existing chat logs and should be used as context to continue the conversation:\n```\n' + JSON.parse($json.chat_messages).map(item => `${item.role}: ${item.message.replaceAll('\n', ' ')}`).join('\n') + '\n```'
  : '* There is no existing conversation so far.'
}}

hasOutputParser: yes

Connections:
Every 24hrs: main: node: Find Follow-Up Candidates
type: main
Cancel Booking: ai_tool: node: Appointment Scheduling Agent1
type: ai_tool
Twilio Trigger: main: node: Check For Command  Words
type: main
Create a Booking: ai_tool: node: Appointment Scheduling Agent1
type: ai_tool
Get Availability: ai_tool: node: Appointment Scheduling Agent1
type: ai_tool
User Request STOP: main: node: Send Confirmation
type: main
OpenAI Chat Model1: ai_languageModel: node: Generate Follow Up Message
type: ai_languageModel
OpenAI Chat Model2: ai_languageModel: node: Auto-fixing Output Parser
type: ai_languageModel
OpenAI Chat Model3: ai_languageModel: node: Appointment Scheduling Agent1
type: ai_languageModel
Reschedule Booking: ai_tool: node: Appointment Scheduling Agent1
type: ai_tool
Get Existing Booking: ai_tool: node: Appointment Scheduling Agent1
type: ai_tool
Create/Update Session: main: node: Send Reply
type: main
Find Existing Booking: ai_tool: node: Appointment Scheduling Agent1
type: ai_tool
Check For Command  Words: main: node: User Request STOP
type: main
node: Get Existing Chat Session
type: main
Structured Output Parser: ai_outputParser: node: Auto-fixing Output Parser
type: ai_outputParser
Auto-fixing Output Parser: ai_outputParser: node: Appointment Scheduling Agent1
type: ai_outputParser
Find Follow-Up Candidates: main: node: Generate Follow Up Message
type: main
Get Existing Chat Session: main: node: Appointment Scheduling Agent1
type: main
Generate Follow Up Message: main: node: Update Follow-Up Count and Date
type: main
Appointment Scheduling Agent1: main: node: Create/Update Session
type: main
Update Follow-Up Count and Date: main: node: Send Follow Up Message
type: main