DEV Community

Alex Kane
Alex Kane

Posted on

Stop losing clients to no-shows: automate appointment reminders with n8n (free workflow JSON)

No-shows are expensive.

A missed appointment is lost revenue, a wasted time slot, and sometimes a client you never get back. For service businesses — consultants, coaches, tutors, salons, clinics — no-shows can cost 10-20% of weekly revenue.

The fix is embarrassingly simple: send a reminder the day before and one hour before. Most no-shows are forgotten appointments, not intentional cancellations.

Here's a 5-node n8n workflow that reads your Google Calendar every hour and sends automatic email reminders — 24 hours out and 1 hour out — to every attendee. Set it up once, forget about it.


What the workflow does

Node 1 — Hourly schedule trigger

Runs every hour (you can change this to every 30 minutes for higher precision). This is the heartbeat of the whole workflow.

Node 2 — Google Calendar: fetch upcoming events

Pulls all calendar events starting from right now through the next 25 hours. The 25-hour window ensures we catch both the "24 hours out" and "1 hour out" reminders in a single pass.

Node 3 — Filter for reminder windows

A Code node checks each event's start time against the current time:

  • If the event starts in 23.5 to 24.5 hours → flag as 24hour reminder
  • If the event starts in 0.9 to 1.1 hours → flag as 1hour reminder
  • All other events → return type: 'none'

The 0.1-hour tolerance on each side accounts for the cron not firing at the exact second.

Node 4 — IF: has reminders?

Filters out the type: 'none' events so we only continue if there's actually a reminder to send.

Node 5 — Gmail: send reminder

Sends a personalized email to all attendees with the event name, time, location, and Google Meet link (if present). Subject line automatically says "in 1 hour" or "tomorrow" based on which window triggered.


Setup (5 minutes)

  1. Import the JSON into n8n (New Workflow, Import from clipboard)
  2. Connect your Google account in both the Calendar and Gmail nodes (one OAuth connection covers both)
  3. Test — manually trigger Node 1 to verify it reads your calendar
  4. Activate the workflow — it runs automatically every hour from now on

That's it. No extra tools, no third-party services, no monthly fees.


Full workflow JSON

{
  "name": "Appointment Reminder",
  "nodes": [
    {"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 * * * *"}]}},"id":"ar1","name":"Check Every Hour","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[240,300]},
    {"parameters":{"operation":"getAll","calendar":{"__rl":true,"value":"primary","mode":"name"},"returnAll":false,"limit":20,"options":{"timeMin":"={{ $now.toISO() }}","timeMax":"={{ $now.plus({hours: 25}).toISO() }}"}},"id":"ar2","name":"Get Upcoming Events","type":"n8n-nodes-base.googleCalendar","typeVersion":3,"position":[460,300]},
    {"parameters":{"jsCode":"const events = $input.all();\nconst now = new Date();\nconst reminders = [];\nfor (const event of events) {\n  const e = event.json;\n  const start = new Date(e.start?.dateTime || e.start?.date);\n  const hoursUntil = (start - now) / (1000 * 60 * 60);\n  if (hoursUntil > 0.9 && hoursUntil < 1.1) {\n    reminders.push({ json: { type: '1hour', summary: e.summary, start: start.toISOString(), attendees: (e.attendees || []).map(a => a.email).join(', '), location: e.location || 'No location', meetLink: e.hangoutLink || '' } });\n  }\n  if (hoursUntil > 23.5 && hoursUntil < 24.5) {\n    reminders.push({ json: { type: '24hour', summary: e.summary, start: start.toISOString(), attendees: (e.attendees || []).map(a => a.email).join(', '), location: e.location || 'No location', meetLink: e.hangoutLink || '' } });\n  }\n}\nreturn reminders.length > 0 ? reminders : [{ json: { type: 'none' } }];"},"id":"ar3","name":"Filter Reminder Times","type":"n8n-nodes-base.code","typeVersion":2,"position":[680,300]},
    {"parameters":{"conditions":{"conditions":[{"leftValue":"={{ $json.type }}","rightValue":"none","operator":{"type":"string","operation":"notEquals"}}]}},"id":"ar4","name":"Has Reminders?","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[900,300]},
    {"parameters":{"sendTo":"={{ $json.attendees }}","subject":"=Reminder: {{ $json.summary }} — {{ $json.type === '1hour' ? 'in 1 hour' : 'tomorrow' }}","message":"=Hi,\n\nThis is a friendly reminder about your upcoming appointment:\n\nEvent: {{ $json.summary }}\nWhen: {{ $json.start }}\nLocation: {{ $json.location }}\n{{ $json.meetLink ? 'Join link: ' + $json.meetLink : '' }}\n\nSee you there!"},"id":"ar5","name":"Send Reminder Email","type":"n8n-nodes-base.gmail","typeVersion":2.1,"position":[1140,300]}
  ],
  "connections": {
    "Check Every Hour":{"main":[[{"node":"Get Upcoming Events","type":"main","index":0}]]},
    "Get Upcoming Events":{"main":[[{"node":"Filter Reminder Times","type":"main","index":0}]]},
    "Filter Reminder Times":{"main":[[{"node":"Has Reminders?","type":"main","index":0}]]},
    "Has Reminders?":{"main":[[{"node":"Send Reminder Email","type":"main","index":0}],[]]}
  },
  "settings":{"executionOrder":"v1"},
  "tags":[{"name":"scheduling"}]
}
Enter fullscreen mode Exit fullscreen mode

Customizations

Add SMS via Twilio
Replace or supplement the Gmail node with an HTTP Request to the Twilio API. SMS reminders have a higher open rate than email for last-minute reminders.

Add a confirmation link
Modify the email template to include a link like https://calendly.com/yourname/confirm?event=XYZ. Clients click to confirm — you know in advance who's coming.

Different reminder windows
Want 48h + 2h instead of 24h + 1h? Just change the hour ranges in the Code node (lines 9-15). No other changes needed.

Use a Sheets-based appointment list instead of Google Calendar
Replace Node 2 with a Google Sheets node that reads your bookings spreadsheet. Filter by date in Node 3. Useful if you book outside of Google Calendar.

Slack notification when a reminder fires
Add a Slack node in parallel with the Gmail node. You see in real-time who got reminded — useful for high-value clients.

Skip reminders for all-day events
In the Code node, add a check: if (!e.start?.dateTime) continue; before the start and hoursUntil calculation. All-day events use e.start.date (no time) and shouldn't trigger reminders.


Real-world impact

A freelance consultant running 8-10 client calls per week, with one no-show per week at $150/call, saves $600/month by eliminating that one no-show. The workflow takes 5 minutes to set up.

For a salon with 40 appointments per week at 5% no-show rate = 2 missed slots = $100-200 lost per week. Automated reminders typically cut no-shows by 50-80% for appointment-based businesses.


Get the full automation bundle

This workflow is part of our 15-template n8n automation bundle — each one covering a different business use case: lead capture, invoice generation, AI customer support, social media automation, price monitoring, and more.

Grab the full bundle at stripeai.gumroad.com — pre-tested, documented, ready to activate.


Built with n8n. Self-hostable, open source, no vendor lock-in.

Top comments (0)