AutomationFlowsEmail & Gmail › Automated Sprint Reports From Jira to Stakeholders via Gmail

Automated Sprint Reports From Jira to Stakeholders via Gmail

ByYassin Zehar @yassinzehar on n8n.io

Automated workflow that generates a Sprint Report from Jira and delivers it by Gmail.

Cron / scheduled trigger★★★★☆ complexity11 nodesGmailJira
Email & Gmail Trigger: Cron / scheduled Nodes: 11 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #8932 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "4b7zzdwKKLNoIiMy",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Generate Jira automatic sprint report for Gmail",
  "tags": [
    {
      "id": "3T7uxjPBNibzqJlE",
      "name": "Google",
      "createdAt": "2025-08-18T07:15:32.460Z",
      "updatedAt": "2025-08-18T07:15:32.460Z"
    },
    {
      "id": "7awkAmV9dEBwVz7l",
      "name": "sprint report",
      "createdAt": "2025-09-25T16:00:40.741Z",
      "updatedAt": "2025-09-25T16:00:40.741Z"
    },
    {
      "id": "EYNbAs3Q9sg9NksG",
      "name": "Jira",
      "createdAt": "2025-09-10T10:47:53.750Z",
      "updatedAt": "2025-09-10T10:47:53.750Z"
    },
    {
      "id": "eAzNESqLUWwWMrE9",
      "name": "gmail",
      "createdAt": "2025-08-18T07:16:22.709Z",
      "updatedAt": "2025-08-18T07:16:22.709Z"
    }
  ],
  "nodes": [
    {
      "id": "e63fae5a-8ccf-4812-bbc8-05e30cd99eb1",
      "name": "Email notification",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1312,
        0
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=Hello, \nThis is your current sprint report\n\n{{$json.html}}\n<p><a href=\"https://yourdomain.atlassian.net/jira/software/c/projects/TES/boards/3\">See the sprint in Jira</a></p>\n",
        "options": {},
        "subject": "=Your Sprint Report"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": false
    },
    {
      "id": "21fed4c4-f686-4fc1-8255-32c76382b377",
      "name": "Get many issues",
      "type": "n8n-nodes-base.jira",
      "position": [
        416,
        0
      ],
      "parameters": {
        "options": {
          "jql": "project = your project AND sprint in openSprints()",
          "fields": "summary,status,assignee,priority,issuetype,labels,created,updated,customfield_10016,customfield_10020"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c46a19f2-7c08-44a4-9e91-60d32b9c31ae",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        -464
      ],
      "parameters": {
        "color": 3,
        "width": 304,
        "height": 176,
        "content": "## Required\n\n\u2022 Jira Cloud project (API email + API token)\n\u2022 Gmail credential"
      },
      "typeVersion": 1
    },
    {
      "id": "a4db27d8-d430-4798-8ffc-c8e71d414897",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        -464
      ],
      "parameters": {
        "width": 640,
        "height": 704,
        "content": "## 1) Trigger and Normalize \n\nTrigger: Scheduled run (e.g., every Friday at 17:00)\n\nData Processing:\n- Fetch issues from Jira with JQL: project = <KEY> AND sprint in openSprints()\n- Select relevant fields: summary, status, assignee, priority, story points, sprint, created, updated\n- Validate presence of critical fields (story points, sprint info)\n- Normalize empty values (e.g., \u201cUnassigned\u201d, \u201cNot defined\u201d)\n\nBest Practices:\nPerform validation and normalization here so later metrics and reports don\u2019t break if a field is missing."
      },
      "typeVersion": 1
    },
    {
      "id": "3c26641b-e1e7-46d9-97f4-03d44934ec43",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -464
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 704,
        "content": "## 2) Calculate Metrics\n\nAction: Aggregate data into KPIs\n\nKey Metrics:\n- Issues by status (To Do / In Progress / Done)\n- Story points: completed vs total\n- Blockers count (priority = High / label = blocker)\n- Sprint timeline (start / end date)\n\nBest Practices:\nKeep calculations in a dedicated node to make debugging easier and allow quick adjustments (e.g., adding new KPIs)."
      },
      "typeVersion": 1
    },
    {
      "id": "5ddc63f8-f59c-4b08-a5f4-c3b7a35e7d45",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        -464
      ],
      "parameters": {
        "color": 4,
        "width": 464,
        "height": 704,
        "content": "## 3) Generate Report & Send Notification\n\nAction: Build an HTML report\n\nContents:\n- Sprint name + dates\n- Metrics overview (done vs total issues, SP, blockers)\n- Table of all issues (key, summary, status, assignee, priority, SP)\n- Links to Jira tickets\n\nBest Practices:\nUse a clean HTML template with inline CSS for email compatibility.\n\n \n\nAction: Send reporting email\n\nRecipients: Person who needs it\n\nInformation: include all information relevant to the sprint status\n"
      },
      "typeVersion": 1
    },
    {
      "id": "7d410e39-680b-483c-af58-43a86eb0e3dd",
      "name": "Weekly trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                5
              ],
              "triggerAtHour": 17
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "cc836891-9da3-45eb-805e-2e1875332ac6",
      "name": "Jira & email configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        208,
        0
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"values\": {\n    \"jiraBaseUrl\": \"https://yourdomain.atlassian.net\",\n    \"jiraEmail\": \"user@example.com\",\n    \"jiraApiToken\": \"ATATT3xFfGF0p65qwDvypM5e-...\",\n    \"projectKey\": \"yourkey\",\n    \"emailRecipients\": \"user@example.com\"\n  }\n}\n"
      },
      "typeVersion": 3.4
    },
    {
      "id": "6ace04d2-c0dd-494e-8c6a-6c96fca7490c",
      "name": "Validation & error handling",
      "type": "n8n-nodes-base.code",
      "position": [
        672,
        0
      ],
      "parameters": {
        "jsCode": "const SP = 'customfield_10016';\nconst SPRINT = 'customfield_10020';\n\nconst items = $input.all();\n\nconst out = items.map(i => {\n  const it = i.json || {};\n  const f = it.fields || {};\n  const sprintObj = Array.isArray(f[SPRINT]) ? f[SPRINT][0] : f[SPRINT]; // souvent tableau\n\n  return {\n    key: it.key,\n    summary: f.summary || '',\n    status: f.status?.name || '\u2014',\n    statusCategory: f.status?.statusCategory?.name || '\u2014',\n    assignee: f.assignee?.displayName || 'Unassigned',\n    priority: f.priority?.name || 'Undefined',\n    sp: Number(f[SP] || 0),\n\n    sprint: sprintObj?.name || '\u2014',\n    sprintStart: sprintObj?.startDate || null,\n    sprintEnd: sprintObj?.endDate || null,\n\n    created: f.created || null,\n    updated: f.updated || null,\n    labels: f.labels || [],\n  };\n});\n\nreturn out.map(x => ({ json: x }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b86c2c98-b12c-410a-a837-46c6d974d3ba",
      "name": "Metrics calculation",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        0
      ],
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nif (!rows.length) return [{ json: { isEmpty: true, message: 'Aucun ticket' } }];\n\nconst low = s => (s||'').toLowerCase();\n\nconst total = rows.length;\nconst done = rows.filter(r => low(r.status) === 'done').length;\nconst inProgress = rows.filter(r => low(r.status) === 'in progress').length;\nconst toDo = rows.filter(r => low(r.status) === 'to do').length;\n\nconst spTotal = rows.reduce((a,r)=>a+(r.sp||0),0);\nconst spDone  = rows.filter(r=>low(r.status)==='done').reduce((a,r)=>a+(r.sp||0),0);\n\nconst blockers = rows.filter(r =>\n  (r.priority||'').toLowerCase()==='highest' ||\n  /blocked|impediment/i.test(r.status) ||\n  (r.labels||[]).map(low).includes('blocked')\n).length;\n\nconst firstWithSprint = rows.find(r => r.sprint && r.sprint !== '\u2014');\nconst sprintName = firstWithSprint?.sprint || 'Sprint en cours';\nconst sprintEnd = firstWithSprint?.sprintEnd || null;\n\nreturn [{\n  json: {\n    sprintName, sprintEnd,\n    counts: { total, done, inProgress, toDo },\n    storyPoints: { total: spTotal, done: spDone, rate: spTotal ? Math.round(100*spDone/spTotal) : 0 },\n    blockers,\n    issues: rows,\n    reportDate: new Date().toLocaleDateString('fr-FR'),\n    reportTime: new Date().toLocaleTimeString('fr-FR'),\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "579c77db-15fb-4ae3-a523-0df994024737",
      "name": "HTML report generation",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        0
      ],
      "parameters": {
        "jsCode": "const d = $json;\n\nfunction row(t){\n  return `<tr>\n    <td><a href=\"https:/yourdomain.atlassian.net/browse/${t.key}\">${t.key}</a></td>\n    <td>${t.summary || ''}</td>\n    <td>${t.status || ''}</td>\n    <td>${t.assignee || 'Unassigned'}</td>\n    <td>${t.priority || 'Undefined'}</td>\n    <td style=\"text-align:right\">${t.sp || 0}</td>\n  </tr>`;\n}\n\nconst table = `<table border=\"1\" cellspacing=\"0\" cellpadding=\"6\" style=\"border-collapse:collapse;width:100%\">\n  <tr>\n    <th>Key</th>\n    <th>Summary</th>\n    <th>Status</th>\n    <th>Assignee</th>\n    <th>Priority</th>\n    <th>SP</th>\n  </tr>\n  ${(d.issues || []).map(row).join('')}\n</table>`;\n\nconst header = `<p><b>Tickets</b>: ${d.counts.done}/${d.counts.total}\n \u2014 <b>Story Points</b>: ${d.storyPoints.done}/${d.storyPoints.total} (${d.storyPoints.rate}%)\n \u2014 <b>Blockers</b>: ${d.blockers}</p>`;\n\nconst meta = `<p style=\"color:#6b7280\">\nGenerated on ${d.reportDate} ${d.reportTime}${\n  d.sprintEnd ? ` \u2014 Planned end: ${new Date(d.sprintEnd).toLocaleDateString('en-GB')}` : ''\n}</p>`;\n\nreturn [{ json: { html: `<h2>Sprint Report \u2014 ${d.sprintName}</h2>${meta}${header}${table}` } }];\n"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "7e8eaceb-500f-431c-92dc-bcdf5e39c926",
  "connections": {
    "Weekly trigger": {
      "main": [
        [
          {
            "node": "Jira & email configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many issues": {
      "main": [
        [
          {
            "node": "Validation & error handling",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Metrics calculation": {
      "main": [
        [
          {
            "node": "HTML report generation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML report generation": {
      "main": [
        [
          {
            "node": "Email notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jira & email configuration": {
      "main": [
        [
          {
            "node": "Get many issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validation & error handling": {
      "main": [
        [
          {
            "node": "Metrics calculation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Automated workflow that generates a Sprint Report from Jira and delivers it by Gmail.

Source: https://n8n.io/workflows/8932/ — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

Streamline IT and operations change management by automating approval routing, Jira issue creation, audit logging, and real-time Slack alerts. This workflow ensures faster reviews, traceable approvals

Monday.com, Slack, Jira +2
Email & Gmail

Streamline IT and operations change management by automating approval routing, Jira issue creation, audit logging, and real-time Slack alerts. This workflow ensures faster reviews, traceable approvals

Monday.com, Slack, Jira +2
Email & Gmail

Keep your product and project teams perfectly aligned by automatically syncing task dependencies between Jira and Monday.com. This workflow ensures real-time visibility into cross-platform blockers an

Monday.com, Gmail, Jira +1
Email & Gmail

YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.

Gmail, Google Drive, Google Sheets +1
Email & Gmail

The FamilyFlow Assistant is your n8n-powered 🚀 companion designed to streamline daily parenting tasks, reduce mental load, and bring a bit more organization and fun into your family life. This versati

Start, Email Send, Telegram Trigger +2