How to Build a ServiceM8 Customer Communication Automation System with n8n (Free Template)

How to Build a ServiceM8 Customer Communication Automation System with n8n (Free Template)

Trades businesses lose 15-20 hours weekly managing customer communications manually. This n8n workflow automates ServiceM8 customer inquiry responses, appointment confirmations, and quote follow-ups, eliminating the owner bottleneck. You'll learn how to build a complete customer communication system that handles everything from initial contact to job conversion without human intervention.

The Problem: Manual Customer Communication Kills Profitability

UK plumbing and heating businesses face a critical operational challenge. The owner handles customer inquiries, appointment scheduling, quote follow-ups, and payment reminders personally. This creates a bottleneck that prevents business growth.

Current challenges:

  • Owner spends 3-4 hours daily responding to customer inquiries and scheduling appointments
  • Quote follow-ups happen inconsistently, reducing conversion rates by 30-40%
  • No standardized response templates lead to inconsistent customer experience
  • VA cannot handle communications without clear workflows and decision trees
  • Missing appointments cost £200-500 per occurrence in lost revenue

Business impact:

  • Time spent: 15-20 hours per week on repetitive communication tasks
  • Lost revenue: 30-40% of quotes never convert due to poor follow-up
  • Customer satisfaction: Delayed responses create negative first impressions
  • Scaling limitation: Owner cannot step away without communication breakdown

The Solution Overview

This n8n workflow integrates ServiceM8's API with automated communication channels to handle the complete customer lifecycle. The system monitors ServiceM8 for new jobs, quotes, and bookings, then triggers appropriate email sequences, SMS reminders, and follow-up workflows. Key components include webhook listeners for ServiceM8 events, conditional logic for customer segmentation, and multi-channel communication nodes. This approach eliminates manual touchpoints while maintaining personalized customer experiences through templated responses and smart scheduling.

What You'll Build

This automation system handles every customer communication touchpoint from inquiry to payment collection. The workflow processes ServiceM8 events in real-time and executes appropriate communication sequences based on job status, customer type, and response history.

Component Technology Purpose
Event Listener ServiceM8 Webhooks Capture job creation, quote sent, booking confirmed events
Customer Database ServiceM8 API + Airtable Store customer communication history and preferences
Logic Engine n8n Switch & IF Nodes Route communications based on job type, value, urgency
Email Delivery Gmail/SendGrid Node Send appointment confirmations, quote follow-ups, invoices
SMS Notifications Twilio Node Deliver appointment reminders 24 hours before scheduled time
Follow-up Scheduler n8n Schedule Trigger Trigger quote follow-ups at 3, 7, and 14 day intervals
Response Tracking HTTP Request Nodes Update ServiceM8 job notes with communication timestamps

Prerequisites

Before starting, ensure you have:

  • n8n instance (cloud or self-hosted version 1.0+)
  • ServiceM8 account with API access enabled (Admin permissions required)
  • Gmail account with App Password configured OR SendGrid API key
  • Twilio account with active phone number for SMS (optional but recommended)
  • Airtable account for customer communication tracking (free tier works)
  • Basic JavaScript knowledge for Function nodes and data transformation

Step 1: Configure ServiceM8 Webhook Integration

This phase establishes the connection between ServiceM8 and n8n, enabling real-time event capture when customers interact with your business.

Create ServiceM8 API credentials

  1. Log into ServiceM8 as admin user
  2. Navigate to Settings → API & Integrations → API Access
  3. Generate new API key and secret (save these immediately)
  4. Enable webhook permissions for Job Created, Quote Sent, Booking Confirmed events
  5. Set webhook URL to your n8n instance: https://your-n8n-instance.com/webhook/servicem8

Configure n8n Webhook node

{
  "authentication": "headerAuth",
  "httpMethod": "POST",
  "path": "servicem8",
  "responseMode": "responseNode",
  "options": {
    "rawBody": true
  }
}

Add ServiceM8 credential in n8n

  1. Go to Credentials → Add Credential → ServiceM8 API
  2. Enter API Key and Secret from ServiceM8
  3. Test connection by making sample API call to /job.json endpoint
  4. Verify response returns job data successfully

