{
  "name": "WF-05 · Customer Win-Back",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [{ "field": "cronExpression", "expression": "0 10 * * *" }]
        }
      },
      "id": "n1",
      "name": "Daily at 10 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [200, 300],
      "notes": "Runs every morning to check for students who have gone quiet."
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.teachable.com/v1/users?page=1&per=100",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "apiKey", "value": "{{ TEACHABLE_API_KEY }}" }
          ]
        },
        "options": {},
        "notes": "Replace with your course platform API (Thinkific, Kajabi, Circle, Teachable, Podia). Endpoint should return enrolled students with last_login or last_activity fields."
      },
      "id": "n2",
      "name": "Fetch Enrolled Students",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [420, 300]
    },
    {
      "parameters": {
        "jsCode": "const students = $input.item.json.users || $input.item.json.data || [];\nconst cutoff = new Date();\ncutoff.setDate(cutoff.getDate() - 5);\n\nconst stalled = students.filter(s => {\n  const lastActive = new Date(s.last_login || s.last_activity_at);\n  const enrolled = s.enrolled_at ? new Date(s.enrolled_at) : null;\n  // Stalled = inactive 5+ days but enrolled within last 90 days\n  const inWindow = enrolled ? (new Date() - enrolled) < 90 * 24 * 60 * 60 * 1000 : true;\n  return lastActive < cutoff && inWindow && s.percent_complete < 100;\n});\n\nreturn stalled.map(s => ({ json: {\n  userId:          s.id,\n  email:           s.email,\n  firstName:       s.name?.split(' ')[0] || s.first_name || 'there',\n  lastName:        s.name?.split(' ')[1] || s.last_name || '',\n  lastActive:      s.last_login || s.last_activity_at,\n  percentComplete: s.percent_complete || 0,\n  courseId:        s.course_id,\n  courseName:      s.course_name || '{{ YOUR_COURSE_NAME }}'\n}}));",
        "options": {}
      },
      "id": "n3",
      "name": "Filter Stalled Students",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [640, 300]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.teachable.com/v1/users/{{ $json.userId }}/course_enrollments/{{ $json.courseId }}/lecture_completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "apiKey", "value": "{{ TEACHABLE_API_KEY }}" }
          ]
        },
        "options": {}
      },
      "id": "n4",
      "name": "Find Last Lesson",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [860, 300]
    },
    {
      "parameters": {
        "jsCode": "const completions = $json.lecture_completions || [];\nconst last = completions.sort((a, b) => new Date(b.completed_at) - new Date(a.completed_at))[0];\n\nreturn [{ json: {\n  ...$('Filter Stalled Students').item.json,\n  lastLessonTitle:  last?.lecture_name || 'your last lesson',\n  lastLessonNumber: last?.lecture_number || '?',\n  moduleNumber:     last?.section_number || '?',\n  resumeUrl:        last?.lecture_url || '{{ YOUR_COURSE_URL }}'\n}}];",
        "options": {}
      },
      "id": "n5",
      "name": "Build Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1060, 300]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "=Hey {{ $json.firstName }} — you're closer than you think",
        "emailType": "html",
        "message": "=<p>Hi {{ $json.firstName }},</p><p>I noticed you haven't been in {{ $json.courseName }} for a few days — life gets busy, no judgment.</p><p>You left off on <strong>{{ $json.lastLessonTitle }}</strong> in Module {{ $json.moduleNumber }}. You're {{ $json.percentComplete }}% through. That's real progress.</p><p>One lesson today puts you past where you are now. Here's exactly where you stopped:</p><p><a href=\"{{ $json.resumeUrl }}\">Pick up where you left off →</a></p><p>Reply to this if you've hit a wall — I read every one.</p><p>{{ YOUR_NAME }}</p>",
        "options": {}
      },
      "id": "n6",
      "name": "Send Personal Nudge",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [1260, 300],
      "credentials": {
        "gmailOAuth2": { "id": "gmail-creds", "name": "Gmail" }
      }
    },
    {
      "parameters": {
        "amount": 48,
        "unit": "hours"
      },
      "id": "n7",
      "name": "Wait 48 Hours",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [1460, 300]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.teachable.com/v1/users/{{ $json.userId }}/course_enrollments/{{ $json.courseId }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "apiKey", "value": "{{ TEACHABLE_API_KEY }}" }
          ]
        },
        "options": {}
      },
      "id": "n8",
      "name": "Check If Back",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1660, 300]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.last_login }}",
              "rightValue": "={{ $('Send Personal Nudge').item.json.calledAt }}",
              "operator": { "type": "string", "operation": "after" }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "n9",
      "name": "Did They Log In?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1860, 300]
    },
    {
      "parameters": {
        "operation": "send",
        "from": "{{ YOUR_TWILIO_PHONE_NUMBER }}",
        "to": "={{ $('Build Context').item.json.phone || '{{ FALLBACK_PHONE }}' }}",
        "message": "=Hey {{ $('Build Context').item.json.firstName }}, {{ YOUR_NAME }} here — just wanted to make sure you got my email about {{ $('Build Context').item.json.courseName }}. Here's a 4-min recap to get you back in: {{ $('Build Context').item.json.resumeUrl }}"
      },
      "id": "n10",
      "name": "Send Rescue Text",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [2060, 460],
      "credentials": {
        "twilioApi": { "id": "twilio-creds", "name": "Twilio" }
      }
    }
  ],
  "connections": {
    "Daily at 10 AM":          { "main": [[{ "node": "Fetch Enrolled Students",  "type": "main", "index": 0 }]] },
    "Fetch Enrolled Students": { "main": [[{ "node": "Filter Stalled Students",  "type": "main", "index": 0 }]] },
    "Filter Stalled Students": { "main": [[{ "node": "Find Last Lesson",         "type": "main", "index": 0 }]] },
    "Find Last Lesson":        { "main": [[{ "node": "Build Context",            "type": "main", "index": 0 }]] },
    "Build Context":           { "main": [[{ "node": "Send Personal Nudge",      "type": "main", "index": 0 }]] },
    "Send Personal Nudge":     { "main": [[{ "node": "Wait 48 Hours",            "type": "main", "index": 0 }]] },
    "Wait 48 Hours":           { "main": [[{ "node": "Check If Back",            "type": "main", "index": 0 }]] },
    "Check If Back":           { "main": [[{ "node": "Did They Log In?",         "type": "main", "index": 0 }]] },
    "Did They Log In?": {
      "main": [
        [],
        [{ "node": "Send Rescue Text", "type": "main", "index": 0 }]
      ]
    }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "id": "wf-05-customer-win-back",
  "versionId": "1.0.0"
}
