SAMPLE WORK, BEFORE AND AFTER

What "handled" actually looks like

Four services, one of them you can try right now. The rest are real examples of the same underlying promise: something messy or manual, taken off your plate and turned into something clean, monitored, and running on its own.

AI CHATBOT WIDGET

This one, you don't have to imagine

Every other example on this page is a screenshot. This one isn't: the chat bubble in the bottom-right corner of this exact page is a live, working instance, tuned to Briven AI Studio's own services, pricing, and FAQs. Ask it something.

Briven AI Studio
Running live on this website, right now

Click the bubble in the corner, or try one of these:

Captures leads who share contact info, with a clear heads-up first Guardrails built in: it never invents a price, policy, or promise
GOOGLE REVIEW RESPONSE SERVICE

Review responses that sound like the owner, not a template

Three real response scenarios, spanning a regulated healthcare practice, a restaurant, and a home services company. Each shows how tone shifts by industry while staying calm, specific, and non-defensive. All three run on the same automated monitoring: the moment a new review posts, we're alerted automatically, not whenever someone remembers to check.

Family Dental Office
Healthcare, Regulated Industry
BeforeUnanswered, 61 days
Sarah M. ★★☆☆☆
Waited 45 minutes past my appointment time and no one told me what was going on. Really frustrating when you take time off work for this.
No response from business
AfterResponded in 6 hrs
Sarah M. ★★☆☆☆
Waited 45 minutes past my appointment time and no one told me what was going on. Really frustrating when you take time off work for this.
Owner response, Family Dental Office
Hi Sarah, thank you for letting us know, and we're sorry your visit didn't start on time. We know your schedule matters, and we're working with our front desk team on communicating delays better going forward. We'd welcome the chance to discuss your visit directly. Please reach out to our front desk whenever works for you.
Bistro Restaurant
Restaurant, Brunch Spot
BeforeUnanswered, 12 days
Marcus T. ★★★★★
Best brunch spot in town. The lemon ricotta pancakes are unreal and our server Dana was so attentive. Will be back every weekend!
No response from business
AfterResponded in 4 hrs
Marcus T. ★★★★★
Best brunch spot in town. The lemon ricotta pancakes are unreal and our server Dana was so attentive. Will be back every weekend!
Owner response, Bistro Restaurant
Marcus, this made our morning! We'll be sure to pass this along to Dana, she'll be thrilled. The lemon ricotta pancakes are a favorite of ours too. See you next weekend, we'll have a table ready!
Local Plumbing Service
Home Services
BeforeUnanswered, 34 days
Janet R. ★★☆☆☆
Technician was polite but showed up 2 hours after the scheduled window with no call. Work itself was fine once he got here.
No response from business
AfterResponded in 9 hrs
Janet R. ★★☆☆☆
Technician was polite but showed up 2 hours after the scheduled window with no call. Work itself was fine once he got here.
Owner response, Local Plumbing Service
Janet, thank you for the honest feedback, and we're sorry about the wait, especially without a heads-up call. That's on us. We've flagged this with dispatch so customers get a call the moment a tech is running behind. Glad the repair itself worked out. Reach out anytime if we can help again.
EXCEL / SHEETS DATA CLEANING & DASHBOARDS

From messy export to an auto-updating dashboard

Three live-formula workbooks, from a single sales table to a dashboard cross-referencing four linked tables at once. Every KPI, summary, and chart recalculates the moment new rows are pasted in. Click "Raw Data" on any of them to see the real rows behind the numbers.

Retail Company: Sales Performance Dashboard
E-commerce, Outdoor Apparel
Retail_Company_Sales_Dashboard.xlsx
Total Revenue
$90,565
Total Orders
420
Avg Order Value
$215.63
Return Rate
3.6%

Revenue by Region

$17.1k
Northeast
$18.5k
Southeast
$18.1k
Midwest
$21.0k
West
$16.0k
Southwest

Monthly Trend ($k)