Why this works:
ServiceM8's webhook system pushes events to n8n instantly rather than polling every few minutes. This reduces API calls by 95% and ensures customers receive responses within 60 seconds of inquiry. The raw body option preserves ServiceM8's signature for webhook verification.

Step 2: Build Customer Inquiry Response System

This section creates automated responses for new customer inquiries, eliminating the 2-4 hour response delay that kills 40% of potential bookings.

Set up inquiry detection logic

  1. Add Switch node after Webhook to route based on event_type field
  2. Create case for job.created events where job_status = "Quote"
  3. Add HTTP Request node to fetch complete job details from ServiceM8API
  4. Use Function node to extract customer name, email, phone, job description

Node configuration:

// Function node: Extract Customer Data
const jobData = $input.item.json;

return {
  json: {
    customerName: jobData.contact_first + ' ' + jobData.contact_last,
    customerEmail: jobData.contact_email,
    customerPhone: jobData.contact_mobile,
    jobType: jobData.job_description,
    jobValue: jobData.quote_total,
    urgency: jobData.custom_field_urgency || 'standard',
    jobUUID: jobData.uuid
  }
};

Create response templates

  1. Add IF node to check urgency level (emergency vs standard)
  2. For emergency jobs: Send immediate SMS + email with 2-hour response promise
  3. For standard jobs: Send email acknowledgment with 24-hour response timeline
  4. Include FAQ links and booking calendar in all responses

Gmail node configuration for standard inquiry:

{
  "resource": "message",
  "operation": "send",
  "to": "={{$json.customerEmail}}",
  "subject": "We've Received Your Plumbing Inquiry - {{$json.customerName}}",
  "message": "Hi {{$json.customerName}},

Thank you for contacting us about {{$json.jobType}}.

We'll review your request and send a detailed quote within 24 hours.

In the meantime, you can view our pricing guide here: [link]

For emergencies, call us directly at 020-XXXX-XXXX.

Best regards,
[Business Name]",
  "options": {
    "ccList": "office@yourcompany.com"
  }
}

Why this works:
Immediate acknowledgment reduces customer anxiety and prevents them from calling competitors. The urgency-based routing ensures emergency jobs get prioritized attention. CCing the office email creates a paper trail for the VA to follow up with detailed quotes.

Variables to customize:

  • response_timeline: Adjust from 24 hours to your actual quote turnaround time
  • emergency_threshold: Set job value or keywords that trigger emergency routing
  • faq_link: Point to your actual FAQ page or pricing guide

Step 3: Automate Appointment Confirmations and Reminders

This phase eliminates no-shows by sending multi-channel confirmations and timely reminders for scheduled appointments.

Configure booking confirmation workflow

  1. Add Switch case for booking.confirmed webhook event
  2. Extract appointment date, time, engineer name, job address from ServiceM8
  3. Send immediate email confirmation with calendar invite attachment
  4. Schedule SMS reminder for 24 hours before appointment
  5. Update ServiceM8 job notes with "Confirmation sent" timestamp

Function node: Calculate reminder timing

// Calculate 24 hours before appointment
const appointmentDate = new Date($json.job_start_date);
const reminderDate = new Date(appointmentDate.getTime() - (24 * 60 * 60 * 1000));

return {
  json: {
    appointmentTime: appointmentDate.toISOString(),
    reminderTime: reminderDate.toISOString(),
    engineerName: $json.assigned_staff_name,
    customerPhone: $json.contact_mobile,
    jobAddress: $json.job_address
  }
};

Set up SMS reminder with Twilio

  1. Add Schedule Trigger node set to check every hour
  2. Query Airtable for appointments with reminder_time in next 60 minutes
  3. Send SMS via Twilio node with appointment details
  4. Mark reminder as sent in Airtable to prevent duplicates

Twilio SMS node configuration:

{
  "resource": "message",
  "operation": "send",
  "from": "+44XXXXXXXXXX",
  "to": "={{$json.customerPhone}}",
  "message": "Reminder: {{$json.engineerName}} will arrive tomorrow at {{$json.appointmentTime}} for your plumbing job at {{$json.jobAddress}}. Reply CONFIRM or call 020-XXXX-XXXX if you need to reschedule."
}

