{
  "name": "Auto review request (The Automation Memo template)",
  "nodes": [
    {
      "parameters": {
        "content": "# AUTO REVIEW REQUEST (template from The Automation Memo)\n\nJob marked done -> happy customer gets a one-tap Google review email within seconds -> every request logged with the measured response time.\n\nSETUP (10 minutes)\n1. Open the 'Job marked done (form)' node and copy its production URL. That is the form you (or your job app via webhook) fill when a job closes.\n2. In the Normalize node set REVIEW_URL to your Google review link (https://search.google.com/local/writereview?placeid=YOUR_PLACE_ID) and BUSINESS to your business name.\n3. Pick your Gmail credential on 'Send review request' (the email goes out from that address).\n4. The two Postgres nodes log to a table named review_requests (customer_name, email, job, happy, job_done_at, sent_at, status, review_url, notes). Point them at your database, or delete them and log elsewhere.\n\nRULES BUILT IN: unhappy customers are never asked (they get a human); the same email is never asked twice in 90 days; the email says it was sent automatically. Keep that line.\n\nFree, from theautomationmemo.com. Content on that account is AI-assisted, and we say so.",
        "color": 4,
        "width": 760,
        "height": 600
      },
      "name": "Read me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -320
      ]
    },
    {
      "parameters": {
        "formTitle": "Job done",
        "formDescription": "Close out a job. If the customer is happy, they get a review request the moment you hit the button.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Customer name",
              "fieldName": "name",
              "fieldType": "text",
              "placeholder": "Dana Whitfield",
              "requiredField": true
            },
            {
              "fieldLabel": "Customer email",
              "fieldName": "email",
              "fieldType": "email",
              "placeholder": "dana@example.com",
              "requiredField": true
            },
            {
              "fieldLabel": "What was the job?",
              "fieldName": "job",
              "fieldType": "text",
              "placeholder": "Water heater replacement",
              "requiredField": true
            },
            {
              "fieldLabel": "Happy customer?",
              "fieldName": "happy",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Yes"
                  },
                  {
                    "option": "No"
                  }
                ]
              },
              "requiredField": true
            }
          ]
        },
        "responseMode": "onReceived",
        "options": {
          "path": "job-done",
          "buttonLabel": "Mark done",
          "appendAttribution": false,
          "respondWithOptions": {
            "values": {
              "respondWith": "text",
              "formSubmittedText": "Done. If they were happy, the review request is already in their inbox."
            }
          }
        }
      },
      "name": "Job marked done (form)",
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.6,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const REVIEW_URL = 'https://search.google.com/local/writereview?placeid=YOUR_PLACE_ID';\nconst BUSINESS = 'Your Business Name';\nconst j = $input.first().json || {};\nconst name = String(j.name || '').trim().slice(0, 80);\nconst email = String(j.email || '').trim().toLowerCase().slice(0, 200);\nconst job = String(j.job || '').trim().slice(0, 120);\nconst happy = /^y/i.test(String(j.happy || ''));\nconst first = name.split(/\\s+/)[0] || 'there';\nreturn [{ json: { name, first, email, job, happy, job_done_at: new Date().toISOString(), review_url: REVIEW_URL, business: BUSINESS } }];"
      },
      "name": "Normalize",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "=select count(*)::int as recent from review_requests where lower(email) = '{{ $json.email.replace(/'/g, \"''\") }}' and status = 'sent' and job_done_at > now() - interval '90 days'",
        "options": {}
      },
      "name": "Asked in the last 90 days?",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $('Normalize').first().json.happy }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "leftValue": "={{ $json.recent }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "name": "Happy and not asked recently?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        0
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $('Normalize').first().json.email }}",
        "subject": "=How did we do, {{ $('Normalize').first().json.first }}?",
        "emailType": "html",
        "message": "=<div style=\"font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#111;max-width:520px\"><p>Hi {{ $('Normalize').first().json.first }},</p><p>Thanks for letting us handle the {{ $('Normalize').first().json.job.toLowerCase() }}. If everything went well, a quick Google review helps a small business more than you would think. It takes about a minute.</p><p style=\"margin:28px 0\"><a href=\"{{ $('Normalize').first().json.review_url }}\" style=\"background:#FFB300;color:#0D0F0E;text-decoration:none;font-weight:bold;padding:14px 22px;border-radius:6px;display:inline-block\">Leave a Google review</a></p><p>If something was not right, reply to this email and a real person will make it right first.</p><p style=\"color:#666;font-size:13px\">{{ $('Normalize').first().json.business }}<br>Sent automatically the moment your job was marked done.</p></div>",
        "options": {
          "appendAttribution": false
        }
      },
      "name": "Send review request",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        960,
        -120
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "=insert into review_requests (customer_name, email, job, happy, job_done_at, sent_at, status, review_url, notes)\nvalues ('{{ $('Normalize').first().json.name.replace(/'/g, \"''\") }}', '{{ $('Normalize').first().json.email.replace(/'/g, \"''\") }}', '{{ $('Normalize').first().json.job.replace(/'/g, \"''\") }}', true, '{{ $('Normalize').first().json.job_done_at }}'::timestamptz, now(), 'sent', '{{ $('Normalize').first().json.review_url }}', 'gmail {{ $json.id || '' }}')\nreturning id",
        "options": {}
      },
      "name": "Log: sent",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1200,
        -120
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "=insert into review_requests (customer_name, email, job, happy, job_done_at, status, review_url, notes)\nvalues ('{{ $('Normalize').first().json.name.replace(/'/g, \"''\") }}', '{{ $('Normalize').first().json.email.replace(/'/g, \"''\") }}', '{{ $('Normalize').first().json.job.replace(/'/g, \"''\") }}', {{ $('Normalize').first().json.happy }}, '{{ $('Normalize').first().json.job_done_at }}'::timestamptz, '{{ $('Normalize').first().json.happy ? \"skipped_recent\" : \"skipped_unhappy\" }}', '{{ $('Normalize').first().json.review_url }}', '{{ $('Normalize').first().json.happy ? \"asked within 90 days already\" : \"unhappy customer: human follow-up, no review ask\" }}')\nreturning id",
        "options": {}
      },
      "name": "Log: skipped",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        960,
        160
      ]
    }
  ],
  "connections": {
    "Job marked done (form)": {
      "main": [
        [
          {
            "node": "Normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize": {
      "main": [
        [
          {
            "node": "Asked in the last 90 days?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Asked in the last 90 days?": {
      "main": [
        [
          {
            "node": "Happy and not asked recently?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Happy and not asked recently?": {
      "main": [
        [
          {
            "node": "Send review request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log: skipped",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send review request": {
      "main": [
        [
          {
            "node": "Log: sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}