{
  "id": "rauEsTkyr7CZDotc",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Klaviyo List Decay Detection Deploy",
  "tags": [],
  "nodes": [
    {
      "id": "afc4935d-c2e3-4647-af95-142d059731c3",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        -576
      ],
      "parameters": {
        "color": 7,
        "width": 728,
        "height": 478,
        "content": "## \ud83d\udcca Klaviyo List Decay Detection\n\nAutomatically monitors your Klaviyo audience health by scoring every profile based on their last engagement date. Critical profiles (90+ days inactive) are bulk-suppressed before they damage your sender reputation.\n\n**How it works**\n1. Runs every night at 2 AM on a cron schedule\n2. Fetches all profiles from the Klaviyo API (paginated)\n3. Scores and classifies each profile into a decay tier\n4. Logs all profiles to Postgres for historical tracking\n5. Auto-suppresses critical profiles via Klaviyo's Bulk Suppression API\n6. Emails a styled HTML decay report\n\n**Decay Tiers**\n\ud83d\udfe2 **Active** \u2014 < 30 days \u00b7 No action\n\ud83d\udfe1 **Moderate Decay** \u2014 30\u201360 days \u00b7 Logged only\n\ud83d\udfe0 **High Decay** \u2014 60\u201390 days \u00b7 Logged only\n\ud83d\udd34 **Critical** \u2014 90+ days \u00b7 Auto-suppressed in Klaviyo"
      },
      "typeVersion": 1
    },
    {
      "id": "1e0c9c94-0577-468e-9a27-261985cf28ce",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        -576
      ],
      "parameters": {
        "color": 3,
        "width": 764,
        "height": 478,
        "content": "## \u2699\ufe0f Setup Instructions\n\n**1. Credentials to configure**\n- **Klaviyo API Key** \u2014 Create an HTTP Header Auth credential. Header name: `Authorization`, value: `Klaviyo-API-Key <YOUR_KEY>`. Use it in the `Get All Profiles` node.\n- **Postgres** \u2014 Connect your database in `Log Profiles to DB` and `Insert Run Summary`.\n- **Gmail OAuth2** \u2014 Authorize Gmail for `Send a message` and `Send Error Alert`.\n\n**2. Environment variable**\nAdd `KLAVIYO_API_KEY` to your n8n environment settings. The suppression batch node reads it via `$env.KLAVIYO_API_KEY`.\n\n**3. Create database tables before activating**\n`klaviyo_decay_log` \u2014 one row per profile per run\nColumns: profile_id, email, days_since_active, tier, engagement_score, action_taken, run_id\n\n`klaviyo_run_summary` \u2014 one row per workflow run\nColumns: run_id, run_start, run_end, total_profiles, active_count, moderate_count, high_count, critical_count, suppressed_count, status, error_message\n\n**4. Update recipient emails** in `Send a message` and `Send Error Alert`, then activate the workflow \u2705"
      },
      "typeVersion": 1
    },
    {
      "id": "9b541159-0955-4dc2-b6b9-ecdb9d0e2f85",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -656,
        304
      ],
      "parameters": {
        "color": 5,
        "width": 430,
        "height": 254,
        "content": "**1 \u2014 Trigger & Initialize**\nFires at 2 AM daily. The Set node initializes run metadata (timestamp, run ID) passed downstream for tracing."
      },
      "typeVersion": 1
    },
    {
      "id": "1bdde67b-b265-40c7-a13f-6dc420243cb7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -192,
        304
      ],
      "parameters": {
        "color": 5,
        "width": 440,
        "height": 254,
        "content": "**2 \u2014 Fetch & Extract Profiles**\nPaginates through the Klaviyo Profiles API requesting `email`, `created`, and `last_event_date`. The Code node unwraps the nested pagination envelope into flat profile items."
      },
      "typeVersion": 1
    },
    {
      "id": "34758865-62fa-457c-b972-e39dc8f4abb0",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        304
      ],
      "parameters": {
        "color": 5,
        "width": 200,
        "height": 254,
        "content": "**3 \u2014 Score Profiles**\nCalculates days since last engagement. Assigns each profile a decay tier, a 0\u2013100 engagement score, and an action tag."
      },
      "typeVersion": 1
    },
    {
      "id": "41274223-9d80-4305-80a0-96eb7c1f66b4",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        -32
      ],
      "parameters": {
        "color": 5,
        "width": 376,
        "height": 222,
        "content": "**4 \u2014 Log All Profiles**\nInserts every scored profile into `klaviyo_decay_log` for historical analysis and auditing."
      },
      "typeVersion": 1
    },
    {
      "id": "ed934b3b-b9a7-409b-923f-bdeb906a0a8f",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 860,
        "height": 270,
        "content": "**5 \u2014 Summarize, Store & Report**\nAggregates tier counts into a single run summary row and saves it to `klaviyo_run_summary`. Builds a styled HTML email and sends the daily decay report."
      },
      "typeVersion": 1
    },
    {
      "id": "a9c24f5e-035f-4a2f-8117-8bdaaa68b6fb",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        576
      ],
      "parameters": {
        "color": 3,
        "width": 586,
        "height": 334,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n**6 \u2014 Auto-Suppress Critical Profiles \u26a0\ufe0f**\nProfiles inactive for 90+ days are filtered here and bulk-suppressed in Klaviyo in batches of 100 using the Profile Suppression Bulk Create API. Non-critical profiles are discarded from this branch."
      },
      "typeVersion": 1
    },
    {
      "id": "7ce5a20a-f9f3-4885-93df-9b4919b6e6e0",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -661,
        721
      ],
      "parameters": {
        "color": 2,
        "width": 938,
        "height": 255,
        "content": "**Error Handling**\nAny uncaught error in the main workflow triggers this path. The failure details are formatted and logged to `klaviyo_run_summary` with `status: failed`, and an alert email is sent immediately."
      },
      "typeVersion": 1
    },
    {
      "id": "0be8b80c-d538-4f33-9b77-5049d898818b",
      "name": "Schedule: Daily 2AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -576,
        400
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 2 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b0b310de-7ff8-41ac-a5b5-935abfd2f6bf",
      "name": "Set: Initialize Run",
      "type": "n8n-nodes-base.set",
      "position": [
        -352,
        400
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "39a8d5fa-f043-499b-8a3b-48746d0094f1",
      "name": "Get All Profiles",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -128,
        400
      ],
      "parameters": {
        "url": "https://a.klaviyo.com/api/profiles/",
        "options": {
          "pagination": {}
        },
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "page[size]",
              "value": "100"
            },
            {
              "name": "fields[profile]",
              "value": "email,created,last_event_date"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "revision",
              "value": "2024-10-15"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "554f666e-9d02-4022-a624-62b78cfb9f7b",
      "name": "Extract Profile Items",
      "type": "n8n-nodes-base.code",
      "position": [
        96,
        400
      ],
      "parameters": {
        "jsCode": "const profiles = [];\n\nfor (const item of $input.all()) {\n  let val = item.json;\n\n  // Step 1 \u00e2 n8n pagination wraps the full API response as a string in item.json.data\n  if (val && typeof val.data === 'string') {\n    try { val = JSON.parse(val.data); } catch (e) { val = null; }\n  }\n\n  // Step 2 \u00e2 parsed result is { data: [...profiles], links: {...} }; extract the array\n  if (val && !Array.isArray(val) && Array.isArray(val.data)) {\n    val = val.data;\n  }\n\n  // Step 3 \u00e2 val is now the profiles array (or a single profile object)\n  if (Array.isArray(val)) {\n    for (const profile of val) {\n      profiles.push({ json: profile });\n    }\n  } else if (val && val.id) {\n    profiles.push({ json: val });\n  }\n}\n\nreturn profiles;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e94fd26c-3ceb-4c9e-a411-4d8a410b96a7",
      "name": "Score Profiles",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        400
      ],
      "parameters": {
        "jsCode": "const now = DateTime.now();\n\n// Generate a stable run_id from the execution start time.\n// Using DateTime avoids $execution.id which is not available in all n8n versions.\nconst runId = now.toFormat('yyyyMMdd-HHmmss') + '-' + Math.random().toString(36).slice(2, 8);\n\nreturn $input.all().map(item => {\n  const profile = item.json;\n  const attrs = profile.attributes || {};\n\n  const candidates = [attrs.last_event_date, attrs.created].filter(Boolean);\n  const lastEngaged = candidates.length > 0\n    ? candidates.map(d => DateTime.fromISO(d)).reduce((a, b) => (a > b ? a : b))\n    : null;\n\n  const daysSince = lastEngaged\n    ? Math.floor(now.diff(lastEngaged, 'days').days)\n    : 999;\n\n  let tier, engagement_score, action_taken;\n  if (daysSince < 30) {\n    tier = 'active';         engagement_score = 100; action_taken = 'none';\n  } else if (daysSince < 60) {\n    tier = 'moderate_decay'; engagement_score = 60;  action_taken = 'log';\n  } else if (daysSince < 90) {\n    tier = 'high_decay';     engagement_score = 25;  action_taken = 'log';\n  } else {\n    tier = 'critical';       engagement_score = 0;   action_taken = 'suppress';\n  }\n\n  return {\n    json: {\n      profile_id:       profile.id,\n      email:            attrs.email,\n      days_since_active: daysSince,\n      tier,\n      engagement_score,\n      action_taken,\n      run_id: runId\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "82a40c05-1635-4856-bce5-5c35dc3ceca1",
      "name": "Log Profiles to DB",
      "type": "n8n-nodes-base.postgres",
      "position": [
        608,
        48
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "klaviyo_decay_log",
          "cachedResultName": "klaviyo_decay_log"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d6d23b5f-a68a-40c0-9157-3ea6e6769913",
      "name": "Build Run Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        400
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nlet active = 0, moderate = 0, high = 0, critical = 0;\n\nfor (const item of items) {\n  const tier = item.json.tier;\n  if (tier === 'active')              active++;\n  else if (tier === 'moderate_decay') moderate++;\n  else if (tier === 'high_decay')     high++;\n  else if (tier === 'critical')       critical++;\n}\n\n// run_id flows through from Score Profiles via item data \u00e2 no cross-node reference needed\nconst runId    = items[0]?.json?.run_id || DateTime.now().toFormat('yyyyMMdd-HHmmss');\nconst runStart = DateTime.now().toISO();\n\nreturn [{\n  json: {\n    run_id:           runId,\n    run_start:        runStart,\n    run_end:          DateTime.now().toISO(),\n    total_profiles:   items.length,\n    active_count:     active,\n    moderate_count:   moderate,\n    high_count:       high,\n    critical_count:   critical,\n    suppressed_count: critical,\n    status:           'success'\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "16a4c769-ec52-4d9b-83a9-159aa13fae8c",
      "name": "Insert Run Summary",
      "type": "n8n-nodes-base.postgres",
      "position": [
        768,
        400
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "klaviyo_run_summary",
          "cachedResultName": "klaviyo_run_summary"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "dab7f2d2-fd60-4805-9894-d8ac3c3cb5f8",
      "name": "Build Email HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        992,
        400
      ],
      "parameters": {
        "jsCode": "const d = $input.first().json;\nconst date = DateTime.now().toFormat('MMMM dd, yyyy');\n\nconst row = (label, count, action, color) =>\n  `\n  <tr>\n    <td style=\"padding:14px 16px;font-size:14px;color:#333;\">${label}</td>\n    <td align=\"right\" style=\"padding:14px 16px;font-size:14px;font-weight:600;color:#111;\">\n      ${count}\n    </td>\n    <td style=\"padding:14px 16px;font-size:13px;\">\n      <span style=\"\n        padding:6px 10px;\n        border-radius:6px;\n        background:${color.bg};\n        color:${color.text};\n        font-weight:500;\n      \">\n        ${action}\n      </span>\n    </td>\n  </tr>\n`;\n\nconst html = `\n<div style=\"background:#f4f6f8;padding:40px 0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\">\n  \n  <div style=\"max-width:600px;margin:0 auto;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 8px 24px rgba(0,0,0,0.06);\">\n    \n    <!-- Header -->\n    <div style=\"padding:24px 28px;background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;\">\n      <h2 style=\"margin:0;font-size:20px;font-weight:600;\">\n        \u00f0 Klaviyo Decay Report\n      </h2>\n      <p style=\"margin:6px 0 0;font-size:13px;opacity:0.9;\">\n        ${date}\n      </p>\n    </div>\n\n    <!-- Summary -->\n    <div style=\"padding:20px 28px;border-bottom:1px solid #eee;\">\n      <p style=\"margin:0;font-size:14px;color:#555;\">\n        Here\u00e2s your latest engagement decay breakdown. Critical profiles have been automatically suppressed to maintain deliverability.\n      </p>\n    </div>\n\n    <!-- Table -->\n    <table style=\"width:100%;border-collapse:collapse;\">\n      <thead>\n        <tr style=\"background:#fafafa;text-transform:uppercase;font-size:11px;color:#888;letter-spacing:0.05em;\">\n          <th align=\"left\" style=\"padding:12px 16px;\">Tier</th>\n          <th align=\"right\" style=\"padding:12px 16px;\">Profiles</th>\n          <th align=\"left\" style=\"padding:12px 16px;\">Status</th>\n        </tr>\n      </thead>\n      <tbody>\n        ${row('Active (<30 days)', d.active_count, 'Healthy', {\n          bg:'#ecfdf5', text:'#059669'\n        })}\n        \n        ${row('Moderate Decay (30\u00e260 days)', d.moderate_count, 'Monitor', {\n          bg:'#fffbeb', text:'#d97706'\n        })}\n        \n        ${row('High Decay (60\u00e290 days)', d.high_count, 'At Risk', {\n          bg:'#fff7ed', text:'#ea580c'\n        })}\n        \n        ${row('<strong>Critical (90+ days)</strong>', `<strong>${d.critical_count}</strong>`, 'Suppressed', {\n          bg:'#fef2f2', text:'#dc2626'\n        })}\n        \n        <tr style=\"border-top:2px solid #eee;background:#fafafa;\">\n          <td style=\"padding:14px 16px;font-weight:600;\">Total</td>\n          <td align=\"right\" style=\"padding:14px 16px;font-weight:700;\">\n            ${d.total_profiles}\n          </td>\n          <td></td>\n        </tr>\n      </tbody>\n    </table>\n\n    <!-- Footer -->\n    <div style=\"padding:20px 28px;font-size:12px;color:#777;border-top:1px solid #eee;\">\n      <p style=\"margin:0 0 8px;\">\n        <strong>Run ID:</strong> ${d.run_id}\n      </p>\n      <p style=\"margin:0 0 8px;\">\n        <strong>Started:</strong> ${d.run_start}\n      </p>\n      <p style=\"margin:0;\">\n        <strong>Completed:</strong> ${d.run_end}\n      </p>\n    </div>\n\n  </div>\n\n  <!-- Bottom note -->\n  <div style=\"max-width:600px;margin:12px auto 0;text-align:center;font-size:11px;color:#aaa;\">\n    Automated report \u00e2\u00a2 Deliverability Protection System\n  </div>\n\n</div>\n`;\n\nreturn [{\n  json: {\n    html_report: html,\n    email_subject: `\u00f0 Klaviyo Decay Report \u00e2 ${date} | ${d.critical_count} suppressed`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "0f061f5f-9db7-4be1-a6e6-1e8f71d8c880",
      "name": "Is Critical?",
      "type": "n8n-nodes-base.if",
      "position": [
        544,
        592
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.tier }}",
              "rightValue": "critical"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c27dff82-61d6-4781-8936-d8f3402105c1",
      "name": "Suppress Critical in Batches",
      "type": "n8n-nodes-base.code",
      "position": [
        800,
        576
      ],
      "parameters": {
        "jsCode": "const criticalProfiles = $input.all();\nif (criticalProfiles.length === 0) {\n  return [{ json: { profiles_suppressed: 0, status: 'no_critical_profiles' } }];\n}\n\nconst batchSize = 100;\nconst apiKey = $env.KLAVIYO_API_KEY;\nconst results = [];\nlet totalSuppressed = 0;\n\nfor (let i = 0; i < criticalProfiles.length; i += batchSize) {\n  const batch = criticalProfiles.slice(i, i + batchSize);\n  const profilesData = batch.map(p => ({\n    type: 'profile',\n    attributes: { email: p.json.email }\n  }));\n\n  try {\n    await $helpers.httpRequest({\n      method: 'POST',\n      url: 'https://a.klaviyo.com/api/profile-suppression-bulk-create-jobs/',\n      headers: {\n        'Authorization': 'Klaviyo-API-Key ' + apiKey,\n        'revision': '2024-10-15',\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        data: {\n          type: 'profile-suppression-bulk-create-job',\n          attributes: { profiles: { data: profilesData } }\n        }\n      })\n    });\n    totalSuppressed += batch.length;\n  } catch (error) {\n    results.push({\n      json: { batch: Math.floor(i / batchSize) + 1, error: error.message, status: 'error' }\n    });\n  }\n}\n\nreturn results.length === 0\n  ? [{ json: { profiles_suppressed: totalSuppressed, status: 'success' } }]\n  : results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "3aede6fa-a3cf-4efa-8e71-5e961d2b07ac",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -576,
        816
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "fa1c848b-f4a9-4485-a279-d9605d9bdad7",
      "name": "Prepare Error Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -352,
        816
      ],
      "parameters": {
        "jsCode": "const err  = $json.execution?.error || {};\nconst init = (() => { try { return $('Set: Initialize Run').first().json; } catch(e) { return {}; } })();\n\nreturn [{\n  json: {\n    run_id:           init.run_id || ('err-' + DateTime.now().toMillis()),\n    run_start:        init.run_start || null,\n    run_end:          DateTime.now().toISO(),\n    total_profiles:   0,\n    active_count:     0,\n    moderate_count:   0,\n    high_count:       0,\n    critical_count:   0,\n    suppressed_count: 0,\n    status:           'failed',\n    error_message:    err.message || $json.error || 'Unknown error'\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "99f6f951-a13e-4dc6-aaa6-229aeaac8ade",
      "name": "Log Error to DB",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -128,
        816
      ],
      "parameters": {
        "table": "klaviyo_run_summary",
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "354b9649-e08e-49d2-9de8-f233ad810ce1",
      "name": "Send Error Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        96,
        816
      ],
      "parameters": {
        "sendTo": "your@email.com",
        "message": "={{ '<h3>Workflow Error</h3><p><b>Error:</b> ' + ($json.error_message || 'Unknown') + '</p><p><b>Time:</b> ' + DateTime.now().toISO() + '</p>' }}",
        "options": {},
        "subject": "={{ '[ERROR] Klaviyo Decay Detection Failed \u00e2 ' + $now.toFormat('yyyy-MM-dd') }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "a4f79a36-33a8-4b68-9b33-b707952e4bc0",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1216,
        400
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.html_report }}",
        "options": {},
        "subject": "={{ $json.email_subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "executionOrder": "v1",
    "saveManualExecutions": true
  },
  "versionId": "e08558ce-fdae-4aed-a183-a62975e15bbf",
  "connections": {
    "Is Critical?": {
      "main": [
        [
          {
            "node": "Suppress Critical in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Prepare Error Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Profiles": {
      "main": [
        [
          {
            "node": "Log Profiles to DB",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Run Summary",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Critical?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Error to DB": {
      "main": [
        [
          {
            "node": "Send Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Email HTML": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All Profiles": {
      "main": [
        [
          {
            "node": "Extract Profile Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Run Summary": {
      "main": [
        [
          {
            "node": "Insert Run Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Run Summary": {
      "main": [
        [
          {
            "node": "Build Email HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Error Data": {
      "main": [
        [
          {
            "node": "Log Error to DB",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: Daily 2AM": {
      "main": [
        [
          {
            "node": "Set: Initialize Run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Initialize Run": {
      "main": [
        [
          {
            "node": "Get All Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Profile Items": {
      "main": [
        [
          {
            "node": "Score Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}