Education teams are drowning in admin work. Enrollment emails, assignment reminders, certificate generation, at-risk student alerts — tasks that consume staff hours that could go toward actually teaching.
This guide covers 5 production-ready n8n workflows for EdTech companies, online course platforms, universities, and school administrators. Every workflow includes full JSON you can import and run today.
Why n8n for Education?
n8n is free (self-hosted), works with any LMS or platform via webhooks, and handles conditional logic that education workflows actually need: "if student hasn't submitted by 24h before deadline, send a reminder; if completion rate drops below 30%, flag as at-risk." No per-task pricing. No per-seat licensing.
Workflow 1: Student Enrollment & Welcome Sequence
When a student enrolls — via Stripe, Teachable, Thinkific, or a signup form — they should receive a personalized welcome email instantly, get added to your enrollment tracker, and trigger a Slack notification to the success team.
Trigger: Webhook (Stripe payment, LMS signup, or Typeform)
{
"name": "Student Enrollment & Welcome",
"nodes": [
{
"parameters": { "httpMethod": "POST", "path": "student-enrolled", "responseMode": "responseNode", "options": {} },
"name": "Enrollment Webhook",
"type": "n8n-nodes-base.webhook",
"position": [260, 300]
},
{
"parameters": {
"jsCode": "const d = $json.body || $json;\nreturn [{ json: {\n name: d.customer_name || d.name,\n email: d.customer_email || d.email,\n course: d.product_name || d.course_name || d.course,\n enrolled_at: new Date().toISOString()\n}}];"
},
"name": "Extract Data",
"type": "n8n-nodes-base.code",
"position": [460, 300]
},
{
"parameters": {
"operation": "append",
"documentId": { "value": "YOUR_SHEET_ID" },
"sheetName": "Enrollments",
"columns": { "mappingMode": "autoMapInputData" },
"options": {}
},
"name": "Log to Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [660, 300]
},
{
"parameters": {
"sendTo": "={{ $('Extract Data').item.json.email }}",
"subject": "Welcome to {{ $('Extract Data').item.json.course }}!",
"message": "<h2>Welcome, {{ $('Extract Data').item.json.name }}!</h2><p>You're now enrolled in <strong>{{ $('Extract Data').item.json.course }}</strong>. Here's what to do next...</p><p>Reply to this email with any questions.</p>",
"options": { "isHtml": true }
},
"name": "Welcome Email",
"type": "n8n-nodes-base.gmail",
"position": [860, 200]
},
{
"parameters": {
"channel": "#new-enrollments",
"text": "New enrollment: *{{ $('Extract Data').item.json.name }}* joined *{{ $('Extract Data').item.json.course }}*"
},
"name": "Slack Notify",
"type": "n8n-nodes-base.slack",
"position": [860, 420]
}
],
"connections": {
"Enrollment Webhook": { "main": [[{ "node": "Extract Data", "type": "main", "index": 0 }]] },
"Extract Data": { "main": [[{ "node": "Log to Sheets", "type": "main", "index": 0 }]] },
"Log to Sheets": { "main": [[{ "node": "Welcome Email", "type": "main", "index": 0 }, { "node": "Slack Notify", "type": "main", "index": 0 }]] }
}
}
Result: Zero manual work. Every new student is welcomed within seconds, logged, and the team is notified.
Workflow 2: Assignment Due Date Reminder (48h + 2h)
Students miss deadlines because they forget. Automated reminders — 48 hours before and 2 hours before — dramatically improve submission rates without any instructor effort.
Trigger: Schedule (every 30 minutes)
Data source: Google Sheet with columns: student_email, assignment_name, due_date, reminder_sent_48h, reminder_sent_2h
{
"name": "Assignment Due Date Reminder",
"nodes": [
{
"parameters": { "rule": { "interval": [{ "field": "minutes", "minutesInterval": 30 }] } },
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [260, 300]
},
{
"parameters": {
"operation": "readAll",
"documentId": { "value": "YOUR_SHEET_ID" },
"sheetName": "Assignments",
"options": {}
},
"name": "Read Assignments",
"type": "n8n-nodes-base.googleSheets",
"position": [460, 300]
},
{
"parameters": {
"jsCode": "const now = Date.now();\nreturn $input.all().filter(item => {\n const due = new Date(item.json.due_date).getTime();\n const hrs = (due - now) / 3600000;\n item.json.hours_until_due = Math.round(hrs);\n item.json.send_48h = hrs > 0 && hrs <= 48 && item.json.reminder_sent_48h !== 'yes';\n item.json.send_2h = hrs > 0 && hrs <= 2 && item.json.reminder_sent_2h !== 'yes';\n return item.json.send_48h || item.json.send_2h;\n});"
},
"name": "Filter Due Soon",
"type": "n8n-nodes-base.code",
"position": [660, 300]
},
{
"parameters": {
"sendTo": "={{ $json.student_email }}",
"subject": "Reminder: {{ $json.assignment_name }} due in {{ $json.hours_until_due }} hours",
"message": "<p>Hi {{ $json.student_name }},</p><p>Your assignment <strong>{{ $json.assignment_name }}</strong> is due in ~{{ $json.hours_until_due }} hours. Don't miss it!</p>",
"options": { "isHtml": true }
},
"name": "Send Reminder",
"type": "n8n-nodes-base.gmail",
"position": [860, 300]
}
],
"connections": {
"Schedule Trigger": { "main": [[{ "node": "Read Assignments", "type": "main", "index": 0 }]] },
"Read Assignments": { "main": [[{ "node": "Filter Due Soon", "type": "main", "index": 0 }]] },
"Filter Due Soon": { "main": [[{ "node": "Send Reminder", "type": "main", "index": 0 }]] }
}
}
Tip: After sending, use an additional Google Sheets node to write "yes" back to the reminder_sent column so reminders aren't sent twice.
Workflow 3: Course Completion Certificate Generator
Students expect a certificate immediately after they complete a course. This workflow fires on a completion webhook, generates an HTML certificate, and emails it automatically.
Trigger: Webhook (LMS completion event)
{
"name": "Course Completion Certificate",
"nodes": [
{
"parameters": { "httpMethod": "POST", "path": "course-completed", "responseMode": "lastNode", "options": {} },
"name": "Completion Webhook",
"type": "n8n-nodes-base.webhook",
"position": [260, 300]
},
{
"parameters": {
"jsCode": "const d = $json.body || $json;\nconst date = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\nreturn [{ json: {\n name: d.student_name,\n email: d.student_email,\n course: d.course_name,\n completion_date: date,\n cert_html: `<html><body style='font-family:Georgia,serif;text-align:center;padding:80px;border:8px double #2c5f2e'><h1 style='color:#2c5f2e'>Certificate of Completion</h1><p style='font-size:18px'>This certifies that</p><h2 style='font-size:32px'>${d.student_name}</h2><p style='font-size:18px'>has successfully completed</p><h3 style='font-size:24px'>${d.course_name}</h3><p style='color:#666'>Completed: ${date}</p></body></html>`\n}}];"
},
"name": "Build Certificate",
"type": "n8n-nodes-base.code",
"position": [460, 300]
},
{
"parameters": {
"sendTo": "={{ $json.email }}",
"subject": "Your Certificate: {{ $json.course }}",
"message": "<p>Congratulations, {{ $json.name }}!</p><p>Your certificate for <strong>{{ $json.course }}</strong> is attached.</p><p>Well done on completing the course!</p>",
"options": { "isHtml": true }
},
"name": "Email Certificate",
"type": "n8n-nodes-base.gmail",
"position": [660, 300]
}
],
"connections": {
"Completion Webhook": { "main": [[{ "node": "Build Certificate", "type": "main", "index": 0 }]] },
"Build Certificate": { "main": [[{ "node": "Email Certificate", "type": "main", "index": 0 }]] }
}
}
Tip: For full PDF certificates, add an HTTP Request node that POSTs the HTML to a PDF generation API (PDF.co, Gotenberg, or WeasyPrint on your server) and attach the binary response to the email.
Workflow 4: Weekly Learning Progress Digest
Every Monday morning, instructors receive a complete report: total enrolled, completion rate, students at-risk (below 30% completion), and top performers. No manual data pulls.
Trigger: Schedule (Monday 8:00 AM)
{
"name": "Weekly Learning Progress Digest",
"nodes": [
{
"parameters": { "rule": { "interval": [{ "field": "weeks", "triggerAtDay": [1], "triggerAtHour": 8 }] } },
"name": "Monday 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [260, 300]
},
{
"parameters": {
"operation": "readAll",
"documentId": { "value": "YOUR_SHEET_ID" },
"sheetName": "Progress",
"options": {}
},
"name": "Read Progress",
"type": "n8n-nodes-base.googleSheets",
"position": [460, 300]
},
{
"parameters": {
"jsCode": "const rows = $input.all().map(i => i.json);\nconst total = rows.length;\nconst completed = rows.filter(r => r.status === 'completed').length;\nconst at_risk = rows.filter(r => parseFloat(r.completion_pct || 0) < 30 && r.status !== 'completed');\nconst rate = total ? Math.round(completed / total * 100) : 0;\nconst riskList = at_risk.map(r => `<li>${r.student_name} — ${r.completion_pct || 0}% done (${r.email})</li>`).join('');\nconst html = `<h2>Weekly Progress — ${new Date().toDateString()}</h2><table border=1 cellpadding=8 style='border-collapse:collapse'><tr><td>Total Enrolled</td><td><b>${total}</b></td></tr><tr><td>Completed</td><td><b>${completed}</b></td></tr><tr><td>Completion Rate</td><td><b>${rate}%</b></td></tr><tr><td>At-Risk (<30%)</td><td><b style='color:${at_risk.length > 0 ? 'red' : 'green'}'>${at_risk.length}</b></td></tr></table>${at_risk.length > 0 ? '<h3>At-Risk Students</h3><ul>' + riskList + '</ul>' : '<p style=color:green>No at-risk students this week.</p>'}`;\nreturn [{ json: { html, total, completed, rate, at_risk: at_risk.length } }];"
},
"name": "Compute Metrics",
"type": "n8n-nodes-base.code",
"position": [660, 300]
},
{
"parameters": {
"sendTo": "instructor@yourplatform.com",
"subject": "Weekly Learning Report — {{ $json.rate }}% Completion | {{ $json.at_risk }} At-Risk",
"message": "={{ $json.html }}",
"options": { "isHtml": true }
},
"name": "Email Digest",
"type": "n8n-nodes-base.gmail",
"position": [860, 300]
}
],
"connections": {
"Monday 8AM": { "main": [[{ "node": "Read Progress", "type": "main", "index": 0 }]] },
"Read Progress": { "main": [[{ "node": "Compute Metrics", "type": "main", "index": 0 }]] },
"Compute Metrics": { "main": [[{ "node": "Email Digest", "type": "main", "index": 0 }]] }
}
}
Scale-up tip: BCC multiple instructors, or add a loop that sends each instructor a filtered view showing only their own students.
Workflow 5: Student Question Auto-Triage (AI-Powered)
Student support inboxes pile up fast. This workflow reads incoming student emails, uses Claude Haiku to classify them (FAQ / needs instructor / billing), auto-replies to FAQ questions, and routes everything else to Slack for fast human follow-up.
Trigger: Gmail Trigger (new email to support inbox)
{
"name": "Student Question Auto-Triage",
"nodes": [
{
"parameters": { "pollTimes": { "item": [{ "mode": "everyMinute" }] }, "filters": { "labelIds": ["INBOX"] } },
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [260, 300]
},
{
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"sendHeaders": true,
"headerParameters": { "parameters": [
{ "name": "x-api-key", "value": "YOUR_ANTHROPIC_KEY" },
{ "name": "anthropic-version", "value": "2023-06-01" },
{ "name": "content-type", "value": "application/json" }
]},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 400, messages: [{ role: 'user', content: 'Classify this student question. Categories: FAQ (common, you can answer it), INSTRUCTOR (complex or personal), BILLING (payment issue). Return JSON only: {\"category\": \"...\", \"suggested_reply\": \"...\"}. Question: ' + $json.text }] }) }}"
},
"name": "AI Classify",
"type": "n8n-nodes-base.httpRequest",
"position": [460, 300]
},
{
"parameters": {
"jsCode": "const raw = $json.content[0].text;\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\nconst parsed = JSON.parse(match[0]);\nreturn [{ json: { ...parsed, original_email: $('Gmail Trigger').item.json.from } }];"
},
"name": "Parse Classification",
"type": "n8n-nodes-base.code",
"position": [660, 300]
},
{
"parameters": {
"conditions": { "string": [{ "value1": "={{ $json.category }}", "operation": "equal", "value2": "FAQ" }] }
},
"name": "Is FAQ?",
"type": "n8n-nodes-base.if",
"position": [860, 300]
},
{
"parameters": {
"operation": "reply",
"messageId": "={{ $('Gmail Trigger').item.json.id }}",
"message": "={{ $('Parse Classification').item.json.suggested_reply }}",
"options": {}
},
"name": "Auto-Reply",
"type": "n8n-nodes-base.gmail",
"position": [1060, 180]
},
{
"parameters": {
"channel": "#course-support",
"text": "*Student question needs attention ({{ $json.category }})*\nFrom: {{ $json.original_email }}"
},
"name": "Slack Alert",
"type": "n8n-nodes-base.slack",
"position": [1060, 420]
}
],
"connections": {
"Gmail Trigger": { "main": [[{ "node": "AI Classify", "type": "main", "index": 0 }]] },
"AI Classify": { "main": [[{ "node": "Parse Classification", "type": "main", "index": 0 }]] },
"Parse Classification": { "main": [[{ "node": "Is FAQ?", "type": "main", "index": 0 }]] },
"Is FAQ?": { "main": [
[{ "node": "Auto-Reply", "type": "main", "index": 0 }],
[{ "node": "Slack Alert", "type": "main", "index": 0 }]
]}
}
}
This single workflow can reduce support inbox volume by 50–70% on a typical EdTech platform.
How These Map to FlowKit Templates
All five patterns are available as ready-to-import templates at stripeai.gumroad.com:
| Workflow | Template | Price |
|---|---|---|
| Enrollment welcome | Email Auto-Responder | $15 |
| Assignment reminders | Appointment Reminder | $15 |
| Weekly progress digest | AI Customer Feedback Analyzer | $29 |
| Student question triage | AI Customer Support Bot | $29 |
| Lead capture + CRM | Lead Capture to CRM | $19 |
Each template includes complete JSON, a setup guide, and all the edge case handling already built in. Browse the full library →
What EdTech automation are you building? Drop it in the comments — happy to share the workflow pattern.
Top comments (0)