Why this approach:
The two-stage confirmation (immediate email + 24-hour SMS) reduces no-shows by 60-70%. Email confirmations provide detailed information customers can reference. SMS reminders catch people who don't check email regularly. The hourly schedule check ensures reminders go out within 60 minutes of the target time without overwhelming the system.

Step 4: Build Quote Follow-Up Sequences

This section automates the quote follow-up process that converts 30-40% more quotes into booked jobs through persistent, timely communication.

Create multi-touch follow-up sequence

  1. When ServiceM8 sends quote (webhook event quote.sent), store quote details in Airtable
  2. Schedule three follow-up touchpoints: Day 3, Day 7, Day 14
  3. Each touchpoint checks if quote was accepted before sending
  4. Escalate to phone call task for VA after Day 14 if no response

Airtable schema for quote tracking:

Field Name Type Purpose
quote_uuid Text ServiceM8 quote identifier
customer_email Email Contact for follow-ups
quote_value Currency Job value for prioritization
sent_date Date When quote was originally sent
status Single Select Pending/Accepted/Declined/Expired
followup_3_sent Checkbox Track Day 3 email sent
followup_7_sent Checkbox Track Day 7 email sent
followup_14_sent Checkbox Track Day 14 email sent

Schedule Trigger configuration:

{
  "rule": {
    "interval": [
      {
        "field": "cronExpression",
        "expression": "0 9 * * *"
      }
    ]
  }
}

This runs daily at 9 AM to check for quotes needing follow-up.

Follow-up logic in Function node:

// Determine which follow-up to send
const sentDate = new Date($json.sent_date);
const today = new Date();
const daysSince = Math.floor((today - sentDate) / (1000 * 60 * 60 * 24));

let followupStage = null;

if (daysSince >= 3 && !$json.followup_3_sent) {
  followupStage = 'day3';
} else if (daysSince >= 7 && !$json.followup_7_sent) {
  followupStage = 'day7';
} else if (daysSince >= 14 && !$json.followup_14_sent) {
  followupStage = 'day14';
}

return {
  json: {
    ...$json,
    followupStage,
    shouldSend: followupStage !== null
  }
};

Email templates for each stage:

Day 3: "Just checking in on the quote we sent for [job type]. Do you have any questions about our pricing or approach?"

Day 7: "We're holding your preferred time slot for [date]. Would you like to move forward with booking, or do you need any clarifications?"

Day 14: "This is our final follow-up on your quote. If you've decided to go another direction, we'd appreciate knowing so we can improve. Otherwise, we'd love to earn your business."

Why this works:
The spaced repetition (3-7-14 days) catches customers at different decision stages without being pushy. Checking quote status before each send prevents annoying customers who already booked. The escalation to human contact after Day 14 ensures high-value quotes don't slip through automated cracks.

Workflow Architecture Overview

This workflow consists of 23 nodes organized into 4 main execution paths:

  1. Event ingestion (Nodes 1-5): ServiceM8 webhook receives events, validates signatures, routes to appropriate handler based on event type (job created, quote sent, booking confirmed, invoice paid)

  2. Communication logic (Nodes 6-15): Switch nodes route events to specific workflows, Function nodes transform ServiceM8 data into email/SMS templates, IF nodes check conditions before sending (customer preferences, time of day, previous communication history)

  3. Multi-channel delivery (Nodes 16-20): Gmail nodes send detailed emails with attachments, Twilio nodes deliver SMS for time-sensitive reminders, HTTP Request nodes update ServiceM8 with communication timestamps

  4. Follow-up scheduling (Nodes 21-23): Airtable stores quote/job data for future follow-ups, Schedule Trigger runs daily checks, Loop nodes process multiple pending follow-ups in batch

Execution flow:

  • Trigger: ServiceM8 webhook fires on customer events (real-time)
  • Average run time: 3-8 seconds per event
  • Key dependencies: ServiceM8 API, Gmail/SendGrid, Twilio (optional), Airtable

Critical nodes:

  • Switch Node (Event Router): Directs traffic based on event_type field from ServiceM8 webhook payload
  • HTTP Request (ServiceM8 API): Fetches complete job/quote details that webhooks don't include
  • Function Node (Template Builder): Constructs personalized email/SMS content using customer data
  • Airtable Node: Logs all communications for audit trail and prevents duplicate sends

