DEV Community

Alex Kane
Alex Kane

Posted on

n8n for Restaurants & Hospitality: 5 Automations That Slash No-Shows and Boost Revenue (Free Workflow JSON)

Running a restaurant or hotel is a constant juggle of reservations, staff schedules, reviews, and end-of-day numbers. Most operators are still doing this manually — or paying $200+/month for specialized software that only half-works.

Here are 5 n8n automations that handle the busywork for free, with full workflow JSON you can import and run today.


1. Reservation Reminder System (Cut No-Shows by 60%)

No-shows kill restaurants. A well-timed reminder sequence cuts them 60-70%.

What it does:

  • Checks your reservations spreadsheet every hour
  • Sends a personalized email 24h before and 2h before each reservation
  • Includes party size, time, and a "reply to cancel" prompt

Workflow (5 nodes): Schedule Trigger → Google Sheets (read reservations) → Code (filter 24h/2h windows) → IF (any pending?) → Gmail (send reminder)

{
  "name": "Reservation Reminder System",
  "nodes": [
    { "id": "1", "name": "Every Hour", "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "hours", "hoursInterval": 1 }] } },
      "position": [240, 300] },
    { "id": "2", "name": "Get Reservations", "type": "n8n-nodes-base.googleSheets",
      "parameters": { "operation": "readRows", "documentId": "YOUR_SHEET_ID", "sheetName": "Reservations" },
      "position": [460, 300] },
    { "id": "3", "name": "Filter 24h & 2h Windows", "type": "n8n-nodes-base.code",
      "parameters": { "jsCode": "const now = Date.now();\nconst h24 = now + 24*3600*1000;\nconst h2 = now + 2*3600*1000;\nconst margin = 5*60*1000;\nreturn items.filter(item => {\n  const t = new Date(item.json.reservation_time).getTime();\n  return (Math.abs(t-h24)<margin || Math.abs(t-h2)<margin) && item.json.reminded !== 'yes';\n});" },
      "position": [680, 300] },
    { "id": "4", "name": "Any to Remind?", "type": "n8n-nodes-base.if",
      "parameters": { "conditions": { "number": [{ "value1": "={{ $items().length }}", "operation": "larger", "value2": 0 }] } },
      "position": [900, 300] },
    { "id": "5", "name": "Send Reminder Email", "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{ $json.email }}",
        "subject": "Reminder: Your reservation tonight at {{ $json.reservation_time }}",
        "message": "Hi {{ $json.name }},\n\nJust a reminder — you have a reservation for {{ $json.party_size }} guests at {{ $json.reservation_time }}.\n\nIf plans change, simply reply to cancel — we really appreciate the heads-up.\n\nSee you soon!"
      },
      "position": [1120, 240] }
  ],
  "connections": {
    "Every Hour": { "main": [[{ "node": "Get Reservations", "type": "main", "index": 0 }]] },
    "Get Reservations": { "main": [[{ "node": "Filter 24h & 2h Windows", "type": "main", "index": 0 }]] },
    "Filter 24h & 2h Windows": { "main": [[{ "node": "Any to Remind?", "type": "main", "index": 0 }]] },
    "Any to Remind?": { "main": [[{ "node": "Send Reminder Email", "type": "main", "index": 0 }], []] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup (5 min): Import JSON → create Google Sheet with columns name, email, reservation_time, party_size, reminded → connect Gmail credentials → activate.

Pro tip: Add a Twilio SMS node in parallel with Gmail. SMS reminders have 95% open rates vs 30% for email.


2. Daily Revenue & Covers Dashboard (Delivered to Your Inbox Every Night)

Stop manually compiling end-of-day numbers. This workflow runs at 10 PM and sends your key metrics directly to you and your managers.

What it does:

  • Pulls today's orders from your tracking spreadsheet (or POS API)
  • Calculates covers, total revenue, average spend per cover, and top-selling items
  • Sends an HTML report to your management team

Workflow (4 nodes): Schedule Trigger (10 PM) → Google Sheets → Code (aggregate KPIs) → Gmail (HTML email)

{
  "name": "Daily Restaurant Revenue Report",
  "nodes": [
    { "id": "1", "name": "10PM Every Night", "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 22 * * *" }] } },
      "position": [240, 300] },
    { "id": "2", "name": "Get Today Orders", "type": "n8n-nodes-base.googleSheets",
      "parameters": { "operation": "readRows", "documentId": "YOUR_SHEET_ID", "sheetName": "Orders" },
      "position": [460, 300] },
    { "id": "3", "name": "Calculate KPIs", "type": "n8n-nodes-base.code",
      "parameters": { "jsCode": "const today = new Date().toISOString().split('T')[0];\nconst todayOrders = items.filter(i => i.json.date === today);\nconst revenue = todayOrders.reduce((s,i) => s + Number(i.json.amount), 0);\nconst covers = todayOrders.reduce((s,i) => s + Number(i.json.covers||1), 0);\nconst avgSpend = covers > 0 ? (revenue/covers).toFixed(2) : 0;\nconst itemCounts = {};\ntodayOrders.forEach(i => { itemCounts[i.json.item] = (itemCounts[i.json.item]||0)+1; });\nconst topItems = Object.entries(itemCounts).sort((a,b)=>b[1]-a[1]).slice(0,3).map(e=>e[0]).join(', ');\nreturn [{ json: { revenue: revenue.toFixed(2), covers, avgSpend, topItems, date: today } }];" },
      "position": [680, 300] },
    { "id": "4", "name": "Email Report", "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "manager@yourrestaurant.com",
        "subject": "Daily Report — {{ $json.date }}",
        "message": "<h2>End of Day Summary</h2><table><tr><td><b>Date</b></td><td>{{ $json.date }}</td></tr><tr><td><b>Revenue</b></td><td>${{ $json.revenue }}</td></tr><tr><td><b>Covers</b></td><td>{{ $json.covers }}</td></tr><tr><td><b>Avg Spend/Cover</b></td><td>${{ $json.avgSpend }}</td></tr><tr><td><b>Top Items</b></td><td>{{ $json.topItems }}</td></tr></table>"
      },
      "position": [900, 300] }
  ],
  "connections": {
    "10PM Every Night": { "main": [[{ "node": "Get Today Orders", "type": "main", "index": 0 }]] },
    "Get Today Orders": { "main": [[{ "node": "Calculate KPIs", "type": "main", "index": 0 }]] },
    "Calculate KPIs": { "main": [[{ "node": "Email Report", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Pro tip: Connect to Square, Toast, or Lightspeed APIs via HTTP Request instead of Google Sheets for real-time POS data.


3. Google Reviews Monitor & Alert (Respond in Under 2 Hours)

Responding to negative reviews within 2 hours is one of the highest-ROI things a restaurant can do. This automation makes it impossible to miss a bad review.

What it does:

  • Checks your Google Places listing daily via the Places API
  • Sends an instant Slack alert for any review 3 stars or under
  • Sends a Slack notification for 4-5 star reviews so you can say thank you

Workflow (5 nodes): Schedule Trigger → HTTP Request (Google Places API) → Code (filter new reviews, classify by rating) → Switch → Slack alert

{
  "name": "Google Reviews Monitor",
  "nodes": [
    { "id": "1", "name": "Every Morning 9AM", "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * *" }] } },
      "position": [240, 300] },
    { "id": "2", "name": "Fetch Reviews", "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "url": "https://maps.googleapis.com/maps/api/place/details/json",
        "method": "GET",
        "queryParameters": { "parameters": [
          { "name": "place_id", "value": "YOUR_PLACE_ID" },
          { "name": "fields", "value": "reviews" },
          { "name": "key", "value": "YOUR_GOOGLE_API_KEY" }
        ]}
      },
      "position": [460, 300] },
    { "id": "3", "name": "Filter New Reviews", "type": "n8n-nodes-base.code",
      "parameters": { "jsCode": "const reviews = $json.result?.reviews || [];\nconst yesterday = Date.now() - 86400000;\nreturn reviews\n  .filter(r => (r.time * 1000) > yesterday)\n  .map(r => ({ json: { rating: r.rating, text: r.text, author: r.author_name, type: r.rating <= 3 ? 'negative' : 'positive' } }));" },
      "position": [680, 300] },
    { "id": "4", "name": "Route by Rating", "type": "n8n-nodes-base.switch",
      "parameters": { "rules": { "rules": [
        { "conditions": { "string": [{ "value1": "={{ $json.type }}", "operation": "equal", "value2": "negative" }] }, "outputKey": "0" },
        { "conditions": { "string": [{ "value1": "={{ $json.type }}", "operation": "equal", "value2": "positive" }] }, "outputKey": "1" }
      ]}},
      "position": [900, 300] },
    { "id": "5", "name": "Alert — Low Rating", "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#reviews",
        "text": "New {{ $json.rating }}-star review from {{ $json.author }}:\n\n\"{{ $json.text }}\"\n\nRespond on Google My Business immediately."
      },
      "position": [1120, 200] }
  ],
  "connections": {
    "Every Morning 9AM": { "main": [[{ "node": "Fetch Reviews", "type": "main", "index": 0 }]] },
    "Fetch Reviews": { "main": [[{ "node": "Filter New Reviews", "type": "main", "index": 0 }]] },
    "Filter New Reviews": { "main": [[{ "node": "Route by Rating", "type": "main", "index": 0 }]] },
    "Route by Rating": { "main": [[{ "node": "Alert — Low Rating", "type": "main", "index": 0 }], []] }
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Staff Schedule Change Notifier

When someone calls in sick or requests a swap, the scramble to notify everyone manually takes 20-30 minutes. This automation handles it in seconds.

What it does:

  • Watches your schedule spreadsheet for any cell changes
  • Immediately emails the affected staff member with shift details
  • Posts a Slack alert to your team's #schedule channel

Workflow (4 nodes): Google Sheets Trigger → Code (extract shift details) → Gmail (notify staff) + Slack (alert team)

{
  "name": "Staff Schedule Change Notifier",
  "nodes": [
    { "id": "1", "name": "Schedule Sheet Changed", "type": "n8n-nodes-base.googleSheetsTrigger",
      "parameters": { "documentId": "YOUR_SCHEDULE_SHEET_ID", "sheetName": "Shifts", "triggerOn": "anyChange" },
      "position": [240, 300] },
    { "id": "2", "name": "Extract Details", "type": "n8n-nodes-base.code",
      "parameters": { "jsCode": "const row = $json;\nreturn [{ json: { date: row.shift_date, time: row.shift_time, station: row.station, name: row.staff_name, email: row.staff_email, status: row.status, notes: row.notes||'' } }];" },
      "position": [460, 300] },
    { "id": "3", "name": "Email Staff", "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{ $json.email }}",
        "subject": "Shift Update — {{ $json.date }} {{ $json.time }}",
        "message": "Hi {{ $json.name }},\n\nYour {{ $json.date }} shift at {{ $json.time }} ({{ $json.station }}) has been updated.\nStatus: {{ $json.status }}\n{{ $json.notes ? 'Notes: ' + $json.notes : '' }}"
      },
      "position": [680, 200] },
    { "id": "4", "name": "Slack Team Alert", "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#schedule",
        "text": "Schedule update: {{ $json.name }}'s {{ $json.date }} shift ({{ $json.time }}, {{ $json.station }}) is now {{ $json.status }}."
      },
      "position": [680, 400] }
  ],
  "connections": {
    "Schedule Sheet Changed": { "main": [[{ "node": "Extract Details", "type": "main", "index": 0 }]] },
    "Extract Details": { "main": [[{ "node": "Email Staff", "type": "main", "index": 0 }, { "node": "Slack Team Alert", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Customer Win-Back Campaign ("We Miss You" Sequence)

Repeat customers spend 67% more than new ones. This automation identifies lapsed guests and sends a personalized offer every Monday morning.

What it does:

  • Scans your customer database weekly
  • Finds guests who haven't visited in 30+ days and haven't received a win-back email
  • Sends a personalized email with a discount code

Workflow (4 nodes): Schedule Trigger (Monday 9 AM) → Google Sheets → Code (filter 30+ days inactive) → Gmail (personalized win-back)

{
  "name": "Customer Win-Back Campaign",
  "nodes": [
    { "id": "1", "name": "Monday 9AM", "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * 1" }] } },
      "position": [240, 300] },
    { "id": "2", "name": "Get Customer List", "type": "n8n-nodes-base.googleSheets",
      "parameters": { "operation": "readRows", "documentId": "YOUR_SHEET_ID", "sheetName": "Customers" },
      "position": [460, 300] },
    { "id": "3", "name": "Find Lapsed Guests", "type": "n8n-nodes-base.code",
      "parameters": { "jsCode": "const threshold = Date.now() - 30*86400000;\nreturn items.filter(i => {\n  const lastVisit = new Date(i.json.last_visit).getTime();\n  return lastVisit < threshold && i.json.winback_sent !== 'yes';\n});" },
      "position": [680, 300] },
    { "id": "4", "name": "Win-Back Email", "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{ $json.email }}",
        "subject": "We miss you, {{ $json.first_name }} — here's 15% off",
        "message": "Hi {{ $json.first_name }},\n\nIt's been a while since we've seen you, and we wanted to reach out.\n\nUse code WELCOMEBACK for 15% off your next visit — valid for the next 30 days.\n\nHope to see you soon!"
      },
      "position": [900, 300] }
  ],
  "connections": {
    "Monday 9AM": { "main": [[{ "node": "Get Customer List", "type": "main", "index": 0 }]] },
    "Get Customer List": { "main": [[{ "node": "Find Lapsed Guests", "type": "main", "index": 0 }]] },
    "Find Lapsed Guests": { "main": [[{ "node": "Win-Back Email", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Pro tip: Track redemption by adding winback_sent: yes + winback_code columns to your sheet after each send. After a month you'll know your exact win-back ROI.


All 5 Workflows at a Glance

Workflow Nodes Setup Impact
Reservation Reminder 5 5 min 60% fewer no-shows
Daily Revenue Report 4 10 min 30 min/day saved
Google Review Monitor 5 15 min Respond in < 2 hours
Staff Schedule Alert 4 5 min 20 min per shift change saved
Customer Win-Back 4 10 min 5-15% repeat visit lift

Total setup time: ~45 minutes. Typical time saved: 4-6 hours per week.


Want Ready-to-Import Packages with Full Documentation?

If you'd rather skip the setup and use production-ready versions with error handling, retry logic, and step-by-step guides already included — check out FlowKit at stripeai.gumroad.com.

The Appointment Reminder ($15) and Daily Report Generator ($19) templates map directly to the restaurant use cases above. There's also a Complete Bundle with all 15 templates for $97 — less than a single month of most hospitality SaaS tools.


Which of these would save you the most time? Drop it in the comments — I read every one.

Top comments (0)