15.4
Feb
12.6
Mar
16.7
Apr
16.2
May
15.6
Jun
14.0
Jul
Pivot tables and charts point at the Raw Data table, not fixed cells, so pasting in new orders and recalculating updates every number above.
Order IDDateRegionChannelCategoryUnitsRevenueReturned?
CO-10001Jul 14, 2026NortheastMarketplaceFootwear2$197.74No
CO-10002Jun 20, 2026NortheastWebsiteOuterwear1$134.35No
CO-10003Feb 7, 2026MidwestMarketplaceBase Layers4$203.97No
CO-10004Feb 2, 2026WestWebsiteAccessories3$99.46No
CO-10005Apr 28, 2026NortheastWebsiteCamp Gear3$270.63No
CO-10006May 29, 2026MidwestRetail PartnerAccessories1$36.51No
CO-10007Jul 9, 2026SouthwestWebsiteFootwear1$95.26No
CO-10008Apr 16, 2026SouthwestMarketplaceOuterwear4$549.43No
CO-10009May 5, 2026NortheastWebsiteCamp Gear1$90.37No
CO-10010Apr 4, 2026NortheastWebsiteBase Layers2$119.86No
CO-10011Feb 15, 2026SoutheastWebsiteCamp Gear4$320.86No
CO-10012Jun 26, 2026SouthwestWebsiteAccessories4$162.35No

Showing 12 of 420 rows from the actual workbook.

Property Management Company: Portfolio Operations Dashboard
Property Management, Multi-site
Property_Management_Dashboard.xlsx
Occupancy Rate
89.7%
Open Work Orders
62
Avg Days to Resolve
3.4
Rent Collected
89.6%

Avg Days to Resolve by Category

2.5
Plumbing
2.0
Electrical
3.5
HVAC
4.2
Appliance
5.5
General

Ticket Status Mix

Closed: 198
Open: 45
Scheduled: 17
Occupancy pulls from the Units tab, ticket metrics from Work Orders, both structured as tables so the dashboard scales as the portfolio grows.
Ticket IDPropertyUnitCategorySubmittedStatusDays to ResolveRating
WO-2026-1001Cedar Ridge2DApplianceMay 10, 2026Open——
WO-2026-1002Magnolia Court3EPlumbingJul 4, 2026Closed45
WO-2026-1003The Wynwood1EPlumbingMay 29, 2026Closed34
WO-2026-1004Magnolia Court4AGeneral/CosmeticMay 6, 2026Closed64
WO-2026-1005Cedar Ridge2AHVACJul 21, 2026Closed55
WO-2026-1006Magnolia Court1EPlumbingJul 27, 2026Closed32
WO-2026-1007Cedar Ridge3BApplianceJul 29, 2026Closed35
WO-2026-1008Cedar Ridge4CApplianceJun 6, 2026Closed55
WO-2026-1009Cedar Ridge2DHVACJul 25, 2026Closed44
WO-2026-1010Cedar Ridge3FElectricalJul 3, 2026Closed12
WO-2026-1011The Wynwood1AApplianceJun 9, 2026Closed51
WO-2026-1012The Wynwood3FElectricalJul 25, 2026Closed34

Showing 12 of 260 rows from the actual workbook.

Flagship example
Multi-Location Restaurant Group: Performance Dashboard
Restaurant Group, 5 Locations
MultiLocation_Restaurant_Dashboard.xlsx
Locations Daily Sales Labor Hours Waste Log 4 tables, cross-referenced by Location ID
Total Revenue
$2.58M
Total Covers
79,242
Avg Check
$32.57
Labor % of Sales
29.4%
Total Waste Cost
$17.0k

Location Performance

LocationRevenueLabor %
Uptown$777,99326.6%
Downtown$639,63828.3%
Riverside$457,36629.2%
Harbor Point$322,96731.2%
Westgate$382,86835.8%

Revenue Trend, Month over Month

$839k
Apr
$895k
May
$847k
Jun
▼ 5.3% May → Jun
Westgate's labor cost is flagged automatically once it crosses 35% of sales, so it surfaces on its own instead of getting lost in five locations' worth of numbers.
DateLocation IDNet SalesCoversComps
Apr 1, 2026L1$6,381.13213$217.80
Apr 1, 2026L2$4,956.32152$131.77
Apr 1, 2026L3$6,639.49220$114.29
Apr 1, 2026L4$3,849.38128$131.95
Apr 1, 2026L5$2,620.5288$32.03
Apr 2, 2026L1$6,802.40215$148.24
Apr 2, 2026L2$4,030.69117$81.01
Apr 2, 2026L3$6,622.83205$116.61
Apr 2, 2026L4$3,095.6085$83.59
Apr 2, 2026L5$3,283.9189$65.24
Apr 3, 2026L1$8,006.24248$161.04
Apr 3, 2026L2$6,158.04219$133.03

Showing 12 of the Daily Sales tab. Location IDs join out to Locations, Labor Hours, and Waste Log in the other 3 tabs.

