{
  "id": "agTxDgU4w3WTcu1u",
  "name": "Business Cards to Qualified Connections \u2014 AI-Powered Follow-up Automation",
  "tags": [],
  "nodes": [
    {
      "id": "df6e276e-8dcd-40e7-8c9a-9286cfa9d65f",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -1920,
        80
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {
          "download": true
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "27da1a38-95f6-4d9c-a3b6-62df4a013411",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1088,
        80
      ],
      "parameters": {
        "url": "https://vision.googleapis.com/v1/images:annotate?key=YOUR_TOKEN_HERE",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"requests\": [\n    {\n      \"image\": {\n        \"content\": \"{{ $json.base64 }}\"\n      },\n      \"features\": [\n        {\n          \"type\": \"TEXT_DETECTION\"\n        }\n      ]\n    }\n  ]\n}\n",
        "sendBody": true,
        "jsonHeaders": "{\n  \"Content-Type\": \"application/json\"\n}\n",
        "sendHeaders": true,
        "specifyBody": "json",
        "specifyHeaders": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "c5dc2e3a-4451-4c8a-bbc1-b10b9f790dd2",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -336,
        64
      ],
      "parameters": {
        "text": "=Here is the raw OCR text extracted from a business card:\n{{ $json.raw_text }}\n\nHere is the caption or context message (what the user said when sending the card):\n{{ $('Telegram Trigger').item.json.message.caption }}\n\nMy details (use these in the message footer or when signing off):\n- Name: Aditya Malur\n- Title: AI Powered Mentor & Consultant\n- Email: hi@adityamalur.com\n- Phone: +91 8088062928\n- LinkedIn: https://www.linkedin.com/in/aditya-malur/\n- Calendly: https://calendly.com/aditya_malur/1-1\n- Positioning: I help startups and businesses achieve product-market fit faster and create revenue efficiencies through Agentic AI Automations and GTM/Product strategies.\n\nTasks:\n1. Extract structured fields:\n   - name\n   - company\n   - role\n   - emails (array, handle multiple)\n   - phones (array, handle multiple, include country codes if visible or infer)\n   - country (detected from phone, address, or domain)\n   - website (if any)\n2. Infer company type and industry from name/domain/web context.\n3. Write a one-line internal summary: who they are, what the company does, why relevant.\n4. Generate 3\u20135 specific automation or AI opportunities relevant to their business (short bullet points).\n5. Score fit (1\u201310) and explain reasoning briefly.\n6. Create:\n   - subject: one-line personalized email subject.\n   - htmlBody: short personalized HTML email:\n       - Start with a natural greeting addressing the person by first name.\n       - Mention the company within the first 2 sentences.\n       - Reference their likely role and industry context.\n       - Transition naturally into the potential benefits of AI/automation or your consulting.\n       - Include warm, professional closing with your signature (details above).\n   - whatsapp_draft: short conversational follow-up (<200 chars).\n7. Write next_actions: 3 concrete internal steps.\n8. Add opportunities_summary: one-paragraph internal note explaining why the opportunities are relevant.\n9. Ensure all fields are valid JSON and match this exact schema:\n\n{\n  \"name\": \"\",\n  \"company\": \"\",\n  \"role\": \"\",\n  \"emails\": [],\n  \"phones\": [],\n  \"country\": \"\",\n  \"website\": \"\",\n  \"one_line_summary\": \"\",\n  \"opportunities\": [\"\", \"\", \"\"],\n  \"opportunities_summary\": \"\",\n  \"fit_score\": 0,\n  \"fit_reason\": \"\",\n  \"subject\": \"\",\n  \"htmlBody\": \"\",\n  \"whatsapp_draft\": \"\",\n  \"next_actions\": [\"\", \"\", \"\"]\n}\n",
        "options": {
          "systemMessage": "You are a professional B2B Sales & Outreach assistant with real-world consulting experience. \nYou must output strict JSON only \u2014 no markdown, no text outside JSON.\nBe factual, concise, structured, and persuasive.\nIf any field is missing, leave it as an empty string or empty array ([]).\nDetect country automatically from phone codes, address, or web context.\nUse the user-provided details for signing off emails and WhatsApp messages.\nAlways start emails with a natural greeting addressing the person by first name (Dear/Hi/Hello <First Name>) and reference the company naturally within the first 2 sentences.\nEnsure the tone is warm, credible, and human \u2014 avoid generic AI-sounding text.\nInclude internal analysis for opportunities, fit, and follow-up steps.\nOutput must strictly follow the JSON schema provided by the USER."
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "3a8fdea7-b8e7-4725-8e14-c001e38aa99e",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -336,
        224
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "147ed8cd-c647-45bd-9507-1f49e9f7b027",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1344,
        48
      ],
      "parameters": {
        "sendTo": "=hi@adityamalur.com",
        "message": "={{ $json['Email Body'] }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $json['Email Subject'] }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2d85f83b-9f66-45c6-b9d5-e53f06406bf1",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        608,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "05e1b262-b993-42e6-ac43-8b5075a399bd",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.emails[0] }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c0d65c52-a40c-414b-a0cd-e5975faa26b1",
      "name": "Append row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1008,
        48
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $json.name }}",
            "Email": "={{ $json.emails[0] }}",
            "Country": "={{ $json.country }}",
            "Website": "={{ $json.website }}",
            "Fit Score": "={{ $json.fit_score }}",
            "Email Body": "={{ $json.htmlBody }}",
            "Fit Reason": "={{ $json.fit_reason }}",
            "User Input": "={{ $('Telegram Trigger').item.json.message.caption }}",
            "Company Name": "={{ $json.company }}",
            "Next Actions": "={{ $json.next_actions[0] }}\n\n{{ $json.next_actions[1] }}\n\n{{ $json.next_actions[2] }}",
            "Phone Number": "={{ $json.phones[0] }}",
            "Email Subject": "={{ $json.subject }}",
            "Opportunities": "={{ $json.opportunities[0] }}\n\n{{ $json.opportunities[1] }}\n\n{{ $json.opportunities[2] }}\n\n{{ $json.opportunities[3] }}\n\n{{ $json.opportunities[4] }}",
            "Position/Role": "={{ $json.role }}",
            "Alternate Email": "={{ $json.emails[1] }}",
            "Whatsapp Message": "={{ $json.whatsapp_draft }}",
            "Opportunities Summary": "={{ $json.opportunities_summary }}",
            "Alternate Phone Number": "={{ $json.phones[1] }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": "gid=0",
        "documentId": "YOUR_GOOGLE_SHEET_ID"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "69c687f0-f3e5-427e-b69a-509dc1ec8186",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2464,
        -112
      ],
      "parameters": {
        "width": 4400,
        "height": 1200,
        "content": "## Business Cards to Qualified Connections (AI-Powered Follow-up Automation)\n\nHow It Flows:\n\nYou send a business card photo to your Telegram bot.\n\nThe image gets converted to Base64.\n\nGoogle Vision reads the text.\n\nAI extracts details & drafts a personalized email.\n\nData gets cleaned, validated, and checked.\n\nEverything is saved to Google Sheets.\n\n(Optional) Email copy gets sent to you."
      },
      "typeVersion": 1
    },
    {
      "id": "33414e1f-43d0-4bcf-8872-aeefc31f5aa2",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1984,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Telegram Trigger\n\nWhat it does: Starts the workflow when someone sends a photo or message on Telegram.\n\nSetup:\n\nConnect your Telegram bot (you\u2019ll get a bot token from BotFather).\n\nChoose Update Type: message.\n\nTurn on Download: \u2705 (so image files are included).\n\nOnce saved, n8n gives you a webhook URL \u2014 connect it to your Telegram bot via /setWebhook"
      },
      "typeVersion": 1
    },
    {
      "id": "6477c0c3-58b0-4ecc-a443-8c4a3e6d3d6b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1584,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Convert Image to Base64\n\nWhat it does: Converts the received business card image into Base64 format so the next node (Google Vision) can read it.\n\nSetup:\n\nNo credentials needed.\n\nJust copy the code given \u2014 it handles conversion automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "85daabac-f793-4929-b51b-bbeec76dfd68",
      "name": "Convert Image to Base64",
      "type": "n8n-nodes-base.code",
      "position": [
        -1472,
        80
      ],
      "parameters": {
        "jsCode": "// n8n stores binary files in items[0].binary\nconst binaryData = items[0].binary.data; // do NOT use .data.data\nconst base64Image = binaryData.data.toString('base64');\n\nitems[0].json.base64 = base64Image;\nreturn items;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bfb600a5-0d7d-470c-abb1-40e468567213",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1184,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Google Vision OCR\n\nWhat it does: Sends the Base64 image to Google Vision API to extract the text (names, emails, etc.).\n\nSetup:\n\nURL: https://vision.googleapis.com/v1/images:annotate?key=YOUR_API_KEY\n\nMethod: POST\n\nAdd your Google Vision API key.\n\nSet Body to the JSON shown in the code (already ready for copy-paste).\n\nHeaders: Content-Type: application/json"
      },
      "typeVersion": 1
    },
    {
      "id": "bba3ec95-5482-40d5-ab3d-413c5368deb5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Extract Raw Text\nWhat it does: Picks only the plain text result from the OCR response.\n\nSetup:\n\nNo config needed \u2014 just passes clean text (raw_text) to the AI Agent."
      },
      "typeVersion": 1
    },
    {
      "id": "4e720a9f-d890-4d50-9daf-a2c25dcdc18c",
      "name": "Extract Raw Text)",
      "type": "n8n-nodes-base.code",
      "position": [
        -688,
        80
      ],
      "parameters": {
        "jsCode": "// Get OCR text from Google Vision HTTP Request output\nconst rawText = $json.responses?.[0]?.fullTextAnnotation?.text || \"\";\n\n// Pass it along to OpenAI\nreturn [{ json: { raw_text: rawText } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b536615c-9b92-4e2e-a35b-ce2a54f0f8e1",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        368
      ],
      "parameters": {
        "color": 4,
        "width": 416,
        "height": 592,
        "content": "## AI Agent\nWhat it does:\nThis is where the magic happens. It takes:\n\nOCR text from the card\n\nCaption (what you typed in Telegram)\n\nYour personal details\nand asks the AI to structure info (name, company, email, phone, etc.) and generate:\n\na personalized email,\n\na WhatsApp follow-up,\n\nautomation ideas,\n\nand next actions.\n\nSetup:\n\nAdd your OpenAI API key.\n\nChoose Language Model: OpenAI Chat Model (connected below).\n\nPaste the detailed prompt already written (don\u2019t edit unless you know what you're doing)."
      },
      "typeVersion": 1
    },
    {
      "id": "aa11be33-ed3a-4233-ad0c-f6d3e40a372e",
      "name": "Clean AI Output",
      "type": "n8n-nodes-base.code",
      "position": [
        272,
        64
      ],
      "parameters": {
        "jsCode": "// Step 1: Get AI Output (stringify if needed)\nlet aiOutput = $json.output;\nif (typeof aiOutput !== 'string') {\n  aiOutput = JSON.stringify(aiOutput);\n}\n\n// Step 2: Clean Markdown or code fences\naiOutput = aiOutput\n  .replace(/^```json\\s*/i, '')\n  .replace(/^```/, '')\n  .replace(/```$/, '')\n  .trim();\n\n// Step 3: Try parsing JSON\nlet parsed;\ntry {\n  parsed = JSON.parse(aiOutput);\n} catch (error) {\n  throw new Error('\u274c Failed to parse AI output. Check format.\\n\\nOutput received:\\n' + aiOutput);\n}\n\n// Step 4: Handle array or single object\nconst data = Array.isArray(parsed) ? parsed[0] : parsed;\n\n// Step 5: Extract subject & htmlBody (fallback if only email_draft present)\nlet subject = data.subject || '';\nlet htmlBody = data.htmlBody || '';\n\nif (!subject && data.email_draft) {\n  const match = data.email_draft.match(/^Subject:\\s*(.+?)\\n\\n([\\s\\S]*)/i);\n  if (match) {\n    subject = match[1].trim();\n    htmlBody = match[2].replace(/\\n/g, '<br>').trim();\n  } else {\n    htmlBody = data.email_draft.replace(/\\n/g, '<br>').trim();\n  }\n}\n\n// Step 6: Normalize arrays and optional fields\nconst emails = (Array.isArray(data.emails) ? data.emails : data.email ? [data.email] : []).map(e => e.trim());\nconst phones = (Array.isArray(data.phones) ? data.phones : data.phone ? [data.phone] : []).map(p => {\n  if (!p) return '';\n  let cleaned = p.replace(/[\\s\\-]+/g, '');\n  return \"'\" + cleaned;\n});\n\n// Step 7: Build clean, consistent output\nreturn [{\n  json: {\n    name: data.name || '',\n    company: data.company || '',\n    role: data.role || '',\n    emails,\n    phones,\n    country: data.country || '',\n    website: data.website || '',\n    one_line_summary: data.one_line_summary || '',\n    opportunities: data.opportunities || [],\n    opportunities_summary: data.opportunities_summary || '',\n    fit_score: data.fit_score || 0,\n    fit_reason: data.fit_reason || '',\n    subject,\n    htmlBody,\n    whatsapp_draft: data.whatsapp_draft || '',\n    next_actions: data.next_actions || []\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "76dc424d-c755-413c-bcb5-2163ee5f6724",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Clean AI Output\nWhat it does:\n\nParses and cleans the AI JSON output.\n\nRemoves formatting or markdown if any.\n\nMakes sure all data is in proper fields before saving or sending.\n\nSetup:\n\nNo manual setup. Just make sure your AI Agent outputs JSON only.doing)."
      },
      "typeVersion": 1
    },
    {
      "id": "e44ff433-3089-4e5d-b74b-29ceef7b1a00",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        272
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Check for Email Found\nWhat it does:\nChecks if the AI extracted at least one valid email address.\n\nSetup:\n\nLeft value: {{$json.emails[0]}}\n\nCondition: \u201cIs not empty\u201d."
      },
      "typeVersion": 1
    },
    {
      "id": "a3b64bf5-4b32-460c-b85d-343b7e35b5a4",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        272
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Append Row in Sheet (Google Sheets)\nWhat it does:\nSaves all extracted info and AI-generated content (emails, phone, company, etc.) to a Google Sheet.\n\nSetup:\n\nConnect your Google account.\n\nPaste your Sheet ID (YOUR_GOOGLE_SHEET_ID).\n\nMap all columns as shown in the JSON.\n\nSheet name: Sheet1 (or use your sheet\u2019s name)."
      },
      "typeVersion": 1
    },
    {
      "id": "9971c4e0-4614-4d99-9abd-e9032feabe45",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        272
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 432,
        "content": "## Send a Message (Gmail)\nWhat it does:\nOptionally sends you a copy of the generated email.\n\nSetup:\n\nConnect Gmail.\n\n\u201cSend To\u201d: your email.\n\nSubject: {{$json['Email Subject']}}\n\nMessage: {{$json['Email Body']}}"
      },
      "typeVersion": 1
    },
    {
      "id": "6248f88c-4dda-4fc5-8a66-2d9105043865",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        960
      ],
      "parameters": {
        "color": 6,
        "width": 384,
        "height": 80,
        "content": "## Created with <3 by Aditya Malur\nLinkedIn: https://www.linkedin.com/in/aditya-malur/"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "7ce9f404-08d8-43de-b534-dc645ec6e814",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Append row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Clean AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Extract Raw Text)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean AI Output": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Convert Image to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Raw Text)": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Append row in sheet": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Image to Base64": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}