DEV Community

Alex Kane
Alex Kane

Posted on

n8n for Nonprofits: 5 Automations That Help You Do More With Less Budget (Free Workflow JSON)

Running a nonprofit means doing more with less — fewer staff, tighter budgets, and a board that expects results. But 10+ hours a week disappear into donor follow-ups, volunteer coordination, grant tracking, and report generation.

This post shares 5 n8n automation workflows your team can deploy today. Each includes complete JSON you can import directly into n8n.


Why n8n for Nonprofits?

Two reasons stand out:

Cost: n8n self-hosted is free. No per-task fees. Zapier charges $19.99/month for 750 tasks — n8n handles unlimited tasks once running.

Data privacy: Donor PII, grant amounts, and beneficiary data should stay on your infrastructure, not routed through third-party servers.


Workflow 1: Donor Thank-You & Tax Receipt Automation

Trigger: Stripe webhook on payment_intent.succeeded
What it does: Sends a personalized thank-you email with a tax receipt, logs the donation to Google Sheets, and posts a Slack alert for gifts over $500.

{
  "name": "Donor Thank-You & Tax Receipt",
  "nodes": [
    {
      "name": "Stripe Donation Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": { "path": "stripe-donation", "httpMethod": "POST" }
    },
    {
      "name": "Extract Donation Data",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const data = $json.body.data.object;\nreturn [{\n  donor_email: data.receipt_email,\n  donor_name: data.shipping?.name || 'Valued Donor',\n  amount_cents: data.amount,\n  amount_usd: (data.amount / 100).toFixed(2),\n  payment_id: data.id,\n  donation_date: new Date().toLocaleDateString('en-US', {year:'numeric',month:'long',day:'numeric'}),\n  is_large: data.amount >= 50000\n}];"
      }
    },
    {
      "name": "Send Thank-You Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.donor_email }}",
        "subject": "Thank you for your gift, {{ $json.donor_name }}!",
        "message": "Dear {{ $json.donor_name }},\n\nThank you for your generous gift of ${{ $json.amount_usd }} on {{ $json.donation_date }}.\n\nThis email serves as your tax receipt. Payment ID: {{ $json.payment_id }}\n\nYour support makes our work possible.\n\nWith gratitude,\nThe Team"
      }
    },
    {
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Donations!A:F"
      }
    },
    {
      "name": "Large Gift Alert?",
      "type": "n8n-nodes-base.if",
      "parameters": { "conditions": { "boolean": [{ "value1": "={{ $json.is_large }}", "value2": true }] } }
    },
    {
      "name": "Slack Major Gift Alert",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#development",
        "text": "Major gift received! ${{ $json.amount_usd }} from {{ $json.donor_name }}. Check CRM."
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: Volunteer Reminder Sequence

Trigger: Schedule (twice daily: 8AM and 4PM)
What it does: Reads upcoming volunteer shifts from Google Sheets. Sends a 48-hour reminder and a 2-hour reminder to each volunteer automatically.

{
  "name": "Volunteer Reminder Sequence",
  "nodes": [
    {
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "hours", "hoursInterval": 8 }] } }
    },
    {
      "name": "Read Volunteer Shifts",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "readRows",
        "sheetId": "YOUR_SHEET_ID",
        "range": "VolunteerShifts!A:E"
      }
    },
    {
      "name": "Filter for Upcoming Shifts",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const now = new Date();\nconst items = [];\nfor (const item of $input.all()) {\n  const shiftTime = new Date(item.json.shift_datetime);\n  const hoursUntil = (shiftTime - now) / (1000 * 60 * 60);\n  if (hoursUntil >= 47 && hoursUntil <= 49) {\n    items.push({ ...item.json, reminder_type: '48h' });\n  } else if (hoursUntil >= 1.75 && hoursUntil <= 2.25) {\n    items.push({ ...item.json, reminder_type: '2h' });\n  }\n}\nreturn items;"
      }
    },
    {
      "name": "Send Volunteer Reminder",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.volunteer_email }}",
        "subject": "Reminder: Volunteer Shift ({{ $json.reminder_type }}) — {{ $json.role }}",
        "message": "Hi {{ $json.volunteer_name }},\n\nThis is your {{ $json.reminder_type }} reminder for your volunteer shift:\n\nRole: {{ $json.role }}\nDate/Time: {{ $json.shift_datetime }}\nLocation: {{ $json.location }}\n\nThank you for giving your time!"
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: Grant Deadline Tracker with Escalating Alerts

Trigger: Schedule daily at 8AM
What it does: Reads grant deadlines from Google Sheets. Sends escalating alerts at 30, 14, 7, and 1 day(s) before each deadline.