N8N / MAKE.COM BUSINESS PROCESS AUTOMATION

Workflows that keep running after delivery

Three real, importable workflow builds, from a straightforward lead-routing pipeline up to a multi-week booking chain with nested payment logic. All three include tested error handling and a human-review step before anything customer-facing goes out. Open "View raw workflow JSON" to see the actual exported file.

Local Plumbing Service: Lead Capture, CRM & Routing
Home Services
n8n_lead_capture_and_routing.json
Trigger, Webhook
New website form submission
Action
Add row to Lead Tracker (Sheets)
Router
IF job value ≥ $1,000
YES
Action
Slack alert: owner (urgent)
NO
Action
Slack alert: standard queue
Action
Auto-reply SMS to customer
{
  "name": "Local Plumbing Service: Lead Capture, CRM & Routing",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "new-lead-form",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "1a2b3c4d-0001-4a1a-9b1a-000000000001",
      "name": "New Website Form Submission",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [-40, 260],
      "notes": "Fires the instant a visitor submits the quote-request form on example.com. Webhook URL is registered with the site's form tool."
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "1Xk9pQ_STERLING_LEAD_LOG_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Leads",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date": "={{ $now.toFormat('yyyy-MM-dd HH:mm') }}",
            "Name": "={{ $json.body.name }}",
            "Phone": "={{ $json.body.phone }}",
            "Service Needed": "={{ $json.body.service_type }}",
            "Job Value Estimate": "={{ $json.body.estimated_value }}",
            "Source": "={{ $json.body.utm_source || 'Direct' }}",
            "Status": "New"
          }
        }
      },
      "id": "1a2b3c4d-0002-4a1a-9b1a-000000000002",
      "name": "Add Row to Lead Tracker (Sheets)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [200, 260],
      "notes": "System of record for every inbound lead, regardless of value."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "cond-1",
              "leftValue": "={{ Number($json.body.estimated_value) }}",
              "rightValue": 1000,
              "operator": { "type": "number", "operation": "gte" }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "1a2b3c4d-0003-4a1a-9b1a-000000000003",
      "name": "IF Job Value >= $1,000",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [440, 260],
      "notes": "High-value jobs (re-pipes, water heater replacements, emergency calls) get routed to the owner directly instead of the standard queue."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#high-value-leads", "mode": "name" },
        "text": "=🔥 High-value lead: *{{ $json.body.name }}*: {{ $json.body.service_type }}, est. ${{ $json.body.estimated_value }}. Phone: {{ $json.body.phone }}. Call within 15 min for best close rate.",
        "otherOptions": {}
      },
      "id": "1a2b3c4d-0004-4a1a-9b1a-000000000004",
      "name": "Slack Alert: Owner (Urgent)",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [700, 140],
      "notes": "Posts to a dedicated Slack channel the owner has muted-except-urgent, so this doesn't get lost in general chatter."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#new-leads", "mode": "name" },
        "text": "=New lead: {{ $json.body.name }}: {{ $json.body.service_type }}. Added to the tracker, standard follow-up queue.",
        "otherOptions": {}
      },
      "id": "1a2b3c4d-0005-4a1a-9b1a-000000000005",
      "name": "Slack Alert: Standard Queue",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [700, 380],
      "notes": "Routine leads still get a lightweight notification, just to the general channel rather than paging the owner."
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "toList": [{ "phone": "={{ $json.body.phone }}" }],
        "message": "Hi {{ $json.body.name }}, thanks for reaching out to Local Plumbing Service! We received your request and will call you back shortly."
      },
      "id": "1a2b3c4d-0006-4a1a-9b1a-000000000006",
      "name": "Auto-Reply SMS to Customer",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [940, 260],
      "notes": "Immediate acknowledgment so the customer isn't left wondering whether the form actually submitted."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "err-1",
              "leftValue": "={{ $json.body.phone }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "empty" }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "1a2b3c4d-0007-4a1a-9b1a-000000000007",
      "name": "Missing Required Field?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [200, 460],
      "notes": "Error-handling branch: catches malformed submissions (e.g. phone field blank) before they silently fail downstream."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#automation-alerts", "mode": "name" },
        "text": "=⚠️ Lead form submitted with a missing phone number. Raw payload: {{ JSON.stringify($json.body) }}. Please follow up manually.",
        "otherOptions": {}
      },
      "id": "1a2b3c4d-0008-4a1a-9b1a-000000000008",
      "name": "Notify: Malformed Submission",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [440, 460],
      "notes": "Fallback path so a bad submission is surfaced to a human instead of vanishing."
    }
  ],
  "connections": {
    "New Website Form Submission": {
      "main": [
        [
          { "node": "Add Row to Lead Tracker (Sheets)", "type": "main", "index": 0 },
          { "node": "Missing Required Field?", "type": "main", "index": 0 }
        ]
      ]
    },
    "Add Row to Lead Tracker (Sheets)": {
      "main": [[{ "node": "IF Job Value >= $1,000", "type": "main", "index": 0 }]]
    },
    "IF Job Value >= $1,000": {
      "main": [
        [{ "node": "Slack Alert: Owner (Urgent)", "type": "main", "index": 0 }],
        [{ "node": "Slack Alert: Standard Queue", "type": "main", "index": 0 }]
      ]
    },
    "Slack Alert: Owner (Urgent)": {
      "main": [[{ "node": "Auto-Reply SMS to Customer", "type": "main", "index": 0 }]]
    },
    "Slack Alert: Standard Queue": {
      "main": [[{ "node": "Auto-Reply SMS to Customer", "type": "main", "index": 0 }]]
    },
    "Missing Required Field?": {
      "main": [
        [{ "node": "Notify: Malformed Submission", "type": "main", "index": 0 }],
        []
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "portfolioSample": true,
    "instructions": "Sample workflow for portfolio purposes. Replace the Google Sheet ID, Slack channel IDs, and Twilio credentials with the client's own before deploying live."
  }
}
Trigger fires in real time, no polling delay Missing-field error path notifies the team directly (not shown above)
Retail Company: AI Support Ticket Triage
E-commerce, Outdoor Apparel
n8n_ai_support_triage.json
Trigger, Polling (1 min)
New support inbox email
AI Step, LLM
Classify topic, urgency & draft reply
Router
IF high urgency or complaint
YES
Action
Slack: urgent review queue
NO
Action
Log to standard queue (Sheets)
{
  "name": "Retail Company: AI Support Ticket Triage",
  "nodes": [
    {
      "parameters": {
        "pollTimes": { "item": [{ "mode": "everyMinute" }] },
        "mailbox": "support@example.com",
        "postProcessAction": "read",
        "options": {}
      },
      "id": "2b3c4d5e-0001-4b2b-9c2b-000000000001",
      "name": "New Support Email",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2,
      "position": [-40, 260],
      "notes": "Polls the shared support inbox every minute. Most email providers don't offer a true webhook trigger, so polling is the realistic option here."
    },
    {
      "parameters": {
        "modelId": { "__rl": true, "value": "claude-sonnet-4-6", "mode": "list" },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a support-ticket classifier for an outdoor apparel retailer. Read the email and return strict JSON only: {\"category\": one of [\"Order Status\",\"Return/Exchange\",\"Product Question\",\"Complaint\",\"Other\"], \"urgency\": one of [\"Low\",\"Normal\",\"High\"], \"draft_reply\": a short, warm first-pass reply under 80 words}. Do not invent order details you were not given."
            },
            {
              "role": "user",
              "content": "=Subject: {{ $json.subject }}\n\nFrom: {{ $json.from }}\n\nBody:\n{{ $json.textPlain }}"
            }
          ]
        },
        "options": { "temperature": 0.2 }
      },
      "id": "2b3c4d5e-0002-4b2b-9c2b-000000000002",
      "name": "Classify & Draft Reply (Claude)",
      "type": "n8n-nodes-base.anthropic",
      "typeVersion": 1,
      "position": [200, 260],
      "notes": "The AI-enhanced step: classifies topic + urgency and drafts a first-pass reply for a human to review, rather than auto-sending anything."
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.content || $input.first().json.text;\nlet parsed;\ntry {\n  parsed = JSON.parse(raw);\n} catch (e) {\n  parsed = { category: 'Other', urgency: 'Normal', draft_reply: '' };\n}\nreturn [{ json: { ...$input.first().json, ...parsed } }];"
      },
      "id": "2b3c4d5e-0003-4b2b-9c2b-000000000003",
      "name": "Parse Classification JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [440, 260],
      "notes": "Safely parses the model's JSON output; falls back to a safe default instead of breaking the workflow if the model ever returns malformed JSON."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "urgent-1",
              "leftValue": "={{ $json.urgency }}",
              "rightValue": "High",
              "operator": { "type": "string", "operation": "equals" }
            },
            {
              "id": "complaint-1",
              "leftValue": "={{ $json.category }}",
              "rightValue": "Complaint",
              "operator": { "type": "string", "operation": "equals" }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "2b3c4d5e-0004-4b2b-9c2b-000000000004",
      "name": "IF High Urgency or Complaint",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [680, 260],
      "notes": "Complaints and high-urgency tickets never get auto-routed to the standard queue: they always page a human directly."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#support-urgent", "mode": "name" },
        "text": "=🚨 {{ $json.category }} ({{ $json.urgency }}) from {{ $json.from }}\nSubject: {{ $json.subject }}\n\nDraft reply for review:\n{{ $json.draft_reply }}",
        "otherOptions": {}
      },
      "id": "2b3c4d5e-0005-4b2b-9c2b-000000000005",
      "name": "Slack: Urgent Review Queue",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [940, 140],
      "notes": "A human reviews and edits the AI draft before anything is sent: this workflow never auto-sends a reply to a customer."
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": { "__rl": true, "value": "1Yk8pR_COASTAL_TICKET_LOG_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Ticket Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date": "={{ $now.toFormat('yyyy-MM-dd HH:mm') }}",
            "From": "={{ $json.from }}",
            "Subject": "={{ $json.subject }}",
            "Category": "={{ $json.category }}",
            "Urgency": "={{ $json.urgency }}",
            "Draft Reply": "={{ $json.draft_reply }}",
            "Status": "Pending Review"
          }
        }
      },
      "id": "2b3c4d5e-0006-4b2b-9c2b-000000000006",
      "name": "Log to Standard Queue (Sheets)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [940, 380],
      "notes": "Normal-priority tickets skip the Slack page and go straight into a reviewable queue the support rep checks a few times a day."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "empty-body",
              "leftValue": "={{ $json.textPlain }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "empty" }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "2b3c4d5e-0007-4b2b-9c2b-000000000007",
      "name": "Empty/Unparseable Email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [-40, 460],
      "notes": "Error-handling branch for emails with no readable body (e.g. image-only or malformed encoding)."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#automation-alerts", "mode": "name" },
        "text": "=⚠️ Received a support email that couldn't be auto-classified (empty body). From: {{ $json.from }}, Subject: {{ $json.subject }}. Please check manually.",
        "otherOptions": {}
      },
      "id": "2b3c4d5e-0008-4b2b-9c2b-000000000008",
      "name": "Notify: Manual Review Needed",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [200, 460],
      "notes": "Fallback so an unparseable email is surfaced to a human rather than silently dropped."
    }
  ],
  "connections": {
    "New Support Email": {
      "main": [
        [
          { "node": "Classify & Draft Reply (Claude)", "type": "main", "index": 0 },
          { "node": "Empty/Unparseable Email?", "type": "main", "index": 0 }
        ]
      ]
    },
    "Classify & Draft Reply (Claude)": {
      "main": [[{ "node": "Parse Classification JSON", "type": "main", "index": 0 }]]
    },
    "Parse Classification JSON": {
      "main": [[{ "node": "IF High Urgency or Complaint", "type": "main", "index": 0 }]]
    },
    "IF High Urgency or Complaint": {
      "main": [
        [{ "node": "Slack: Urgent Review Queue", "type": "main", "index": 0 }],
        [{ "node": "Log to Standard Queue (Sheets)", "type": "main", "index": 0 }]
      ]
    },
    "Empty/Unparseable Email?": {
      "main": [
        [{ "node": "Notify: Manual Review Needed", "type": "main", "index": 0 }],
        []
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "portfolioSample": true,
    "instructions": "Sample workflow for portfolio purposes. Replace the mailbox, Google Sheet ID, Slack channel IDs, and AI credentials with the client's own before deploying live. This workflow only ever drafts replies for human review: it never auto-sends to a customer."
  }
}
AI drafts a reply, a human always reviews before it's sent Empty/unparseable-email error path included (not shown above)
Flagship example
Photography Studio: Booking to Deposit to Confirmation Chain
Photography Studio, Multi-week Chain
n8n_booking_deposit_confirmation_chain.json
Trigger, Webhook
New booking submitted
Action
Create/update contact (CRM)
Action
Create deal, deposit pending
Action
Create 30% deposit invoice
Action, Wait 5 days
Send invoice email, then check status
Router
IF invoice paid
PAID
Action
Confirm session on calendar
Action
Send confirmation email
Action
Notify studio team (Slack)
UNPAID
Action, Wait 48h
Send reminder, then re-check status
Router
IF still unpaid
Action
Escalate to owner + flag at-risk in CRM
If paid after the reminder, rejoins the "Paid" path above instead of dead-ending.
{
  "name": "Photography Studio: Booking to Deposit to Confirmation Chain",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "studio-new-booking",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "3c4d5e6f-0001-4c3c-9d3c-000000000001",
      "name": "New Booking Submitted",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [-60, 400],
      "notes": "Fires the instant a client submits the booking form on example.com (session type, date, package price)."
    },
    {
      "parameters": {
        "resource": "contact",
        "operation": "upsert",
        "email": "={{ $json.body.email }}",
        "additionalFields": {
          "firstName": "={{ $json.body.first_name }}",
          "lastName": "={{ $json.body.last_name }}",
          "phone": "={{ $json.body.phone }}"
        }
      },
      "id": "3c4d5e6f-0002-4c3c-9d3c-000000000002",
      "name": "Create/Update Contact (CRM)",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2,
      "position": [200, 400],
      "notes": "Creates the client record if new, or updates it if they have booked before. Every booking is tracked as a deal in the CRM pipeline."
    },
    {
      "parameters": {
        "resource": "deal",
        "operation": "create",
        "dealName": "={{ $json.body.first_name }} {{ $json.body.last_name }}, {{ $json.body.session_type }}",
        "additionalFields": {
          "amount": "={{ $json.body.package_price }}",
          "dealStage": "booking_deposit_pending",
          "closeDate": "={{ $json.body.session_date }}"
        }
      },
      "id": "3c4d5e6f-0003-4c3c-9d3c-000000000003",
      "name": "Create Deal (Deposit Pending)",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2,
      "position": [440, 400],
      "notes": "Deal stage doubles as the studio's at-a-glance status tracker across every open booking."
    },
    {
      "parameters": {
        "resource": "invoice",
        "operation": "create",
        "customer": "={{ $json.body.email }}",
        "amount": "={{ Math.round($json.body.package_price * 0.3 * 100) / 100 }}",
        "description": "=30% deposit, {{ $json.body.session_type }} session on {{ $json.body.session_date }}",
        "dueDate": "={{ $now.plus({days: 5}).toISODate() }}"
      },
      "id": "3c4d5e6f-0004-4c3c-9d3c-000000000004",
      "name": "Create 30% Deposit Invoice",
      "type": "n8n-nodes-base.stripe",
      "typeVersion": 1,
      "position": [680, 400],
      "notes": "Deposit is always 30% of the package price, calculated at send time rather than hardcoded per package."
    },
    {
      "parameters": {
        "sendTo": "={{ $json.body.email }}",
        "subject": "=Your studio booking, deposit invoice enclosed",
        "message": "=Hi {{ $json.body.first_name }}, thanks for booking your {{ $json.body.session_type }} session! To lock in {{ $json.body.session_date }}, please pay the attached deposit invoice within 5 days. We'll follow up automatically as the date approaches."
      },
      "id": "3c4d5e6f-0005-4c3c-9d3c-000000000005",
      "name": "Send Invoice Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [920, 400],
      "notes": "Confirms receipt of the booking immediately, before any payment status is known."
    },
    {
      "parameters": {
        "amount": 5,
        "unit": "days"
      },
      "id": "3c4d5e6f-0006-4c3c-9d3c-000000000006",
      "name": "Wait Until Invoice Due",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [1160, 400],
      "notes": "Pauses the workflow execution itself, no separate scheduled workflow needed to check back in."
    },
    {
      "parameters": {
        "resource": "invoice",
        "operation": "get",
        "invoiceId": "={{ $('Create 30% Deposit Invoice').item.json.id }}"
      },
      "id": "3c4d5e6f-0007-4c3c-9d3c-000000000007",
      "name": "Check Invoice Payment Status",
      "type": "n8n-nodes-base.stripe",
      "typeVersion": 1,
      "position": [1400, 400],
      "notes": "Reads the live invoice status directly from Stripe rather than trusting a local flag."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "paid-1",
              "leftValue": "={{ $json.status }}",
              "rightValue": "paid",
              "operator": { "type": "string", "operation": "equals" }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "3c4d5e6f-0008-4c3c-9d3c-000000000008",
      "name": "IF Invoice Paid",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1640, 400],
      "notes": "Splits the workflow into a confirmed path and a reminder/escalation path."
    },
    {
      "parameters": {
        "calendar": "={{ 'studio-bookings@example.com' }}",
        "start": "={{ $('New Booking Submitted').item.json.body.session_date }}",
        "summary": "=Session, {{ $('New Booking Submitted').item.json.body.first_name }} {{ $('New Booking Submitted').item.json.body.last_name }}"
      },
      "id": "3c4d5e6f-0009-4c3c-9d3c-000000000009",
      "name": "Confirm Session on Calendar",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [1900, 220],
      "notes": "Only ever added to the studio calendar once the deposit clears, so the calendar never shows unconfirmed holds as real bookings."
    },
    {
      "parameters": {
        "sendTo": "={{ $('New Booking Submitted').item.json.body.email }}",
        "subject": "You're confirmed! Session details enclosed",
        "message": "Deposit received, your session is officially on the calendar. We'll send a prep guide 48 hours before your shoot."
      },
      "id": "3c4d5e6f-0010-4c3c-9d3c-00000000000a",
      "name": "Send Confirmation Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [2140, 220],
      "notes": "Distinct from the initial booking email, confirms the deposit specifically."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#studio-bookings", "mode": "name" },
        "text": "=Session confirmed: {{ $('New Booking Submitted').item.json.body.first_name }} {{ $('New Booking Submitted').item.json.body.last_name }}, {{ $('New Booking Submitted').item.json.body.session_type }} on {{ $('New Booking Submitted').item.json.body.session_date }}.",
        "otherOptions": {}
      },
      "id": "3c4d5e6f-0011-4c3c-9d3c-00000000000b",
      "name": "Notify Studio Team",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [2380, 220],
      "notes": "Keeps the whole team aware of new confirmed sessions without anyone needing to check the CRM manually."
    },
    {
      "parameters": {
        "sendTo": "={{ $('New Booking Submitted').item.json.body.email }}",
        "subject": "Friendly reminder: deposit still pending",
        "message": "=Hi {{ $('New Booking Submitted').item.json.body.first_name }}, just a reminder that your deposit invoice for {{ $('New Booking Submitted').item.json.body.session_date }} is still outstanding. Let us know if you'd like a different payment method or have any questions."
      },
      "id": "3c4d5e6f-0012-4c3c-9d3c-00000000000c",
      "name": "Send Payment Reminder Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [1900, 580],
      "notes": "First reminder, kept friendly and low-pressure rather than an automatic penalty notice."
    },
    {
      "parameters": {
        "amount": 48,
        "unit": "hours"
      },
      "id": "3c4d5e6f-0013-4c3c-9d3c-00000000000d",
      "name": "Wait 48 Hours",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [2140, 580],
      "notes": "Gives the reminder email time to land before checking payment status again."
    },
    {
      "parameters": {
        "resource": "invoice",
        "operation": "get",
        "invoiceId": "={{ $('Create 30% Deposit Invoice').item.json.id }}"
      },
      "id": "3c4d5e6f-0014-4c3c-9d3c-00000000000e",
      "name": "Re-check Payment Status",
      "type": "n8n-nodes-base.stripe",
      "typeVersion": 1,
      "position": [2380, 580],
      "notes": "Second live status check rather than assuming the reminder worked."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "still-unpaid-1",
              "leftValue": "={{ $json.status }}",
              "rightValue": "paid",
              "operator": { "type": "string", "operation": "notEquals" }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "3c4d5e6f-0015-4c3c-9d3c-00000000000f",
      "name": "IF Still Unpaid",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [2620, 580],
      "notes": "Second branch point. A late payment here still reconnects to the confirmed path rather than staying stuck."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#studio-bookings-urgent", "mode": "name" },
        "text": "=Deposit still unpaid after reminder: {{ $('New Booking Submitted').item.json.body.first_name }} {{ $('New Booking Submitted').item.json.body.last_name }}, session date {{ $('New Booking Submitted').item.json.body.session_date }}. Recommend a personal follow-up.",
        "otherOptions": {}
      },
      "id": "3c4d5e6f-0016-4c3c-9d3c-000000000010",
      "name": "Escalate to Owner",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [2860, 460],
      "notes": "Escalations go to a separate, higher-attention channel than routine booking confirmations."
    },
    {
      "parameters": {
        "resource": "deal",
        "operation": "update",
        "dealId": "={{ $('Create Deal (Deposit Pending)').item.json.id }}",
        "additionalFields": {
          "dealStage": "at_risk_unpaid_deposit"
        }
      },
      "id": "3c4d5e6f-0017-4c3c-9d3c-000000000011",
      "name": "Flag Deal At-Risk in CRM",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2,
      "position": [2860, 640],
      "notes": "So the studio's pipeline view reflects reality without anyone updating it by hand."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "missing-1",
              "leftValue": "={{ $json.body.session_date }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "empty" }
            },
            {
              "id": "missing-2",
              "leftValue": "={{ $json.body.email }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "empty" }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "3c4d5e6f-0018-4c3c-9d3c-000000000012",
      "name": "Missing Required Booking Field?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [200, 620],
      "notes": "Error-handling branch, catches a malformed booking submission before it creates a broken CRM record or invoice."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#automation-alerts", "mode": "name" },
        "text": "=Booking form submitted with a missing session date or email. Raw payload: {{ JSON.stringify($json.body) }}. Please follow up manually.",
        "otherOptions": {}
      },
      "id": "3c4d5e6f-0019-4c3c-9d3c-000000000013",
      "name": "Notify - Malformed Booking",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [440, 620],
      "notes": "Fallback so a bad submission is surfaced to a human instead of silently creating a broken deal or invoice."
    }
  ],
  "connections": {
    "New Booking Submitted": {
      "main": [
        [
          { "node": "Create/Update Contact (CRM)", "type": "main", "index": 0 },
          { "node": "Missing Required Booking Field?", "type": "main", "index": 0 }
        ]
      ]
    },
    "Create/Update Contact (CRM)": {
      "main": [[{ "node": "Create Deal (Deposit Pending)", "type": "main", "index": 0 }]]
    },
    "Create Deal (Deposit Pending)": {
      "main": [[{ "node": "Create 30% Deposit Invoice", "type": "main", "index": 0 }]]
    },
    "Create 30% Deposit Invoice": {
      "main": [[{ "node": "Send Invoice Email", "type": "main", "index": 0 }]]
    },
    "Send Invoice Email": {
      "main": [[{ "node": "Wait Until Invoice Due", "type": "main", "index": 0 }]]
    },
    "Wait Until Invoice Due": {
      "main": [[{ "node": "Check Invoice Payment Status", "type": "main", "index": 0 }]]
    },
    "Check Invoice Payment Status": {
      "main": [[{ "node": "IF Invoice Paid", "type": "main", "index": 0 }]]
    },
    "IF Invoice Paid": {
      "main": [
        [{ "node": "Confirm Session on Calendar", "type": "main", "index": 0 }],
        [{ "node": "Send Payment Reminder Email", "type": "main", "index": 0 }]
      ]
    },
    "Confirm Session on Calendar": {
      "main": [[{ "node": "Send Confirmation Email", "type": "main", "index": 0 }]]
    },
    "Send Confirmation Email": {
      "main": [[{ "node": "Notify Studio Team", "type": "main", "index": 0 }]]
    },
    "Send Payment Reminder Email": {
      "main": [[{ "node": "Wait 48 Hours", "type": "main", "index": 0 }]]
    },
    "Wait 48 Hours": {
      "main": [[{ "node": "Re-check Payment Status", "type": "main", "index": 0 }]]
    },
    "Re-check Payment Status": {
      "main": [[{ "node": "IF Still Unpaid", "type": "main", "index": 0 }]]
    },
    "IF Still Unpaid": {
      "main": [
        [
          { "node": "Escalate to Owner", "type": "main", "index": 0 },
          { "node": "Flag Deal At-Risk in CRM", "type": "main", "index": 0 }
        ],
        [{ "node": "Confirm Session on Calendar", "type": "main", "index": 0 }]
      ]
    },
    "Missing Required Booking Field?": {
      "main": [
        [{ "node": "Notify - Malformed Booking", "type": "main", "index": 0 }],
        []
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "portfolioSample": true,
    "instructions": "Sample workflow for portfolio purposes. Replace the HubSpot, Stripe, Gmail, Google Calendar, and Slack credentials with the client's own before deploying live. The late-payment branch reconnects into the same confirmed-session path rather than dead-ending, so a client who pays after the reminder still gets booked normally."
  }
}
Reads live payment status from Stripe rather than trusting a local flag A late payment still reconnects to the confirmed path, it never dead-ends

All businesses shown above are generic placeholders built to demonstrate delivery quality, not real clients.

Want to see this on your own business?

Get in touch and we'll put together something built around what you actually do.

Get in touch