{
  "name": "AllHabesha - Version Upgrade Outreach",
  "active": false,
  "nodes": [
    {
      "parameters": {
        "content": "AllHabesha Version Upgrade Outreach\n\nPurpose:\n- Find old app installs from the contacts sheet.\n- Send one upgrade message per eligible user.\n- Wait a random 1 to 9 minutes before each send.\n- Mark the sheet after each send so the same user is not reprocessed.\n\nExpected source fields:\n- email\n- approved_to_contact\n- consent_email\n- unsubscribed\n- app_version or last_seen_app_version or version or installed_version\n\nNotes:\n- Email path is active.\n- SMS can be added later as a separate branch after consent is confirmed.\n- Keep sender as support@digitalnebi.com.\n- Use the live native Google Sheet, not the old XLSX file.\n- Do not send to users whose version is missing or already on the minimum supported release.",
        "height": 420,
        "width": 520,
        "color": 4
      },
      "id": "upgrade-sticky",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1040,
        -520
      ]
    },
    {
      "parameters": {},
      "id": "upgrade-manual-trigger",
      "name": "Run Now - Upgrade Sweep",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -960,
        -40
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1
            }
          ]
        }
      },
      "id": "upgrade-schedule-trigger",
      "name": "Every 1 Day - Upgrade Sweep",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -960,
        160
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "campaign-name",
              "name": "campaignName",
              "type": "string",
              "value": "AllHabesha Version Upgrade"
            },
            {
              "id": "sheet-url",
              "name": "googleSheetUrl",
              "type": "string",
              "value": "https://docs.google.com/spreadsheets/d/10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ/edit?gid=0#gid=0"
            },
            {
              "id": "sheet-id",
              "name": "googleSheetDocumentId",
              "type": "string",
              "value": "10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ"
            },
            {
              "id": "sheet-tab",
              "name": "googleSheetTab",
              "type": "string",
              "value": "Contacts"
            },
            {
              "id": "min-version",
              "name": "minimumSupportedVersion",
              "type": "string",
              "value": "1.0.4"
            },
            {
              "id": "min-delay",
              "name": "minRandomDelayMinutes",
              "type": "number",
              "value": 1
            },
            {
              "id": "max-delay",
              "name": "maxRandomDelayMinutes",
              "type": "number",
              "value": 9
            },
            {
              "id": "email-from",
              "name": "emailFrom",
              "type": "string",
              "value": "support@digitalnebi.com"
            },
            {
              "id": "app-store-url",
              "name": "appStoreUrl",
              "type": "string",
              "value": "https://apps.apple.com/app/id6757008682"
            },
            {
              "id": "play-store-url",
              "name": "playStoreUrl",
              "type": "string",
              "value": "https://play.google.com/store/apps/details?id=com.digitalnebi.allhabesha"
            },
            {
              "id": "upgrade-url",
              "name": "upgradeHelpUrl",
              "type": "string",
              "value": "https://www.digitalnebi.com/email-preferences"
            }
          ]
        },
        "options": {}
      },
      "id": "upgrade-config",
      "name": "Upgrade Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -640,
        60
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ",
          "mode": "list",
          "cachedResultName": "AllHabesha Outreach Public Business Contacts Native",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ/edit?gid=0#gid=0"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Contacts",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ/edit?gid=0#gid=0"
        },
        "filtersUI": {
          "values": []
        },
        "options": {
          "returnAll": true
        }
      },
      "id": "upgrade-sheet-reader",
      "name": "Read Upgrade Candidates From Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        -320,
        60
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "AllHabesha Outreach Google API Service Account",
          "name": "AllHabesha Outreach Google API Service Account"
        }
      },
      "notes": "Reads the live contacts sheet."
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nconst now = new Date();\nconst norm = v => String(v ?? '').trim().toLowerCase();\nconst yes = v => ['yes','y','true','1','checked','approved','opted in','opted-in'].includes(norm(v));\nfunction parseVersion(value) {\n  const text = String(value ?? '').trim();\n  const parts = text.match(/\\d+/g);\n  if (!parts || !parts.length) return null;\n  return parts.slice(0, 3).map(n => Number(n));\n}\nfunction compareVersions(a, b) {\n  const len = Math.max(a.length, b.length);\n  for (let i = 0; i < len; i++) {\n    const left = a[i] ?? 0;\n    const right = b[i] ?? 0;\n    if (left !== right) return left - right;\n  }\n  return 0;\n}\nconst minVersionRaw = String(rows[0]?.minimumSupportedVersion || '1.0.4').trim();\nconst minVersion = parseVersion(minVersionRaw) || [1, 0, 4];\nconst out = [];\nfor (const row of rows) {\n  const email = String(row.email || '').trim();\n  const phone = String(row.phone || '').trim();\n  const approved = yes(row.approved_to_contact);\n  const unsubscribed = yes(row.unsubscribed || row.unsubscribe_status);\n  const consentEmail = yes(row.consent_email || row.email_consent || row.consent);\n  const consentSms = yes(row.consent_sms || row.sms_consent);\n  const versionRaw = row.app_version || row.last_seen_app_version || row.version || row.installed_version || '';\n  const version = parseVersion(versionRaw);\n  const versionIsOld = !!version && compareVersions(version, minVersion) < 0;\n  const selectedChannel = email ? 'email' : phone ? 'sms' : '';\n  const consentOk = selectedChannel === 'sms' ? consentSms : consentEmail;\n  const eligible = approved && consentOk && !unsubscribed && versionIsOld && !!selectedChannel;\n  const delayMinutes = Math.floor(Math.random() * (Number(row.maxRandomDelayMinutes || 9) - Number(row.minRandomDelayMinutes || 1) + 1)) + Number(row.minRandomDelayMinutes || 1);\n  const nextContactAt = new Date(now.getTime() + delayMinutes * 60000).toISOString();\n  out.push({\n    json: {\n      ...row,\n      evaluated_at: now.toISOString(),\n      minimumSupportedVersion: minVersionRaw,\n      detected_app_version: versionRaw,\n      version_is_old: versionIsOld,\n      selected_channel: selectedChannel,\n      destination: selectedChannel === 'email' ? email : phone,\n      recipient_email: selectedChannel === 'email' ? email : '',\n      eligible_to_queue: eligible,\n      not_eligible_reason: eligible ? '' : [\n        approved ? '' : 'not approved_to_contact',\n        consentOk ? '' : `missing ${selectedChannel || 'email'} consent`,\n        unsubscribed ? 'unsubscribed' : '',\n        versionIsOld ? '' : 'app version already current or missing',\n        selectedChannel ? '' : 'missing destination'\n      ].filter(Boolean).join('; '),\n      planned_random_delay_minutes: delayMinutes,\n      planned_next_contact_at: nextContactAt,\n      emailSubject: `Update your AllHabesha app to keep receiving the latest features`,\n      emailHtml: `<p>Your current AllHabesha app version is older than the latest release.</p><p>Please update to the newest version to keep getting push notifications and the latest features.</p><p><a href=\"https://apps.apple.com/app/id6757008682\">Update on App Store</a> | <a href=\"https://play.google.com/store/apps/details?id=com.digitalnebi.allhabesha\">Update on Google Play</a></p><p><a href=\"https://www.digitalnebi.com/email-preferences\">Manage preferences</a></p>`\n    }\n  });\n}\nreturn out;"
      },
      "id": "upgrade-build-queue",
      "name": "Filter Old Version Users + Build Queue",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        20,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nconst eligible = rows.filter(r => r.eligible_to_queue);\nconst blocked = rows.filter(r => !r.eligible_to_queue);\nreturn [{ json: {\n  title: 'AllHabesha Version Upgrade Status',\n  generated_at: new Date().toISOString(),\n  minimum_supported_version: rows[0]?.minimumSupportedVersion || '1.0.4',\n  total_rows_checked: rows.length,\n  eligible_to_queue: eligible.length,\n  blocked_or_waiting: blocked.length,\n  next_send_preview: eligible.slice(0, 5).map(r => ({\n    rowNumber: r.rowNumber,\n    name: r.name,\n    email: r.email,\n    selected_channel: r.selected_channel,\n    detected_app_version: r.detected_app_version,\n    planned_next_contact_at: r.planned_next_contact_at\n  })),\n  blocked_preview: blocked.slice(0, 10).map(r => ({\n    rowNumber: r.rowNumber,\n    name: r.name,\n    reason: r.not_eligible_reason\n  })),\n  read_this_first: 'Only users whose app version is below the minimum supported version and who are approved_to_contact are eligible.'\n}}];"
      },
      "id": "upgrade-status",
      "name": "READ ME - Upgrade Status",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        340,
        -160
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.all().filter(item => item.json.eligible_to_queue).map(item => ({ json: item.json }));"
      },
      "id": "upgrade-eligible",
      "name": "Eligible Upgrade Queue",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        340,
        60
      ]
    },
    {
      "parameters": {
        "batchSize": 1
      },
      "id": "upgrade-split",
      "name": "Split In Batches - Upgrade Queue",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        660,
        60
      ]
    },
    {
      "parameters": {
        "amount": "={{ $json.planned_random_delay_minutes || 1 }}",
        "unit": "minutes"
      },
      "id": "upgrade-wait",
      "name": "WAITING - Random Delay / Quiet Hours",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        980,
        -40
      ]
    },
    {
      "parameters": {
        "jsCode": "const email = String($json.recipient_email || $json.email || '').trim();\nif (!email || !email.includes('@')) {\n  throw new Error('No recipients defined');\n}\nreturn [{ json: { ...$json, recipient_email: email } }];"
      },
      "id": "upgrade-validate",
      "name": "Validate Upgrade Recipient",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        -40
      ]
    },
    {
      "parameters": {
        "operation": "send",
        "fromEmail": "support@digitalnebi.com",
        "toEmail": "={{ $json.recipient_email || $json.email || '' }}",
        "subject": "={{ $json.emailSubject || 'Update your AllHabesha app to keep receiving the latest features' }}",
        "emailFormat": "html",
        "html": "={{ $json.emailHtml || `<p>Your current AllHabesha app version is older than the latest release.</p><p>Please update to the newest version to keep getting push notifications and the latest features.</p><p><a href=\"https://apps.apple.com/app/id6757008682\">Update on App Store</a> | <a href=\"https://play.google.com/store/apps/details?id=com.digitalnebi.allhabesha\">Update on Google Play</a></p><p><a href=\"https://www.digitalnebi.com/email-preferences\">Manage preferences</a></p>` }}",
        "options": {}
      },
      "id": "upgrade-send-email",
      "name": "Send Upgrade Email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2,
      "position": [
        1600,
        -40
      ],
      "credentials": {
        "smtp": {
          "id": "SMTP account",
          "name": "SMTP account"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ",
          "mode": "list",
          "cachedResultName": "AllHabesha Outreach Public Business Contacts Native",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ/edit?gid=0#gid=0"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Contacts",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/10EMI2j-ZgKGOePVqxOA2WhhQeOldH4IvYLPn25QhOiQ/edit?gid=0#gid=0"
        },
        "mappingMode": "defineBelow",
        "matchingColumns": [
          "email"
        ],
        "columns": {
          "value": {
            "outreach_status": "={{ `sent_upgrade_${new Date().toLocaleString('en-US', { month: 'long', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true }).toLowerCase()}` }}",
            "last_contacted_at": "={{ new Date().toISOString() }}",
            "next_contact_at": "={{ $('Validate Upgrade Recipient').item.json.planned_next_contact_at || '' }}",
            "notes": "={{ $('Validate Upgrade Recipient').item.json.notes || '' }}",
            "email": "={{ $('Validate Upgrade Recipient').item.json.email || $('Validate Upgrade Recipient').item.json.recipient_email || $('Validate Upgrade Recipient').item.json.destination || '' }}"
          },
          "matchingColumns": [
            "email"
          ]
        },
        "options": {}
      },
      "id": "upgrade-update-sheet",
      "name": "WAITING - Update Upgrade Sheet Status",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1920,
        -40
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "AllHabesha Outreach Google API Service Account",
          "name": "AllHabesha Outreach Google API Service Account"
        }
      },
      "notes": "Marks the contacted user in the live sheet."
    }
  ],
  "connections": {
    "Run Now - Upgrade Sweep": {
      "main": [
        [
          {
            "node": "Upgrade Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 1 Day - Upgrade Sweep": {
      "main": [
        [
          {
            "node": "Upgrade Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upgrade Config": {
      "main": [
        [
          {
            "node": "Read Upgrade Candidates From Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Upgrade Candidates From Google Sheet": {
      "main": [
        [
          {
            "node": "Filter Old Version Users + Build Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Old Version Users + Build Queue": {
      "main": [
        [
          {
            "node": "READ ME - Upgrade Status",
            "type": "main",
            "index": 0
          },
          {
            "node": "Eligible Upgrade Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Eligible Upgrade Queue": {
      "main": [
        [
          {
            "node": "Split In Batches - Upgrade Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split In Batches - Upgrade Queue": {
      "main": [
        [
          {
            "node": "WAITING - Random Delay / Quiet Hours",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "WAITING - Random Delay / Quiet Hours": {
      "main": [
        [
          {
            "node": "Validate Upgrade Recipient",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Upgrade Recipient": {
      "main": [
        [
          {
            "node": "Send Upgrade Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Upgrade Email": {
      "main": [
        [
          {
            "node": "WAITING - Update Upgrade Sheet Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "WAITING - Update Upgrade Sheet Status": {
      "main": [
        [
          {
            "node": "Split In Batches - Upgrade Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {},
  "versionId": "1"
}