{
  "name": "Grant Deadline Tracker",
  "nodes": [
    {
      "name": "Daily 8AM Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * *" }] } }
    },
    {
      "name": "Read Grant Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "readRows",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Grants!A:E"
      }
    },
    {
      "name": "Calculate Urgency",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const today = new Date();\nconst alerts = [];\nfor (const item of $input.all()) {\n  const deadline = new Date(item.json.deadline_date);\n  const daysLeft = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24));\n  let urgency = null;\n  if (daysLeft === 1) urgency = 'CRITICAL — due TOMORROW';\n  else if (daysLeft === 7) urgency = 'URGENT — 7 days left';\n  else if (daysLeft === 14) urgency = 'WARNING — 14 days left';\n  else if (daysLeft === 30) urgency = 'NOTICE — 30 days left';\n  if (urgency) alerts.push({ ...item.json, days_left: daysLeft, urgency });\n}\nreturn alerts;"
      }
    },
    {
      "name": "Email Grant Manager",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.grant_manager_email }}",
        "subject": "[{{ $json.urgency }}] Grant Deadline: {{ $json.grant_name }}",
        "message": "Grant: {{ $json.grant_name }}\nFunder: {{ $json.funder }}\nDeadline: {{ $json.deadline_date }}\nDays Remaining: {{ $json.days_left }}\nStatus: {{ $json.urgency }}"
      }
    },
    {
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#grants",
        "text": "{{ $json.urgency }}: {{ $json.grant_name }} deadline {{ $json.deadline_date }}."
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: Monthly Donor Impact Report

Trigger: 1st of every month at 8AM
What it does: Reads donation data from the past month. Builds a formatted HTML impact report with key metrics and emails it to leadership.

{
  "name": "Monthly Donor Impact Report",
  "nodes": [
    {
      "name": "1st of Month Cron",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 1 * *" }] } }
    },
    {
      "name": "Read Donations Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "readRows",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Donations!A:F"
      }
    },
    {
      "name": "Build Impact Report",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const now = new Date();\nconst lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);\nconst endLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);\nlet total = 0, count = 0;\nconst donors = new Set();\nfor (const item of $input.all()) {\n  const d = new Date(item.json.donation_date);\n  if (d >= lastMonth && d <= endLastMonth) {\n    total += parseFloat(item.json.amount_usd) || 0;\n    count++;\n    donors.add(item.json.donor_email);\n  }\n}\nconst avg = count > 0 ? (total / count).toFixed(2) : '0.00';\nconst period = lastMonth.toLocaleString('default',{month:'long',year:'numeric'});\nconst html = `<h2>Monthly Donor Report — ${period}</h2><table border='1' cellpadding='8'><tr><th>Metric</th><th>Value</th></tr><tr><td>Total Donations</td><td>$${total.toFixed(2)}</td></tr><tr><td>Number of Gifts</td><td>${count}</td></tr><tr><td>Unique Donors</td><td>${donors.size}</td></tr><tr><td>Average Gift</td><td>$${avg}</td></tr></table>`;\nreturn [{ html, period, total: total.toFixed(2), count }];"
      }
    },
    {
      "name": "Email Leadership",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "leadership@yourorg.org",
        "subject": "Monthly Donor Report — {{ $json.period }}",
        "message": "={{ $json.html }}",
        "options": { "bodyContentType": "html" }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Event Registration & Volunteer Coordination

Trigger: Typeform webhook on new event signup
What it does: Confirms registration, logs to Sheets, notifies coordinator in Slack, then sends an automated reminder 24 hours before the event.

{
  "name": "Event Registration & Volunteer Coordination",
  "nodes": [
    {
      "name": "Typeform Registration Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": { "path": "event-registration", "httpMethod": "POST" }
    },
    {
      "name": "Confirmation Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.email }}",
        "subject": "You're registered: {{ $json.event_name }}",
        "message": "Hi {{ $json.name }},\n\nYou're confirmed for {{ $json.event_name }} on {{ $json.event_date }} at {{ $json.event_location }}.\n\nWe'll send you a reminder 24 hours before. See you there!"
      }
    },
    {
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Registrations!A:D"
      }
    },
    {
      "name": "Notify Coordinator",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#events",
        "text": "New registration: {{ $json.name }} for {{ $json.event_name }}."
      }
    },
    {
      "name": "Wait 24h",
      "type": "n8n-nodes-base.wait",
      "parameters": { "amount": 24, "unit": "hours" }
    },
    {
      "name": "Send Event Reminder",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.email }}",
        "subject": "Reminder: {{ $json.event_name }} is tomorrow!",
        "message": "Hi {{ $json.name }},\n\nJust a reminder — {{ $json.event_name }} is tomorrow at {{ $json.event_location }}.\n\nWe look forward to seeing you!"
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The ROI Case

Task Manual Time/Week Automated
Donor thank-you emails 3h 0h
Volunteer reminders 2h 0h
Grant deadline check-ins 2h 0h
Monthly donor report 4h 0h
Event coordination 3h 0h
Total 14h/week 0h

At $25/hour staff cost, that's $350/week back in your budget — or $18,200/year.


Pre-Built Templates

Don't want to build from scratch? The FlowKit n8n Template Library has ready-to-import workflows for email automation, lead capture, daily reports, and more — all tested and documented.

FlowKit builds n8n automation templates for teams that need to move fast. Browse the library →

Top comments (0)