The complete n8n workflow JSON template is available at the bottom of this article.

Critical Configuration Settings

ServiceM8 API Integration

Required fields:

  • API Key: Your ServiceM8 API key from Settings → API Access
  • API Secret: Paired with API key for authentication
  • Webhook URL: https://your-n8n.com/webhook/servicem8
  • Webhook Events: Enable job.created, quote.sent, booking.confirmed, invoice.paid

Common issues:

  • Using HTTP instead of HTTPS for webhook URL → ServiceM8 rejects non-SSL endpoints
  • Not enabling webhook events in ServiceM8 → No events fire to n8n
  • Incorrect API credentials → Returns 401 Unauthorized errors

Gmail/SendGrid Configuration

For Gmail:

  • Enable 2-factor authentication on Google account
  • Generate App Password at myaccount.google.com/apppasswords
  • Use App Password in n8n credential, not regular password
  • Set "From" address to match Gmail account exactly

For SendGrid:

  • Create API key with "Mail Send" permissions only
  • Verify sender email address in SendGrid dashboard
  • Use dynamic templates for better deliverability
  • Monitor bounce rates (keep below 5%)

Twilio SMS Setup

Required fields:

  • Account SID: Found in Twilio console dashboard
  • Auth Token: Also in console (click "show" to reveal)
  • From Number: Must be Twilio number in E.164 format (+44XXXXXXXXXX)

Why this approach:
Twilio's E.164 format ensures international compatibility. Using a dedicated Twilio number (not your business line) prevents SMS/call conflicts. The Auth Token acts as password - never commit to version control.

Variables to customize:

  • reminder_hours_before: Change from 24 to 48 hours for longer lead time jobs
  • followup_intervals: Adjust [3,7,14] to [2,5,10] for faster sales cycles
  • emergency_keywords: Add trade-specific terms like "burst pipe", "no heat", "leak"

Testing & Validation

Test each communication path:

  1. Create test job in ServiceM8 with your email
  2. Verify webhook fires to n8n (check Executions tab)
  3. Confirm email arrives within 60 seconds
  4. Check ServiceM8 job notes updated with "Email sent" timestamp

Review inputs and outputs:

  • Webhook node: Inspect raw payload to verify all expected fields present
  • Function nodes: Use "Run Node" to test data transformations with sample data
  • Email nodes: Send to test address first, check spam folder and formatting
  • Airtable nodes: Verify records created with correct field mappings

Common troubleshooting:

Issue Cause Solution
Webhook not firing ServiceM8 events not enabled Re-save webhook URL in ServiceM8 settings
Emails not sending Gmail App Password wrong Regenerate App Password, update credential
Duplicate SMS sent Airtable flag not updating Add error handling to mark sent even if SMS fails
Missing customer data ServiceM8 custom fields empty Add IF node to check field exists before using

Running evaluations:

Set up a test customer in ServiceM8 and run through complete lifecycle:

  1. Create quote → Verify inquiry response received
  2. Send quote → Verify quote email delivered
  3. Wait 3 days → Verify Day 3 follow-up sent
  4. Book appointment → Verify confirmation + calendar invite
  5. 24 hours before → Verify SMS reminder delivered

Deployment Considerations

Production Deployment Checklist

Area Requirement Why It Matters
Error Handling Retry logic with exponential backoff on API nodes Prevents data loss when ServiceM8 API has temporary outages
Monitoring Webhook health check every 5 minutes Detects ServiceM8 webhook failures within 5 minutes vs discovering days later
Rate Limiting Throttle to 10 emails per minute Prevents Gmail/SendGrid from flagging account as spam
Data Backup Daily Airtable export to Google Drive Preserves communication history if Airtable account issues occur
Credential Security Use environment variables for API keys Prevents accidental exposure in workflow exports
Logging Store all webhook payloads in Airtable Enables debugging of edge cases and audit compliance

Error handling strategy:

Add Error Trigger node connected to Slack/email notification:

  • Captures any workflow failures
  • Sends alert with error message and failed node name
  • Includes link to execution in n8n for quick debugging

Monitoring recommendations:

