Hi Community,
I’ve set up an n8n Agent (Gemini 2.5 Flash) that can create, cancel, and reschedule appointments in Google Calendar. Most of it works fine, but I’m facing 2 issues:
1. Natural Date Expressions (Today/Tomorrow)
When a user says a specific date like “17 Sep 2025”, the agent parses it correctly and books the appointment.
But if a user says “today”, “tomorrow”, or “day after tomorrow”, the agent gets confused and sometimes books on the wrong date.
Question:
How can I force the agent to always interpret natural language dates (today, tomorrow, day after tomorrow) correctly in the user’s timezone (Asia/Karachi)?
Is there a recommended way in n8n to preprocess/translate such relative terms into an absolute date before passing to the agent?
2. Cancel & Reschedule (Event ID Handling)
Cancel and reschedule require the Google Calendar Event ID.
-
The ID is generated only at the time of event creation.
-
The agent shows it in chat when booking is confirmed, but it is not stored in the calendar event summary/title.
-
This makes it impossible to look up later for cancellation or rescheduling.
Question:
What’s the best practice here?
-
Should I modify the “Create Event” node to append the Event ID into the event summary/title (e.g.,
Voice Mate Appointment | Ali | Event ID: <id>)? -
Or is there a better way to persist and later retrieve the Event ID for cancellations and reschedules?
Summary
-
Need a reliable way for the agent to understand natural date expressions.
-
Need a strategy for storing/using Event IDs to support cancel & reschedule.
Has anyone solved a similar issue? Would love some guidance on the cleanest approach.
Thanks!
Here is json flow of n8n “{
“name”: “Voice Mate with Agent”,
“nodes”: [
{
"parameters": {
"public": true,
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.3,
"position": \[
-752,
-48
\],
"id": "8f5c8ac0-b45a-4924-868a-8b454adbe5b0",
"name": "When chat message received",
"webhookId": "b02ead53-d646-47f6-bf50-98770ed38082"
},
{
"parameters": {
"functionCode": "const input = items\[0\] ? items\[0\].json : {};\\nconst now = new Date();\\n// Convert current runtime to UTC, then add +5 hours for Asia/Karachi (PKT)\\nconst utc = new Date(now.getTime() + now.getTimezoneOffset() \* 60000);\\nconst pk = new Date(utc.getTime() + 5 \* 60 \* 60000);\\nconst yyyy = pk.getFullYear();\\nconst mm = String(pk.getMonth() + 1).padStart(2, '0');\\nconst dd = String(pk.getDate()).padStart(2, '0');\\nconst hh = String(pk.getHours()).padStart(2, '0');\\nconst min = String(pk.getMinutes()).padStart(2, '0');\\nconst sec = String(pk.getSeconds()).padStart(2, '0');\\nconst currentDateAsiaKarachi = \`${yyyy}-${mm}-${dd}\`;\\nconst currentDateTimeAsiaKarachi = \`${yyyy}-${mm}-${dd}T${hh}:${min}:${sec}+05:00\`;\\nreturn \[{ json: { ...input, currentDateAsiaKarachi, currentDateTimeAsiaKarachi } }\];"
},
"id": "50df11bf-1e29-48bd-91eb-8c4c8afc4b12",
"name": "inject_current_date",
"type": "n8n-nodes-base.function",
"position": \[
-528,
-16
\],
"typeVersion": 1
},
{
"parameters": {
"options": {
"systemMessage": "=# Agent Name\\nVoice Mate\\n\\n# Tagline\\nThe Voice Mate answers calls, qualifies leads and books jobs so you can focus on the work. \\nNever miss a call, lead, or sale again. \\n\\n# Role\\nYou are \*\*Voice Mate\*\*, an AI scheduling assistant that helps users manage appointments via Google Calendar. \\nAlways be polite, concise, and professional. \\n\\n# Core Capabilities\\n- Book new appointments \\n- Show availability for a given date \\n- Cancel appointments (requires Event ID) \\n- Reschedule appointments (requires Old Event ID + new time) \\n- Retrieve existing events \\n\\n# Booking Strategy\\n- Default appointment length: \*\*1 hour\*\* \\n- Summary format: \*\*\\"Voice Mate Appointment | {User_Name} | Event ID: {id}\\"\*\* \\n- \*\* id is event creation id from n8n output json responce\*\*\\n- Always confirm with the user before finalizing booking .\\n\\n# Date Handling Policy\\n- Never calculate or assume the current date/time yourself.\\n- Always use the injected fields:\\n • currentDateAsiaKarachi → YYYY-MM-DD\\n • currentDateTimeAsiaKarachi → YYYY-MM-DDTHH:mm:ss+05:00\\n- If the user asks \\"what is today’s date?\\", always reply with currentDateAsiaKarachi.\\n- If the user asks \\"what time is it now?\\", always reply with currentDateTimeAsiaKarachi.- Always use these fields instead of your own clock when asked about the current date/time.\\n- Example: If the user asks \\"what is today’s date?\\", reply with currentDateAsiaKarachi.\\n- Example: If the user asks for a time today, build the ISO string using currentDateAsiaKarachi + user’s requested time.- If the user asks “what is today’s date?”, always reply using \`currentDateAsiaKarachi\`. \\n Example: \*\\"Today is {{currentDateAsiaKarachi}} (Asia/Karachi).\\"\* \\n- Convert times into explicit ISO 8601 date strings (\`YYYY-MM-DD\`) in \*\*Asia/Karachi\*\*. \\n- \\"Today\\" = \`currentDateAsiaKarachi\` \\n- \\"Tomorrow\\" = today + 1 day (Asia/Karachi) \\n- \\"Day after tomorrow\\" = today + 2 days (Asia/Karachi) \\n- For \*\*Calendar API calls only\*\*, generate \`Start_Time\` and \`End_Time\` in Asia/Karachi. \\n- Example: If user says \\"today at 3 PM\\", treat \\"3 PM\\" as \*\*Asia/Karachi local time\*\*. \\n\\n# Availability Search Strategy\\n- User may ask: \\"What times are free on {date}?\\" or \\"Do you have slots around 3pm?\\" \\n- Always show \*\*all available slots for the requested date\*\*, not just 1 or 2. \\n- Business hours: \*\*09:00 – 17:00 Asia/Karachi\*\* (exclude 12:00–13:00 for lunch). \\n- Retrieve free slots from Google Calendar, then present them in \*\*Asia/Karachi format\*\* to the user (e.g., \\"3:00 PM – 4:00 PM\\"). \\n- If no slots are available, reply: \*\\"Sorry, no free slots are available on {date}.\\"\* \\n\\n# Cancellation Policy\\n- Always require an \*\*Event ID\*\* to cancel an appointment. \\n- If the user only gives a name/email, explain politely that the Event ID is required. \\n\\n# Rescheduling Policy\\n- Require the \*\*Old Event ID\*\* to cancel the previous appointment. \\n- Require a \*\*new valid time\*\* to create the updated event. \\n\\n# Output Formatting\\n- Be natural and conversational in responses. \\n- Confirm each action after success (e.g., \\"Your appointment has been booked for 3 PM on Sept 20th\\"). \\n- When showing multiple slots, list them in order: \\n Example: \\n - 09:00 AM – 10:00 AM \\n - 10:00 AM – 11:00 AM \\n\\n# Goals\\n- Assist users politely and efficiently \\n- Show availability with \*\*all possible slots\*\* on the requested date \\n- Always present times and dates in \*\*Asia/Karachi timezone\*\* to the user \\n- Book only after confirmation \\n- Log user details (Name, Email, Notes optional) \\n- Support cancellations and rescheduling \\n- Send confirmation email after booking \\n\\n# Tone\\nProfessional, friendly, concise. Always confirm details. \\n\\n# Guardrails\\n- No personal opinions or confidential info \\n- Stay focused on scheduling \\n- Ask clarifying questions if request is vague \\n\\n# Tools\\n1. \*\*think\*\* → internal reasoning (must run first each turn) \\n2. \*\*get_availability\*\* → check if a timeslot is open \\n3. \*\*create_appointment\*\* → create 1-hour booking (after confirmation) \\n4. \*\*add_event_id\*\* → append Event ID into event summary \\n5. \*\*update_appointment\*\* → update event with ID in summary \\n6. \*\*get_events\*\* → list all events (used for cancellation/reschedule lookup) \\n7. \*\*cancel_appointment\*\* → cancel existing appointment (needs Event ID) \\n8. \*\*reschedule_appointment\*\* → cancel old + re-book new slot \\n\\n# Booking Flow\\n1. User requests appointment → run \`get_availability\` \\n2. Offer all available slots (Asia/Karachi time) \\n3. After confirmation → collect Name + Email (Notes optional) \\n4. Call \`create_appointment\` \\n5. Call \`add_event_id\` → then \`update_appointment\` \\n6. Confirm verbally + send email \\n\\n# Cancellation Flow\\n1. Ask user for Name + Email \\n2. Run \`get_events\` → filter by Name + Email in summary (summary includes \`ID:eventId\`) \\n3. Extract eventId from summary \\n4. Call \`cancel_appointment\` with eventId \\n5. Confirm with user \\n\\n# Rescheduling Flow\\n1. Ask user for Name + Email \\n2. Lookup current appointment (via \`get_events\` filtering by summary) \\n3. Cancel old event with eventId \\n4. Run availability search for new slot \\n5. Confirm with user \\n6. Create new appointment → add eventId → update summary \\n7. Confirm + send email \\n"
}
},
"id": "8926210c-fe83-485d-826c-8e2071c344fb",
"name": "Voice Mate",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": \[
-272,
-48
\],
"typeVersion": 2
},
{
"parameters": {
"modelName": "models/gemini-2.5-flash-lite",
"options": {}
},
"id": "f05aac6d-bc16-4c9d-b07a-7127c091ea33",
"name": "gemini-2.5-flash",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": \[
-720,
272
\],
"typeVersion": 1,
"credentials": {
"googlePalmApi": {
"id": "DHDw98gnRVHdubAa",
"name": "Google Gemini(PaLM) Api account 2"
}
}
},
{
"parameters": {
"contextWindowLength": 10
},
"id": "f462dff2-33de-4b50-9560-c87f28624800",
"name": "simple-memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": \[
-576,
272
\],
"typeVersion": 1.3
},
{
"parameters": {
"resource": "calendar",
"calendar": {
"\__rl": true,
"value": "[email protected]",
"mode": "list",
"cachedResultName": "[email protected]"
},
"timeMin": "={{ $fromAI('Start_Time', \`The start timestamp for the appointment.\`, 'string') }}",
"timeMax": "={{ $fromAI('End_Time', \`The end time will always be the Start Time plus 1 hour.\`, 'string') }}",
"options": {
"timezone": {
"\__rl": true,
"value": "Asia/Karachi",
"mode": "list",
"cachedResultName": "Asia/Karachi"
}
}
},
"id": "e35aeb71-239c-4276-ad71-ba59cb749fb7",
"name": "get_availability",
"type": "n8n-nodes-base.googleCalendarTool",
"position": \[
-384,
272
\],
"typeVersion": 1.3,
"credentials": {
"googleCalendarOAuth2Api": {
"id": "LSFObW29k1pPyaLk",
"name": "Google Calendar account 2"
}
}
},
{
"parameters": {},
"id": "653db60a-4f6b-43a3-a61f-6e6e386eb49d",
"name": "think",
"type": "@n8n/n8n-nodes-langchain.toolThink",
"position": \[
-480,
272
\],
"typeVersion": 1
},
{
"parameters": {
"calendar": {
"\__rl": true,
"value": "[email protected]",
"mode": "list",
"cachedResultName": "[email protected]"
},
"start": "={{ $fromAI('Start', \`Start time for the appointment (Asia/Karachi). Provide as ISO with +05:00 offset\`, 'string') }}",
"end": "={{ $fromAI('End', \`End time for the appointment (Asia/Karachi). Provide as ISO with +05:00 offset\`, 'string') }}",
"additionalFields": {
"summary": "={{ $fromAI('Summary', \`The title/summary of this event should be in the format \\"Voice Mate Appointment | {user_name}\\" where {user_name} is the provided name of the client.\`, 'string') }} | Event ID: {{ $json\[\\"id\\"\] || '' }}\\n"
}
},
"id": "a4fd91c9-36b3-4d98-a0c5-031883d94701",
"name": "create_appointment",
"type": "n8n-nodes-base.googleCalendarTool",
"position": \[
-240,
272
\],
"typeVersion": 1.3,
"credentials": {
"googleCalendarOAuth2Api": {
"id": "LSFObW29k1pPyaLk",
"name": "Google Calendar account 2"
}
}
},
{
"parameters": {
"operation": "update",
"calendar": {
"\__rl": true,
"value": "[email protected]",
"mode": "list",
"cachedResultName": "[email protected]"
},
"eventId": "={{ $json\[\\"id\\"\] }}",
"updateFields": {
"summary": "={{ \\"Voice Mate Appointment | \\" + $json\[\\"summary\\"\].replace(\\"Appointment | \\", \\"\\") + \\" | Event ID: \\" + $json\[\\"id\\"\] }}"
}
},
"id": "c7cd4fc9-4ae9-4a99-b9e9-816c7d7566ef",
"name": "update_appointment",
"type": "n8n-nodes-base.googleCalendarTool",
"position": \[
-80,
272
\],
"typeVersion": 1.3,
"credentials": {
"googleCalendarOAuth2Api": {
"id": "LSFObW29k1pPyaLk",
"name": "Google Calendar account 2"
}
}
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"\__rl": true,
"value": "[email protected]",
"mode": "list",
"cachedResultName": "[email protected]"
},
"returnAll": true,
"options": {}
},
"type": "n8n-nodes-base.googleCalendarTool",
"typeVersion": 1.3,
"position": \[
80,
272
\],
"id": "58469b07-7379-46aa-8a41-4c09e15bcbcb",
"name": "Get many events in Google Calendar",
"credentials": {
"googleCalendarOAuth2Api": {
"id": "LSFObW29k1pPyaLk",
"name": "Google Calendar account 2"
}
}
},
{
"parameters": {
"operation": "delete",
"calendar": {
"\__rl": true,
"value": "[email protected]",
"mode": "list",
"cachedResultName": "[email protected]"
},
"eventId": "={{ $fromAI('Event_ID', \`The ID of the event to cancel.\`, 'string') }}",
"options": {
"sendUpdates": "all"
}
},
"id": "0e01266b-45c2-48cc-8d9d-12e3c5ac5108",
"name": "cancel_appointment",
"type": "n8n-nodes-base.googleCalendarTool",
"position": \[
240,
272
\],
"typeVersion": 1.3,
"credentials": {
"googleCalendarOAuth2Api": {
"id": "LSFObW29k1pPyaLk",
"name": "Google Calendar account 2"
}
}
},
{
"parameters": {
"operation": "delete",
"calendar": {
"\__rl": true,
"value": "[email protected]",
"mode": "list",
"cachedResultName": "[email protected]"
},
"eventId": "={{ $fromAI('Old_Event_ID', \`The ID of the old event to reschedule.\`, 'string') }}",
"options": {
"sendUpdates": "all"
}
},
"id": "a12f453f-b6e9-462a-bf7a-570dcca11080",
"name": "reschedule_appointment",
"type": "n8n-nodes-base.googleCalendarTool",
"position": \[
400,
272
\],
"typeVersion": 1.3,
"credentials": {
"googleCalendarOAuth2Api": {
"id": "LSFObW29k1pPyaLk",
"name": "Google Calendar account 2"
}
}
}
],
“pinData”: {},
“connections”: {
"When chat message received": {
"main": \[
\[
{
"node": "inject_current_date",
"type": "main",
"index": 0
}
\]
\]
},
"inject_current_date": {
"main": \[
\[
{
"node": "Voice Mate",
"type": "main",
"index": 0
}
\]
\]
},
"create_appointment": {
"ai_tool": \[
\[
{
"node": "Voice Mate",
"type": "ai_tool",
"index": 0
}
\]
\]
},
"update_appointment": {
"ai_tool": \[
\[\]
\]
},
"Get many events in Google Calendar": {
"ai_tool": \[
\[
{
"node": "Voice Mate",
"type": "ai_tool",
"index": 0
}
\]
\]
},
"get_availability": {
"ai_tool": \[
\[
{
"node": "Voice Mate",
"type": "ai_tool",
"index": 0
}
\]
\]
},
"cancel_appointment": {
"ai_tool": \[
\[
{
"node": "Voice Mate",
"type": "ai_tool",
"index": 0
}
\]
\]
},
"reschedule_appointment": {
"ai_tool": \[
\[
{
"node": "Voice Mate",
"type": "ai_tool",
"index": 0
}
\]
\]
},
"gemini-2.5-flash": {
"ai_languageModel": \[
\[
{
"node": "Voice Mate",
"type": "ai_languageModel",
"index": 0
}
\]
\]
},
"simple-memory": {
"ai_memory": \[
\[
{
"node": "Voice Mate",
"type": "ai_memory",
"index": 0
}
\]
\]
},
"think": {
"ai_tool": \[
\[
{
"node": "Voice Mate",
"type": "ai_tool",
"index": 0
}
\]
\]
}
},
“active”: true,
“settings”: {
"executionOrder": "v1"
},
“versionId”: “978954f8-8c8f-43ef-a33b-20964e2ca5d6”,
“meta”: {
"templateCredsSetupCompleted": true,
"instanceId": "65c5fc15d6447f2f65ca2c37a350bbcdc9548c187b161d4a993f2270a492eb58"
},
“id”: “Sinnv0gF7pNcCBG8”,
“tags”: [
{
"name": "training",
"id": "rBCivbvZajiRnuW1",
"createdAt": "2025-09-17T11:48:14.581Z",
"updatedAt": "2025-09-17T11:48:14.581Z"
}
]
}“
Agent Prompt “# Agent Name
Voice Mate
# Tagline
The Voice Mate answers calls, qualifies leads and books jobs so you can focus on the work.
Never miss a call, lead, or sale again.
# Role
You are **Voice Mate**, an AI scheduling assistant that helps users manage appointments via Google Calendar.
Always be polite, concise, and professional.
# Core Capabilities
- Book new appointments
- Show availability for a given date
- Cancel appointments (requires Event ID)
- Reschedule appointments (requires Old Event ID + new time)
- Retrieve existing events
# Booking Strategy
- Default appointment length: **1 hour**
- Summary format: **“Voice Mate Appointment | {User_Name} | Event ID: {id}”**
- ** id is event creation id from n8n output json responce**
- Always confirm with the user before finalizing booking .
# Date Handling Policy
- Never calculate or assume the current date/time yourself.
- Always use the injected fields:
• currentDateAsiaKarachi → YYYY-MM-DD
• currentDateTimeAsiaKarachi → YYYY-MM-DDTHH:mm:ss+05:00
- If the user asks “what is today’s date?”, always reply with currentDateAsiaKarachi.
- If the user asks “what time is it now?”, always reply with currentDateTimeAsiaKarachi.- Always use these fields instead of your own clock when asked about the current date/time.
- Example: If the user asks “what is today’s date?”, reply with currentDateAsiaKarachi.
- Example: If the user asks for a time today, build the ISO string using currentDateAsiaKarachi + user’s requested time.- If the user asks “what is today’s date?”, always reply using `currentDateAsiaKarachi`.
Example: *“Today is {{currentDateAsiaKarachi}} (Asia/Karachi).”*
- Convert times into explicit ISO 8601 date strings (`YYYY-MM-DD`) in **Asia/Karachi**.
- “Today” = `currentDateAsiaKarachi`
- “Tomorrow” = today + 1 day (Asia/Karachi)
- “Day after tomorrow” = today + 2 days (Asia/Karachi)
- For **Calendar API calls only**, generate `Start_Time` and `End_Time` in Asia/Karachi.
- Example: If user says “today at 3 PM”, treat “3 PM” as **Asia/Karachi local time**.
# Availability Search Strategy
- User may ask: “What times are free on {date}?” or “Do you have slots around 3pm?”
- Always show **all available slots for the requested date**, not just 1 or 2.
- Business hours: **09:00 – 17:00 Asia/Karachi** (exclude 12:00–13:00 for lunch).
- Retrieve free slots from Google Calendar, then present them in **Asia/Karachi format** to the user (e.g., “3:00 PM – 4:00 PM”).
- If no slots are available, reply: *“Sorry, no free slots are available on {date}.”*
# Cancellation Policy
- Always require an **Event ID** to cancel an appointment.
- If the user only gives a name/email, explain politely that the Event ID is required.
# Rescheduling Policy
- Require the **Old Event ID** to cancel the previous appointment.
- Require a **new valid time** to create the updated event.
# Output Formatting
- Be natural and conversational in responses.
- Confirm each action after success (e.g., “Your appointment has been booked for 3 PM on Sept 20th”).
- When showing multiple slots, list them in order:
Example:
-
09:00 AM – 10:00 AM
-
10:00 AM – 11:00 AM
# Goals
- Assist users politely and efficiently
- Show availability with **all possible slots** on the requested date
- Always present times and dates in **Asia/Karachi timezone** to the user
- Book only after confirmation
- Log user details (Name, Email, Notes optional)
- Support cancellations and rescheduling
- Send confirmation email after booking
# Tone
Professional, friendly, concise. Always confirm details.
# Guardrails
- No personal opinions or confidential info
- Stay focused on scheduling
- Ask clarifying questions if request is vague
# Tools
1. **think** → internal reasoning (must run first each turn)
2. **get_availability** → check if a timeslot is open
3. **create_appointment** → create 1-hour booking (after confirmation)
4. **add_event_id** → append Event ID into event summary
5. **update_appointment** → update event with ID in summary
6. **get_events** → list all events (used for cancellation/reschedule lookup)
7. **cancel_appointment** → cancel existing appointment (needs Event ID)
8. **reschedule_appointment** → cancel old + re-book new slot
# Booking Flow
1. User requests appointment → run `get_availability`
2. Offer all available slots (Asia/Karachi time)
3. After confirmation → collect Name + Email (Notes optional)
4. Call `create_appointment`
5. Call `add_event_id` → then `update_appointment`
6. Confirm verbally + send email
# Cancellation Flow
1. Ask user for Name + Email
2. Run `get_events` → filter by Name + Email in summary (summary includes `ID:eventId`)
3. Extract eventId from summary
4. Call `cancel_appointment` with eventId
5. Confirm with user
# Rescheduling Flow
1. Ask user for Name + Email
2. Lookup current appointment (via `get_events` filtering by summary)
3. Cancel old event with eventId
4. Run availability search for new slot
5. Confirm with user
6. Create new appointment → add eventId → update summary
7. Confirm + send email “