{
  "id": "Hv7GmHAYKRKRu8UL",
  "name": "AI Cold Email Drafter for Medical & Dental Practices",
  "tags": [],
  "nodes": [
    {
      "id": "0b8089ee-98d1-4093-8485-f86b5d726b25",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3760,
        1904
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## AI Cold Email Drafter for Medical & Dental Practices\n\n### How it works\n\nThis workflow manually processes a Google Sheets lead list for medical and dental practices, selecting only pending records and handling them one at a time. For each lead, it scrapes the practice website with Apify, prepares and validates the scraped text, then uses OpenAI to analyze the practice and draft a tailored cold email. Successful drafts are saved back to Google Sheets with a rate-limit pause between leads, while failed scrapes are marked and skipped.\n\n### Setup steps\n\n- Configure Google Sheets credentials and select the spreadsheet, sheet, and columns used for lead input, draft output, pending status, and scrape-failure status.\n- Configure Apify credentials and set the actor plus input fields needed to scrape each lead\u2019s website URL.\n- Configure OpenAI credentials and review the prompts/model settings for both the analysis step and the final email drafting step.\n- Verify the filter conditions for pending leads and the code-node field mappings so they match the exact column names in your sheet.\n- Set an appropriate wait duration in the rate-limit node for your Apify, OpenAI, and Google Sheets usage limits.\n\n### Customization\n\nAdjust the pending-status filter, scrape actor settings, OpenAI prompts, A/B framing logic, and output columns to match your target specialty, email style, and sales process."
      },
      "typeVersion": 1
    },
    {
      "id": "f13827b1-b6d0-4976-9bed-7ae900e56d2a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4336,
        1872
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 320,
        "content": "## Load pending leads\n\nStarts the workflow manually, reads independent practice leads from Google Sheets, filters to pending rows, and feeds them into a batch loop for one-at-a-time processing."
      },
      "typeVersion": 1
    },
    {
      "id": "81755562-6966-4098-9334-100c61de7a8d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5344,
        1872
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 320,
        "content": "## Scrape practice website\n\nRuns an Apify actor for the current lead, retrieves the actor dataset items, combines scraped pages into a trimmed text blob, and checks whether enough usable content was collected."
      },
      "typeVersion": 1
    },
    {
      "id": "3bcfcd7f-dad7-46b7-9665-35da1cb51888",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6272,
        1872
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 320,
        "content": "## Analyze scraped content\n\nSends validated scraped website text to an OpenAI model for analysis, then parses the model response and selects an A/B positioning or framing strategy for the outreach email."
      },
      "typeVersion": 1
    },
    {
      "id": "bd01496d-0cc0-49a2-82a6-751ed3513ebf",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6448,
        2320
      ],
      "parameters": {
        "color": 7,
        "width": 272,
        "height": 464,
        "content": "## Handle scrape failure\n\nMarks leads with insufficient or failed scraped content as failed in Google Sheets, then routes control back to the lead loop to continue with the next record."
      },
      "typeVersion": 1
    },
    {
      "id": "38215e85-e97d-45d6-9857-cc86f3ed42ef",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6912,
        1872
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 320,
        "content": "## Generate email draft\n\nUses the selected framing and analysis context to have OpenAI draft the cold email, then formats the result into a row structure ready for storage."
      },
      "typeVersion": 1
    },
    {
      "id": "b69ba3c8-830f-465f-920f-8bc86c928e7d",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7472,
        1872
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 320,
        "content": "## Save and throttle\n\nWrites the completed draft back to Google Sheets and waits before returning to the batch loop, helping respect rate limits across scraping, AI, and sheet operations."
      },
      "typeVersion": 1
    },
    {
      "id": "02e6550c-0d43-498e-acbe-9b240be69756",
      "name": "Start Manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        4368,
        2032
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "53b0765e-8801-4afb-9859-c977ac003835",
      "name": "Read Leads from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4576,
        2032
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1794109238,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0/edit#gid=1794109238",
          "cachedResultName": "Independent"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0/edit?usp=drivesdk",
          "cachedResultName": "leads_workflow_ready_v3"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "0d6ca66a-2dff-4cae-acdf-bf7cd60db448",
      "name": "Filter Pending Leads",
      "type": "n8n-nodes-base.filter",
      "position": [
        4768,
        2032
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "filter_pending",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "pending"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "e94e15d9-f51c-428d-a971-2ecbbab9d66b",
      "name": "Loop Through Leads",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        4976,
        2032
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "d1b4bd87-92dd-4d82-af58-a5e2c4ccef90",
      "name": "Combine Scraped Text",
      "type": "n8n-nodes-base.code",
      "position": [
        5744,
        2048
      ],
      "parameters": {
        "jsCode": "// Combine scraped pages into one trimmed text blob for the LLM\nconst items = $input.all();\nlet combined = '';\nfor (const it of items) {\n  const j = it.json || {};\n  const md = j.markdown || j.text || '';\n  if (md) combined += md + '\\n\\n---\\n\\n';\n}\n// Trim to keep token cost sane (~6000 chars \u2248 enough signal)\ncombined = combined.replace(/\\n{3,}/g, '\\n\\n').slice(0, 6000);\n\n// Carry forward the original lead row from the loop\nconst lead = $('Loop Through Leads').item.json;\n\nif (!combined || combined.trim().length < 50) {\n  return [{ json: { ...lead, scrapeOk: false, siteText: '' } }];\n}\nreturn [{ json: { ...lead, scrapeOk: true, siteText: combined } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "7453dd5a-5959-4218-8be6-563470f9e58c",
      "name": "Check Scrape Success",
      "type": "n8n-nodes-base.if",
      "position": [
        5952,
        2048
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "scrape_ok",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.scrapeOk }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "37d9e2d2-64f0-44e0-b907-751a9c145aee",
      "name": "Decide Email Framing",
      "type": "n8n-nodes-base.code",
      "position": [
        6656,
        2064
      ],
      "parameters": {
        "jsCode": "// Parse analysis JSON safely + decide A/B framing\nconst lead = $('Loop Through Leads').item.json;\n\n// \"Message a model\" node nests text deep \u2014 check all known shapes\nlet analysisRaw =\n  $json.output?.[0]?.content?.[0]?.text   // Message a model (Responses API)\n  ?? $json.message?.content                // classic OpenAI node\n  ?? $json.choices?.[0]?.message?.content  // chat completions\n  ?? $json.text\n  ?? $json.content\n  ?? '{}';\n\nif (typeof analysisRaw === 'object') analysisRaw = JSON.stringify(analysisRaw);\n\n// Strip code fences if model wrapped it\nanalysisRaw = String(analysisRaw).replace(/```json|```/g, '').trim();\n\nlet analysis = {};\ntry { analysis = JSON.parse(analysisRaw); }\ncatch(e) { analysis = { practice_summary:'', observations:[], top_angle:'' }; }\n\n// A/B framing: alternate soft vs direct\nconst idx = $('Loop Through Leads').context?.currentRunIndex ?? 0;\nconst framing = (idx % 2 === 0) ? 'soft' : 'direct';\n\nreturn [{ json: { ...lead, analysis, analysisJson: JSON.stringify(analysis), framing } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "eaa39920-3f25-45c8-aeda-5e9d4b9bed94",
      "name": "Prepare Email Draft Row",
      "type": "n8n-nodes-base.code",
      "position": [
        7248,
        2064
      ],
      "parameters": {
        "jsCode": "const lead = $('Loop Through Leads').item.json;\nlet raw =\n  $json.output?.[0]?.content?.[0]?.text\n  ?? $json.message?.content\n  ?? $json.choices?.[0]?.message?.content\n  ?? $json.text ?? $json.content ?? '{}';\nif (typeof raw === 'object') raw = JSON.stringify(raw);\nraw = String(raw).replace(/```json|```/g, '').trim();\nlet draft = {};\ntry { draft = JSON.parse(raw); } catch(e) { draft = { subject:'', body:'' }; }\n\nconst subject = draft.subject || 'Quick idea for your practice';\nconst body = draft.body || '';\nconst full = `Subject: ${subject}\\n\\n${body}`;\n\nconst a = $('Decide Email Framing').item.json.analysis || {};\nconst notes = a.practice_summary || '';\nconst weak = (a.observations || []).map(o => `\u2022 ${o.area}: ${o.finding}`).join('\\n');\n\nreturn [{ json: { ...lead, ScrapedNotes: notes, WeakPoints: weak, DraftEmail: full, Status: 'drafted' } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "a4cd4c9d-6219-4724-a693-930e635387d0",
      "name": "Update Draft in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        7568,
        2064
      ],
      "parameters": {
        "columns": {
          "value": {
            "Email": "={{ $json.Email }}",
            "Status": "={{ $json.Status }}",
            "DraftEmail": "={{ $json.DraftEmail }}",
            "WeakPoints": "={{ $json.WeakPoints }}",
            "ScrapedNotes": "={{ $json.ScrapedNotes }}"
          },
          "schema": [
            {
              "id": "Email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "FirstName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "FirstName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LastName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "LastName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Domain",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Domain",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WebsiteURL",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "WebsiteURL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ScrapedNotes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "ScrapedNotes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WeakPoints",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "WeakPoints",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "DraftEmail",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "DraftEmail",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "SentAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "SentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ThreadId",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ThreadId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Replied",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Replied",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Stage",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Stage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Followup1SentAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Followup1SentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Followup2SentAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Followup2SentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email1_Content",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Email1_Content",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email2_Followup1_Content",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Email2_Followup1_Content",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email3_Followup2_Content",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Email3_Followup2_Content",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Email"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1794109238,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0/edit#gid=1794109238",
          "cachedResultName": "Independent"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0/edit?usp=drivesdk",
          "cachedResultName": "leads_workflow_ready_v3"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "08dcf9cd-2b55-44c2-94bf-c1ac8b0f5d06",
      "name": "Wait 8 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        7776,
        2064
      ],
      "parameters": {
        "amount": 8
      },
      "typeVersion": 1.1
    },
    {
      "id": "7395490d-854e-4df6-beed-aca572c70ed8",
      "name": "Update Failed Scrape Status",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        6528,
        2560
      ],
      "parameters": {
        "columns": {
          "value": {
            "Email": "={{ $('Loop Through Leads').item.json.Email }}",
            "Status": "scrape_failed"
          },
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Email"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1794109238,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0/edit#gid=1794109238",
          "cachedResultName": "Independent"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uZZtQNWaM6UV0vEtDXLUB-7D7cjGJhNRYLtZuaCtJT0/edit?usp=drivesdk",
          "cachedResultName": "leads_workflow_ready_v3"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "7209d35e-598a-414b-a903-2c211cf40f29",
      "name": "Execute Apify Actor",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        5408,
        2048
      ],
      "parameters": {
        "memory": 4096,
        "actorId": {
          "__rl": true,
          "mode": "list",
          "value": "aYG0l9s7dbB7j3gbS",
          "cachedResultUrl": "https://console.apify.com/actors/aYG0l9s7dbB7j3gbS/input",
          "cachedResultName": "Website Content Crawler (apify/website-content-crawler)"
        },
        "timeout": {},
        "customBody": "={\n    \"startUrls\": [\n        {\n            \"url\": \"{{ $json.WebsiteURL }}\"\n        }\n    ],\n    \"maxCrawlPages\": 4,\n    \"maxResults\": 4,\n    \"maxCrawlDepth\": 1,\n    \"crawlerType\": \"playwright:adaptive\",\n    \"saveMarkdown\": true,\n    \"blockMedia\": true,\n    \"removeCookieWarnings\": true,\n    \"proxyConfiguration\": {\n        \"useApifyProxy\": true\n    },\n    \"removeElementsCssSelector\": \"nav, footer, script, style, noscript, svg, img[src^='data:']\",\n    \"readableTextCharThreshold\": 100,\n    \"respectRobotsTxtFile\": true\n}",
        "actorSource": "store",
        "authentication": "apifyOAuth2Api"
      },
      "credentials": {
        "apifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "877e322b-456e-4c7b-99ae-09296a6ae8f8",
      "name": "Fetch Dataset Items",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        5568,
        2048
      ],
      "parameters": {
        "offset": {},
        "options": {},
        "resource": "Datasets",
        "datasetId": "={{ $json.defaultDatasetId }}",
        "authentication": "apifyOAuth2Api"
      },
      "credentials": {
        "apifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "ff3d96d9-c21a-49ec-953e-246b9b57c47f",
      "name": "Send to OpenAI Model",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        6336,
        2064
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "=You are a web/conversion consultant who reviews US medical & dental practice websites for Jainam, a freelance developer who builds AI phone-call assistants, website chatbots, and appointment-booking automation for clinics.\n\nYour job: read the scraped website text and find 2-3 CONCRETE, factual gaps where one of Jainam's services would genuinely help the practice get and serve more patients. Focus especially on:\n- How patients currently book (phone-only? printable PDF form? no online booking?)\n- Whether there is any chatbot / live chat / instant answer for visitor questions\n- After-hours / missed-call handling\n- New-patient intake (manual paper forms vs automated)\n- Anything that creates phone-tag or front-desk load\n\nGround EVERY observation strictly in the provided text. Never invent features the text doesn't mention. If the site genuinely looks modern with online booking already, say so honestly and pick a softer angle.\n\nOutput STRICT JSON only \u2014 no markdown, no backticks:\n{\n  \"practice_summary\": \"one sentence on what this practice does and who runs it\",\n  \"observations\": [\n    {\"area\": \"e.g. booking / chatbot / after-hours / intake\", \"finding\": \"specific factual observation from the text\", \"opportunity\": \"which of Jainam's services (AI call assistant, website chatbot, booking automation, intake automation) would help and the patient/staff benefit\"}\n  ],\n  \"top_angle\": \"the single strongest service to pitch \u2014 one of: AI phone-call assistant, website AI chatbot, online appointment booking, patient intake automation\"\n}\nRules: max 3 observations. Be specific to THIS practice, not generic."
            },
            {
              "content": "=Practice: {{ $('Loop Through Leads').item.json.FirstName }} {{ $('Loop Through Leads').item.json.LastName }} \u2014 {{ $('Loop Through Leads').item.json.Domain }}\n\nWebsite content (scraped):\n\"\"\"\n{{ $json.siteText }}\n\"\"\"\n\nAnalyze and return the STRICT JSON described in your instructions."
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b218f1c5-df3e-45d2-a2af-74f6aa0925c2",
      "name": "Draft Email with OpenAI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        6960,
        2064
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "=You write short, human cold outreach emails for Jainam Jain, a freelance developer who builds AI phone-call assistants, website chatbots, and appointment-booking automation specifically for medical and dental practices.\n\nPortfolio: jainam.dev\nCalendly: https://calendly.com/jainamjain742/ai-automation\n\nAudience: US medical/dental practice owners (busy doctors). Goal: book a 15-minute call.\n\nRules:\n- Under 130 words. Plain, warm, human \u2014 like a real person who actually looked at their website.\n- Reference ONE concrete observation from the analysis (their actual booking/intake/after-hours gap).\n- Pitch the single most relevant service (AI call assistant, chatbot, or online booking) in terms of patient/staff BENEFIT, never tech jargon. Never say \"LangChain\", \"API\", \"n8n\" etc.\n- Never invent fake stats or fake client names.\n- Always end with the Calendly link.\n- Always end with this exact unsubscribe line as the final paragraph: \"Not relevant? Reply 'unsubscribe' and I won't reach out again.\"\n\nOutput STRICT JSON only, no markdown, no backticks:\n{\"subject\":\"...\",\"body\":\"...\"}\nThe body must be plain text with line breaks written as \\n."
            },
            {
              "content": "=Recipient: Dr. {{ $json.LastName }} at {{ $json.Domain }}\nFraming style: {{ $json.framing }}  (soft = frame the gap gently as an \"opportunity I noticed\", warm and respectful; direct = name the gap more plainly but still polite \u2014 never insulting)\n\nAnalysis JSON:\n{{ $json.analysisJson }}\n\nWrite the email using top_angle and the single most relevant observation. Make it specific to THIS practice, not generic.\n\nSign exactly as:\nJainam Jain\nAI Automation Developer\njainam.dev"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "00d20b04-2d52-497f-aec0-cbdba3065fbc",
  "nodeGroups": [],
  "connections": {
    "Start Manually": {
      "main": [
        [
          {
            "node": "Read Leads from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 8 Seconds": {
      "main": [
        [
          {
            "node": "Loop Through Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Through Leads": {
      "main": [
        [],
        [
          {
            "node": "Execute Apify Actor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Apify Actor": {
      "main": [
        [
          {
            "node": "Fetch Dataset Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Dataset Items": {
      "main": [
        [
          {
            "node": "Combine Scraped Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Scrape Success": {
      "main": [
        [
          {
            "node": "Send to OpenAI Model",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Failed Scrape Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Scraped Text": {
      "main": [
        [
          {
            "node": "Check Scrape Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decide Email Framing": {
      "main": [
        [
          {
            "node": "Draft Email with OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Pending Leads": {
      "main": [
        [
          {
            "node": "Loop Through Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to OpenAI Model": {
      "main": [
        [
          {
            "node": "Decide Email Framing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Leads from Sheets": {
      "main": [
        [
          {
            "node": "Filter Pending Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Draft in Sheets": {
      "main": [
        [
          {
            "node": "Wait 8 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Email with OpenAI": {
      "main": [
        [
          {
            "node": "Prepare Email Draft Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Email Draft Row": {
      "main": [
        [
          {
            "node": "Update Draft in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Failed Scrape Status": {
      "main": [
        [
          {
            "node": "Loop Through Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}