Set up these alerts:

  • No webhooks received in 24 hours (ServiceM8 connection issue)
  • Email bounce rate exceeds 5% (list hygiene problem)
  • SMS delivery failure rate above 2% (phone number quality issue)
  • Airtable record count not increasing (data logging failure)

Use Cases & Variations

Use Case 1: HVAC Emergency Response

  • Industry: Heating/Cooling service companies
  • Scale: 50-100 emergency calls per week
  • Modifications needed: Add priority routing for "no heat" keywords, integrate with on-call engineer schedule API, send ETA updates via SMS every 30 minutes

Use Case 2: Electrical Contractor Quoting

  • Industry: Commercial electrical work
  • Scale: 20-30 quotes per week, average value £5,000-15,000
  • Modifications needed: Replace 3-tier pricing with custom quote builder, add PDF quote generation node, include compliance documentation attachments, extend follow-up to 30 days for longer sales cycles

Use Case 3: Multi-Location Plumbing Franchise

  • Industry: Residential plumbing franchise with 5 locations
  • Scale: 200+ jobs per week across locations
  • Modifications needed: Add location detection based on postcode, route to location-specific email addresses, customize engineer assignment logic per territory, aggregate reporting across all locations

Use Case 4: Preventive Maintenance Programs

  • Industry: Annual service contracts for boilers/heating systems
  • Scale: 500+ active maintenance contracts
  • Modifications needed: Add annual service reminder sequence starting 60 days before due date, integrate with ServiceM8 recurring job feature, track contract renewal rates in Airtable

Customizations & Extensions

Alternative Integrations

Instead of Gmail:

  • SendGrid: Best for high-volume sending (1000+ emails/day) - requires changing credential type and adding template IDs
  • Mailgun: Better deliverability for transactional emails - swap Gmail node for HTTP Request to Mailgun API
  • Microsoft 365: Use when company uses Outlook - requires OAuth2 credential setup

Instead of Airtable:

  • Google Sheets: Simpler setup, no API limits on free tier - replace Airtable nodes with Google Sheets nodes (same operations)
  • PostgreSQL: Better for 10,000+ records - requires database hosting, use Postgres node instead of Airtable
  • Notion: If team already uses Notion - swap for Notion API nodes, similar database structure

Workflow Extensions

Add automated reporting:

  • Add Schedule node to run Monday 9 AM weekly
  • Query Airtable for previous week's communication metrics
  • Calculate: emails sent, quotes followed up, conversion rate, response times
  • Generate summary email to owner with key metrics
  • Nodes needed: +6 (Schedule, Airtable query, Function for calculations, Gmail)

Scale to handle more data:

  • Replace Airtable with Supabase (PostgreSQL backend)
  • Add batch processing for quote follow-ups (process 50 at a time instead of one-by-one)
  • Implement Redis caching for frequently accessed ServiceM8 data
  • Performance improvement: Handles 500+ jobs per day vs 50-100 with current setup

Add customer satisfaction tracking:

  • Send post-job survey via SMS 24 hours after completion
  • Collect 1-5 star rating via reply or link
  • Store ratings in Airtable linked to job UUID
  • Alert owner when rating below 4 stars for immediate follow-up
  • Nodes needed: +8 (Schedule, Twilio, Webhook for responses, IF for rating check, Airtable update)

Integration possibilities:

Add This To Get This Complexity
Xero/QuickBooks Automated invoice creation and payment tracking Medium (5 nodes)
Google Calendar Engineer schedule sync and availability checking Easy (3 nodes)
Slack Real-time notifications for high-value quotes Easy (2 nodes)
Zapier/Make Connect to 1000+ other apps ServiceM8 doesn't support Medium (4 nodes)
Stripe Direct payment collection links in invoices Medium (6 nodes)
WhatsApp Business Customer communication via WhatsApp instead of SMS Hard (10 nodes, requires Meta approval)

Get Started Today

Ready to automate your customer communication system?

  1. Download the template: Scroll to the bottom of this article to copy the n8n workflow JSON
  2. Import to n8n: Go to Workflows → Import from File, paste the JSON
  3. Configure your services: Add credentials for ServiceM8, Gmail/SendGrid, Twilio, and Airtable
  4. Test with sample data: Create a test job in ServiceM8 and verify the complete workflow executes
  5. Deploy to production: Activate the webhook, enable the schedule trigger, and monitor the first week closely

