This workflow corresponds to n8n.io template #16155 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Send a rescheduling email after a missed appointment",
"tags": [],
"nodes": [
{
"id": "overview-note",
"name": "Workflow guide",
"type": "n8n-nodes-base.stickyNote",
"position": [
-520,
-320
],
"parameters": {
"color": 4,
"width": 520,
"height": 580,
"content": "## Send a rescheduling email after a missed appointment\n\nThis workflow receives a missed-appointment webhook, validates the event, blocks ineligible contacts, sends one rescheduling email through Gmail, and returns a JSON acknowledgement.\n\n### Who it is for\n\nLocal businesses, agencies, and operators who receive appointment status updates from a CRM or scheduling system.\n\n### Setup\n\n1. Open **Configure recovery message** and set the business name, booking URL, and subject.\n2. Connect a customer-owned Gmail credential to **Send rescheduling email**.\n3. Send the example payload below to the test webhook.\n4. Confirm that non-missed appointments and contacts with `transactional_contact_allowed: false` do not send.\n5. Activate only after the business approves the message and data flow.\n\nThe imported workflow is inactive. Use a contact you control for testing."
},
"typeVersion": 1
},
{
"id": "payload-note",
"name": "Test payload",
"type": "n8n-nodes-base.stickyNote",
"position": [
-520,
300
],
"parameters": {
"color": 7,
"width": 440,
"height": 430,
"content": "## Example webhook payload\n\n```json\n{\n \"event_id\": \"evt_test_001\",\n \"contact\": {\n \"name\": \"Test Customer\",\n \"email\": \"operator-controlled@example.com\",\n \"transactional_contact_allowed\": true\n },\n \"appointment\": {\n \"id\": \"apt_test_001\",\n \"status\": \"missed\"\n }\n}\n```\n\nUse a stable, unique `event_id`. For production, add a Data Store lookup if your email provider does not support idempotency."
},
"typeVersion": 1
},
{
"id": "missed-webhook",
"name": "Receive missed appointment",
"type": "n8n-nodes-base.webhook",
"position": [
40,
0
],
"parameters": {
"path": "missed-appointment-recovery",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "configuration",
"name": "Configure recovery message",
"type": "n8n-nodes-base.set",
"position": [
280,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "business-name",
"name": "business_name",
"type": "string",
"value": "Your Business"
},
{
"id": "booking-url",
"name": "booking_url",
"type": "string",
"value": "https://example.com/book"
},
{
"id": "email-subject",
"name": "email_subject",
"type": "string",
"value": "Would you like to reschedule?"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "prepare",
"name": "Validate event and prepare email",
"type": "n8n-nodes-base.code",
"position": [
520,
0
],
"parameters": {
"jsCode": "const p = $json.body || $json;\nconst required = [p.event_id, p.contact?.email, p.appointment?.id];\nif (required.some((value) => !value)) {\n throw new Error('Missing event_id, contact.email, or appointment.id');\n}\n\nconst status = String(p.appointment.status || '').toLowerCase();\nconst blocked = p.contact.transactional_contact_allowed === false || status !== 'missed';\nconst greeting = p.contact.name ? `Hi ${p.contact.name},` : 'Hi there,';\nconst message = `${greeting}\\n\\nWe missed you at your appointment with ${$json.business_name}. You can choose a new time here: ${$json.booking_url}\\n\\nReply to this email if you need help rescheduling.`;\n\nreturn [{\n json: {\n event_id: p.event_id,\n appointment_id: p.appointment.id,\n contact_email: p.contact.email,\n blocked,\n email_subject: $json.email_subject,\n message\n }\n}];"
},
"typeVersion": 2
},
{
"id": "allowed",
"name": "Is recovery allowed?",
"type": "n8n-nodes-base.if",
"position": [
760,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "allowed-condition",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.blocked }}",
"rightValue": false
}
]
}
},
"typeVersion": 2.2
},
{
"id": "send",
"name": "Send rescheduling email",
"type": "n8n-nodes-base.gmail",
"onError": "continueErrorOutput",
"position": [
1000,
-100
],
"parameters": {
"sendTo": "={{ $json.contact_email }}",
"message": "={{ $json.message }}",
"options": {},
"subject": "={{ $json.email_subject }}",
"emailType": "text"
},
"typeVersion": 2.1
},
{
"id": "response",
"name": "Return webhook acknowledgement",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1240,
0
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ { accepted: true, eventId: $json.event_id, blocked: $json.blocked || false } }}"
},
"typeVersion": 1.4
},
{
"id": "safeguards-note",
"name": "Production safeguards",
"type": "n8n-nodes-base.stickyNote",
"position": [
760,
260
],
"parameters": {
"color": 7,
"width": 420,
"height": 300,
"content": "## Production safeguards\n\n- Re-check appointment status before any delayed send.\n- Use a Data Store or provider idempotency key to suppress duplicate event IDs.\n- Keep the message transactional; do not add unrelated promotions.\n- Connect credentials owned by the end customer.\n- Review failed executions and provide a human support route."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Is recovery allowed?": {
"main": [
[
{
"node": "Send rescheduling email",
"type": "main",
"index": 0
}
],
[
{
"node": "Return webhook acknowledgement",
"type": "main",
"index": 0
}
]
]
},
"Send rescheduling email": {
"main": [
[
{
"node": "Return webhook acknowledgement",
"type": "main",
"index": 0
}
],
[
{
"node": "Return webhook acknowledgement",
"type": "main",
"index": 0
}
]
]
},
"Configure recovery message": {
"main": [
[
{
"node": "Validate event and prepare email",
"type": "main",
"index": 0
}
]
]
},
"Receive missed appointment": {
"main": [
[
{
"node": "Configure recovery message",
"type": "main",
"index": 0
}
]
]
},
"Validate event and prepare email": {
"main": [
[
{
"node": "Is recovery allowed?",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow receives missed-appointment events via a webhook, validates and filters them, then sends a single rescheduling email through Gmail and returns a JSON acknowledgement to the caller. Receives a POST webhook request containing appointment and contact details.…
Source: https://n8n.io/workflows/16155/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Automate WhatsApp communication for recruitment agencies with an interactive, structured customer experience. This workflow handles pricing inquiries, request submissions, tracking, complaints, and hu
Code. Uses googleSheets, gmail, supabase, stickyNote. Webhook trigger; 51 nodes.
This template turns Podium's conversation inbox into a full sales CRM with a custom funnel, AI message classification, automated drip follow-ups, daily admin reports, and a live Kanban dashboard. Six
Suspicious_login_detection. Uses postgres, httpRequest, noOp, html. Webhook trigger; 43 nodes.
This n8n workflow is designed for security monitoring and incident response when suspicious login events are detected. It can be initiated either manually from within the n8n UI for testing or automat