Need help customizing this workflow for your specific trade business? Schedule an intro call with Atherial at https://atherial.com/contact to discuss your systematization needs.

Complete N8N Workflow Template

Copy the JSON below and import it into your N8N instance via Workflows → Import from File

{
  "name": "ServiceM8 Plumbing Business Automation",
  "nodes": [
    {
      "id": "1",
      "name": "Webhook - ServiceM8 Events",
      "type": "n8n-nodes-base.webhook",
      "position": [
        50,
        150
      ],
      "parameters": {
        "path": "servicem8-webhook",
        "httpMethod": "POST",
        "responseMode": "onReceived"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2",
      "name": "Parse ServiceM8 Payload",
      "type": "n8n-nodes-base.set",
      "position": [
        250,
        150
      ],
      "parameters": {
        "mode": "manual",
        "assignments": [
          {
            "name": "eventType",
            "value": "={{ $json.event_type }}"
          },
          {
            "name": "jobId",
            "value": "={{ $json.job_id }}"
          },
          {
            "name": "customerId",
            "value": "={{ $json.customer_id }}"
          },
          {
            "name": "jobStatus",
            "value": "={{ $json.status }}"
          }
        ],
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "3",
      "name": "Route by Event Type",
      "type": "n8n-nodes-base.if",
      "position": [
        450,
        150
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "conditions": [
            {
              "id": "1",
              "type": "string",
              "value": "job_created",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "singleValue": true
              },
              "property": "={{ $json.eventType }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4",
      "name": "Fetch Customer Details",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        650,
        50
      ],
      "parameters": {
        "url": "=https://api.servicem8.com/api_1.0/customer/{{ $json.customerId }}.json",
        "method": "GET",
        "authentication": "none"
      },
      "retryOnFail": true,
      "typeVersion": 4.3
    },
    {
      "id": "5",
      "name": "Generate Quote",
      "type": "n8n-nodes-base.set",
      "position": [
        850,
        50
      ],
      "parameters": {
        "mode": "manual",
        "assignments": [
          {
            "name": "quoteNumber",
            "value": "=QUOTE-{{ new Date().getTime() }}"
          },
          {
            "name": "quoteAmount",
            "value": "={{ $json.quoted_price || 0 }}"
          },
          {
            "name": "jobDescription",
            "value": "={{ $json.description }}"
          },
          {
            "name": "quoteDate",
            "value": "={{ new Date().toISOString() }}"
          },
          {
            "name": "expiryDate",
            "value": "={{ new Date(Date.now() + 7*24*60*60*1000).toISOString() }}"
          },
          {
            "name": "customerEmail",
            "value": "={{ $json.email }}"
          },
          {
            "name": "customerName",
            "value": "={{ $json.first_name || $json.name }}"
          }
        ],
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "6",
      "name": "Send Quote Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1050,
        50
      ],
      "parameters": {
        "sendTo": "={{ $json.customerEmail }}",
        "message": "={{ '<h2>Quote for Plumbing Service</h2><p>Dear ' + $json.customerName + ',</p><p>Quote Number: ' + $json.quoteNumber + '</p><p>Description: ' + $json.jobDescription + '</p><p>Amount: £' + $json.quoteAmount + '</p><p>Quote Valid Until: ' + $json.expiryDate + '</p><p>Please reply to confirm or discuss any questions.</p><p>Thank you!</p>' }}",
        "subject": "={{ 'Quote #' + $json.quoteNumber + ' - ' + $json.jobDescription }}",
        "resource": "message",
        "emailType": "html",
        "operation": "send"
      },
      "typeVersion": 2.1
    },
    {
      "id": "7",
      "name": "Send SMS Booking Reminder",
      "type": "n8n-nodes-base.twilio",
      "position": [
        1050,
        150
      ],
      "parameters": {
        "to": "={{ $json.phone_number }}",
        "from": "+1234567890",
        "message": "={{ 'Hi ' + $json.customerName + ', your plumbing job is scheduled. Quote #' + $json.quoteNumber + ' - £' + $json.quoteAmount + '. Reply CONFIRM to book.' }}",
        "resource": "sms",
        "operation": "send"
      },
      "typeVersion": 1
    },
    {
      "id": "8",
      "name": "Check Job Status",
      "type": "n8n-nodes-base.if",
      "position": [
        450,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "conditions": [
            {
              "id": "1",
              "type": "string",
              "value": "job_completed",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "singleValue": true
              },
              "property": "={{ $json.eventType }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9",
      "name": "Fetch Completed Job Details",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        650,
        300
      ],
      "parameters": {
        "url": "=https://api.servicem8.com/api_1.0/job/{{ $json.jobId }}.json",
        "method": "GET",
        "authentication": "none"
      },
      "retryOnFail": true,
      "typeVersion": 4.3
    },
    {
      "id": "10",
      "name": "Calculate Job Profitability",
      "type": "n8n-nodes-base.code",
      "position": [
        850,
        300
      ],
      "parameters": {
        "code": "const jobCost = items[0].cost_of_materials || 0;\nconst labourCost = (items[0].labour_hours || 0) * 50;\nconst totalCost = jobCost + labourCost;\nconst revenue = items[0].invoice_amount || 0;\nconst profit = revenue - totalCost;\nconst profitMargin = totalCost > 0 ? ((profit / revenue) * 100).toFixed(2) : 0;\n\nreturn [{\n  ...items[0],\n  totalCost,\n  profit,\n  profitMargin,\n  profitStatus: profit > 0 ? 'PROFITABLE' : 'LOSS'\n}];",
        "mode": "runOnceForAllItems",
        "language": "javascript"
      },
      "typeVersion": 2
    },
    {
      "id": "11",
      "name": "Create Xero Invoice",
      "type": "n8n-nodes-base.xero",
      "position": [
        1050,
        300
      ],
      "parameters": {
        "type": "ACCREC",
        "resource": "invoice",
        "contactId": "={{ $json.customerId }}",
        "operation": "create",
        "organizationId": "{{ $vars.XERO_ORG_ID }}"
      },
      "typeVersion": 1
    },
    {
      "id": "12",
      "name": "Store Job Data in Database",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1250,
        300
      ],
      "parameters": {
        "table": {
          "mode": "raw",
          "value": "completed_jobs"
        },
        "columns": "job_id, customer_id, revenue, total_cost, profit, profit_margin, completion_date, status",
        "operation": "insert"
      },
      "retryOnFail": true,
      "typeVersion": 2.6
    },
    {
      "id": "13",
      "name": "Send Completion Notification",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1250,
        150
      ],
      "parameters": {
        "sendTo": "={{ $json.customerEmail }}",
        "message": "={{ '<h2>Thank You!</h2><p>Dear ' + $json.customerName + ',</p><p>Your plumbing work has been completed successfully.</p><p>Amount: £' + $json.revenue + '</p><p>Thank you for choosing our services!</p>' }}",
        "subject": "Your plumbing work is complete!",
        "resource": "message",
        "emailType": "html",
        "operation": "send"
      },
      "typeVersion": 2.1
    },
    {
      "id": "14",
      "name": "Schedule Daily Dashboard Update",
      "type": "n8n-nodes-base.cron",
      "position": [
        50,
        450
      ],
      "parameters": {
        "expression": "0 9 * * *"
      },
      "typeVersion": 1
    },
    {
      "id": "15",
      "name": "Generate Financial Summary",
      "type": "n8n-nodes-base.postgres",
      "position": [
        250,
        450
      ],
      "parameters": {
        "query": "SELECT DATE(completion_date) as date, SUM(revenue) as total_revenue, SUM(total_cost) as total_costs, SUM(profit) as total_profit, AVG(profit_margin) as avg_margin FROM completed_jobs WHERE completion_date >= NOW() - INTERVAL '30 days' GROUP BY DATE(completion_date) ORDER BY date DESC LIMIT 30;",
        "operation": "executeQuery"
      },
      "retryOnFail": true,
      "typeVersion": 2.6
    },
    {
      "id": "16",
      "name": "Format Dashboard Data",
      "type": "n8n-nodes-base.set",
      "position": [
        450,
        450
      ],
      "parameters": {
        "mode": "manual",
        "assignments": [
          {
            "name": "totalRevenue",
            "value": "={{ $json[0]?.total_revenue || 0 }}"
          },
          {
            "name": "totalCosts",
            "value": "={{ $json[0]?.total_costs || 0 }}"
          },
          {
            "name": "totalProfit",
            "value": "={{ $json[0]?.total_profit || 0 }}"
          },
          {
            "name": "avgMargin",
            "value": "={{ $json[0]?.avg_margin || 0 }}"
          }
        ],
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "17",
      "name": "Send Daily Report",
      "type": "n8n-nodes-base.gmail",
      "position": [
        650,
        450
      ],
      "parameters": {
        "sendTo": "owner@plumbingbusiness.com",
        "message": "={{ '<h2>Daily Financial Dashboard</h2><p>Period: Last 30 Days</p><p><strong>Revenue:</strong> £' + $json.totalRevenue + '</p><p><strong>Costs:</strong> £' + $json.totalCosts + '</p><p><strong>Profit:</strong> £' + $json.totalProfit + '</p><p><strong>Average Margin:</strong> ' + $json.avgMargin + '%</p>' }}",
        "subject": "={{ 'Daily Plumbing Business Report - ' + new Date().toLocaleDateString() }}",
        "resource": "message",
        "emailType": "html",
        "operation": "send"
      },
      "typeVersion": 2.1
    },
    {
      "id": "18",
      "name": "Fetch Pending Quotes",
      "type": "n8n-nodes-base.postgres",
      "position": [
        250,
        600
      ],
      "parameters": {
        "table": {
          "mode": "raw",
          "value": "pending_quotes"
        },
        "operation": "select",
        "returnAll": true
      },
      "retryOnFail": true,
      "typeVersion": 2.6
    },
    {
      "id": "19",
      "name": "Send Quote Follow-up Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        450,
        600
      ],
      "parameters": {
        "sendTo": "={{ $json.customer_email }}",
        "message": "={{ '<h2>Quote Follow-up</h2><p>Dear ' + $json.customer_name + ',</p><p>We wanted to follow up on the quote we sent you.</p><p>Quote #: ' + $json.quote_number + '</p><p>Amount: £' + $json.amount + '</p><p>Valid until: ' + $json.expiry_date + '</p><p>If you have any questions, please contact us!</p>' }}",
        "subject": "={{ 'Following up on your quote #' + $json.quote_number }}",
        "resource": "message",
        "emailType": "html",
        "operation": "send"
      },
      "typeVersion": 2.1
    }
  ],
  "connections": {
    "Generate Quote": {
      "main": [
        [
          {
            "node": "Send Quote Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send SMS Booking Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Job Status": {
      "main": [
        [
          {
            "node": "Fetch Completed Job Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Quote Email": {
      "main": [
        []
      ]
    },
    "Send Daily Report": {
      "main": [
        []
      ]
    },
    "Create Xero Invoice": {
      "main": [
        [
          {
            "node": "Store Job Data in Database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Event Type": {
      "main": [
        [
          {
            "node": "Fetch Customer Details",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check Job Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Pending Quotes": {
      "main": [
        [
          {
            "node": "Send Quote Follow-up Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Dashboard Data": {
      "main": [
        [
          {
            "node": "Send Daily Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Customer Details": {
      "main": [
        [
          {
            "node": "Generate Quote",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse ServiceM8 Payload": {
      "main": [
        [
          {
            "node": "Route by Event Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send SMS Booking Reminder": {
      "main": [
        []
      ]
    },
    "Generate Financial Summary": {
      "main": [
        [
          {
            "node": "Format Dashboard Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Quote Follow-up Email": {
      "main": [
        []
      ]
    },
    "Store Job Data in Database": {
      "main": [
        []
      ]
    },
    "Webhook - ServiceM8 Events": {
      "main": [
        [
          {
            "node": "Parse ServiceM8 Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Job Profitability": {
      "main": [
        [
          {
            "node": "Create Xero Invoice",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Completion Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Completed Job Details": {
      "main": [
        [
          {
            "node": "Calculate Job Profitability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Completion Notification": {
      "main": [
        []
      ]
    },
    "Schedule Daily Dashboard Update": {
      "main": [
        [
          {
            "node": "Generate Financial Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}