This workflow corresponds to n8n.io template #15876 — we link there as the canonical source.
This workflow follows the Gmail → Google Sheets recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "6EwJM2jSGk6rblwH",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Bug Resolution Notification System",
"tags": [
{
"id": "4Of2LE0HSt4qmmUG",
"name": "Ansh",
"createdAt": "2026-04-20T10:32:01.726Z",
"updatedAt": "2026-04-20T10:32:01.726Z"
}
],
"nodes": [
{
"id": "c2455f76-ed97-47a5-9f53-124e44da46b5",
"name": "Jira Trigger",
"type": "n8n-nodes-base.jiraTrigger",
"position": [
-2016,
64
],
"parameters": {
"events": [
"jira:issue_updated"
],
"additionalFields": {}
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "295d3fc4-43fe-4f06-9e38-2a8d41fb4454",
"name": "Filter: Bug Resolved?",
"type": "n8n-nodes-base.if",
"position": [
-1808,
64
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-issue-type",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.issue.fields.issuetype.name }}",
"rightValue": "Bug"
},
{
"id": "cond-status-resolved",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.issue.fields.status.name }}",
"rightValue": "Done"
},
{
"id": "cond-changelog-status",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ ($json.changelog?.items || []).some(i => i.field === 'status') }}",
"rightValue": "true"
}
]
}
},
"typeVersion": 2
},
{
"id": "63830dfe-a64f-439a-bda4-b05bf0141179",
"name": "Normalize & Enrich Payload",
"type": "n8n-nodes-base.code",
"position": [
-1456,
48
],
"parameters": {
"jsCode": "// \u2500\u2500\u2500 Normalize & Enrich Jira Trigger Payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst data = $input.first().json;\nconst issue = data.issue;\nconst fields = issue.fields;\nconst changelog = data.changelog;\n\n// Fix versions\nconst fixVersions = (fields.fixVersions || []).map(v => v.name).join(', ') || 'N/A';\n\n// Components\nconst components = (fields.components || []).map(c => c.name).join(', ') || 'General';\n\n// Labels\nconst labels = (fields.labels || []).join(', ') || 'None';\n\n// Customer email \u2014 falls back to reporter email\nconst customerEmail =\n fields.customfield_10050 ||\n fields.reporter?.emailAddress ||\n null;\n\n// Customer name\nconst customerName =\n fields.customfield_10051 ||\n fields.reporter?.displayName ||\n 'Valued Customer';\n\n// Resolution comment\nconst resolutionComment =\n fields.resolution?.description ||\n fields.customfield_10052 ||\n 'This issue has been fully resolved by our engineering team.';\n\n// Priority\nconst priority = fields.priority?.name || 'Medium';\n\n// Find status change from changelog\nconst statusChange = (changelog?.items || []).find(i => i.field === 'status');\nconst fromStatus = statusChange?.fromString || 'Unknown';\nconst toStatus = statusChange?.toString || 'Done';\n\n// Dates\nconst resolutionDate = new Date().toISOString();\nconst createdDate = fields.created || resolutionDate;\nconst daysOpen = Math.round(\n (new Date(resolutionDate) - new Date(createdDate)) / (1000 * 60 * 60 * 24)\n);\n\n// Assignee\nconst assignee = fields.assignee?.displayName || 'Engineering Team';\n\n// Sprint\nconst sprints = fields.customfield_10020 || fields.customfield_10021;\nconst sprintName = Array.isArray(sprints) && sprints.length > 0\n ? (sprints[sprints.length - 1]?.name || 'Current Sprint')\n : 'N/A';\n\n// Issue URL\nconst baseUrl = issue.self.split('/rest')[0];\nconst issueUrl = `${baseUrl}/browse/${issue.key}`;\n\nreturn [{\n json: {\n issueKey: issue.key,\n issueId: issue.id,\n issueUrl,\n summary: fields.summary,\n description: fields.description || 'No description provided.',\n priority,\n components,\n labels,\n fixVersions,\n sprintName,\n assignee,\n reporter: fields.reporter?.displayName || 'Unknown',\n fromStatus,\n toStatus,\n customerEmail,\n customerName,\n resolutionComment,\n resolutionDate,\n daysOpen,\n createdDate,\n hasCustomerEmail: !!customerEmail\n }\n}];"
},
"typeVersion": 2
},
{
"id": "6c09a9b0-c22f-4d2f-b15b-37d940f28648",
"name": "Deduplication Check",
"type": "n8n-nodes-base.code",
"position": [
-1088,
48
],
"parameters": {
"jsCode": "// \u2500\u2500\u2500 Deduplication via workflow static data (global scope) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst issueKey = $input.first().json.issueKey;\nconst storeKey = `resolved_${issueKey}`;\nconst store = $getWorkflowStaticData('global');\nconst already = store[storeKey] === true;\nif (!already) store[storeKey] = true;\nreturn [{ json: { ...$input.first().json, alreadyProcessed: already } }];\n"
},
"typeVersion": 2
},
{
"id": "1c6b3635-1f05-4cc3-8b1e-967c6e6e1304",
"name": "Already Processed?",
"type": "n8n-nodes-base.if",
"position": [
-896,
48
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-dedup",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.alreadyProcessed }}",
"rightValue": false
}
]
}
},
"typeVersion": 2
},
{
"id": "1277c1bc-4367-4683-bac6-f295ba5ab57b",
"name": "Has Customer Email?",
"type": "n8n-nodes-base.if",
"position": [
-496,
32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-has-email",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.hasCustomerEmail }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "481e120d-79b7-4f95-9efc-85105d7c649b",
"name": "Get Jira Issue Details",
"type": "n8n-nodes-base.jira",
"position": [
-144,
-96
],
"parameters": {
"issueKey": "={{ $json.issueKey }}",
"operation": "get",
"additionalFields": {
"fields": "comment,attachment,worklog"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5d2f477d-522c-4fb5-8343-52560bf7a08f",
"name": "Merge Jira + HubSpot Data",
"type": "n8n-nodes-base.merge",
"position": [
64,
0
],
"parameters": {},
"typeVersion": 3
},
{
"id": "1900c3e2-414e-4ace-ab70-c71feb09d940",
"name": "Merge Data & Build Templates",
"type": "n8n-nodes-base.code",
"position": [
240,
0
],
"parameters": {
"jsCode": "// \u2500\u2500\u2500 Get enriched data via node reference \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst enriched = $('Has Customer Email?').first().json;\n\n// \u2500\u2500\u2500 Get parallel fetch results \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst inputs = $input.all().map(i => i.json);\n\n// Jira details response has nested fields.comment\nconst jiraDetails = inputs.find(i =>\n i.hasOwnProperty('fields') && i.fields?.comment !== undefined\n) || {};\n\n// HubSpot response has 'results' array or 'total' property\nconst hsResult = inputs.find(i =>\n i.hasOwnProperty('results') || i.hasOwnProperty('total')\n) || {};\n\n// \u2500\u2500 Latest Jira comment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst comments = jiraDetails.fields?.comment?.comments || [];\nconst lastComment = comments.length > 0\n ? (\n comments[comments.length - 1]?.body?.content?.[0]?.content?.[0]?.text ||\n comments[comments.length - 1]?.body ||\n enriched.resolutionComment\n )\n : enriched.resolutionComment;\nconst safeComment = String(lastComment || 'This issue has been fully resolved.').substring(0, 600);\n\n// \u2500\u2500 HubSpot contact \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst hsContacts = Array.isArray(hsResult.results) ? hsResult.results\n : Array.isArray(hsResult) ? hsResult\n : [];\nconst hsContact = hsContacts[0] || null;\nconst hsContactId = hsContact?.id || null;\nconst hsFirstName = hsContact?.properties?.firstname || (enriched.customerName || '').split(' ')[0] || 'Customer';\nconst hsLastName = hsContact?.properties?.lastname || '';\nconst hsCompany = hsContact?.properties?.company || 'N/A';\n\n// \u2500\u2500 Email subject \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst emailSubject = `[RESOLVED] ${enriched.issueKey}: ${enriched.summary}`;\n\nconst resolvedDateFormatted = enriched.resolutionDate\n ? new Date(enriched.resolutionDate).toLocaleDateString('en-US', {\n year: 'numeric', month: 'long', day: 'numeric'\n })\n : new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\n\n// \u2500\u2500 HTML Email \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst emailHtml = `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<style>\n body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#1a1a2e;background:#f4f5f7;margin:0;padding:0}\n .wrap{max-width:620px;margin:36px auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 16px rgba(0,0,0,.09)}\n .hd{background:linear-gradient(135deg,#0052cc 0%,#0747a6 100%);padding:30px 40px}\n .hd h1{color:#fff;margin:0;font-size:22px;font-weight:600}\n .badge{display:inline-block;background:#00875a;color:#fff;font-size:11px;font-weight:700;padding:3px 11px;border-radius:100px;margin-top:8px;letter-spacing:.6px;text-transform:uppercase}\n .bd{padding:30px 40px}\n p{margin:0 0 14px;font-size:15px;line-height:1.6}\n .box{background:#f0f4ff;border-left:4px solid #0052cc;border-radius:4px;padding:14px 18px;margin:18px 0}\n .box h3{margin:0 0 6px;font-size:12px;color:#0052cc;text-transform:uppercase;letter-spacing:.5px}\n .box p{margin:0;font-size:15px;color:#2d3a5a}\n table{width:100%;border-collapse:collapse;margin:18px 0;font-size:14px}\n td{padding:8px 0;border-bottom:1px solid #eef0f6}\n td:first-child{color:#6b7894;width:44%}\n td:last-child{color:#1a1a2e;font-weight:500}\n .res{background:#e8f9f0;border-left:4px solid #00875a;border-radius:4px;padding:14px 18px;margin:18px 0}\n .res h3{margin:0 0 6px;font-size:12px;color:#00875a;text-transform:uppercase;letter-spacing:.5px}\n .res p{margin:0;font-size:15px;color:#1a4731}\n .cta{text-align:center;margin:26px 0 8px}\n .btn{display:inline-block;background:#0052cc;color:#fff;text-decoration:none;padding:12px 28px;border-radius:8px;font-weight:600;font-size:14px}\n .ft{background:#f4f5f7;padding:22px 40px;border-top:1px solid #eef0f6}\n .ft p{color:#8a9bbf;font-size:12px;margin:3px 0}\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n <div class=\"hd\"><h1>Bug Fix Resolved</h1><span class=\"badge\">✓ Resolved</span></div>\n <div class=\"bd\">\n <p>Hi ${enriched.customerName || 'Valued Customer'},</p>\n <p>We are pleased to confirm that the bug you reported has been <strong>fully resolved</strong>. Here is a summary of what was fixed:</p>\n <div class=\"box\"><h3>Issue Summary</h3><p>${enriched.summary || 'N/A'}</p></div>\n <table>\n <tr><td>Issue Key</td><td><a href=\"${enriched.issueUrl || '#'}\" style=\"color:#0052cc\">${enriched.issueKey || 'N/A'}</a></td></tr>\n <tr><td>Priority</td><td>${enriched.priority || 'N/A'}</td></tr>\n <tr><td>Fix Version</td><td>${enriched.fixVersions || 'N/A'}</td></tr>\n <tr><td>Component</td><td>${enriched.components || 'N/A'}</td></tr>\n <tr><td>Resolved By</td><td>${enriched.assignee || 'Engineering Team'}</td></tr>\n <tr><td>Days to Resolve</td><td>${enriched.daysOpen ?? 'N/A'} ${typeof enriched.daysOpen === 'number' ? (enriched.daysOpen !== 1 ? 'days' : 'day') : ''}</td></tr>\n <tr><td>Resolved On</td><td>${resolvedDateFormatted}</td></tr>\n </table>\n <div class=\"res\"><h3>Resolution Notes</h3><p>${safeComment}</p></div>\n <p>If you experience any related issues or have questions, please do not hesitate to reach out. We truly appreciate your patience.</p>\n <div class=\"cta\"><a href=\"${enriched.issueUrl || '#'}\" class=\"btn\">View Full Issue Details</a></div>\n </div>\n <div class=\"ft\">\n <p>You received this email because you are listed as a contact on Jira issue ${enriched.issueKey || 'N/A'}.</p>\n <p>This is an automated notification from our Engineering Operations system.</p>\n </div>\n</div>\n</body>\n</html>`;\n\n// \u2500\u2500 HubSpot note body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst hubspotNoteBody =\n`[BUG RESOLVED] ${enriched.issueKey}: ${enriched.summary}\n\nResolution Date : ${resolvedDateFormatted}\nPriority : ${enriched.priority}\nFix Version : ${enriched.fixVersions}\nComponent : ${enriched.components}\nResolved By : ${enriched.assignee}\nDays Open : ${enriched.daysOpen}\nSprint : ${enriched.sprintName}\n\nResolution Notes:\n${safeComment}\n\nJira Link: ${enriched.issueUrl}`;\n\nreturn [{\n json: {\n ...enriched,\n lastComment: safeComment,\n hsContactId,\n hsFirstName,\n hsLastName,\n hsCompany,\n hsContactFound: !!hsContactId,\n emailSubject,\n emailHtml,\n hubspotNoteBody,\n resolvedDateFormatted\n }\n}];"
},
"typeVersion": 2
},
{
"id": "af30024b-d6d7-4a39-a4bb-0909949acd89",
"name": "Send Gmail to Customer",
"type": "n8n-nodes-base.gmail",
"position": [
448,
0
],
"parameters": {
"sendTo": "={{ $json.customerEmail }}",
"message": "={{ $json.emailHtml }}",
"options": {
"replyTo": "user@example.com",
"appendAttribution": false
},
"subject": "={{ $json.emailSubject }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "09a56e3e-cc30-4e93-bfe0-11d77f62173a",
"name": "Add Jira Comment",
"type": "n8n-nodes-base.jira",
"position": [
624,
0
],
"parameters": {
"comment": "=\u2705 Issue Resolved & Customer Notified\n\nIssue: {{ $('Merge Data & Build Templates').item.json.issueKey }}\nSummary: {{ $('Merge Data & Build Templates').item.json.summary }}\n\n\ud83d\udc68\u200d\ud83d\udcbb Resolved By: {{ $('Merge Data & Build Templates').item.json.assignee }}\n\ud83d\udcc5 Resolved On: {{ $('Merge Data & Build Templates').item.json.resolvedDateFormatted }}\n\u23f1 Time Taken: {{ $('Merge Data & Build Templates').item.json.daysOpen }} days\n\n\ud83d\udce7 Customer Notification: Email sent successfully\n\n\ud83d\udcdd Resolution Notes:\n{{ $('Merge Data & Build Templates').item.json.lastComment }}\n\n\ud83d\udd17 View Issue: {{ $('Merge Data & Build Templates').item.json.issueUrl }}",
"options": {},
"issueKey": "={{ $('Merge Data & Build Templates').item.json.issueKey }}",
"resource": "issueComment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c611ab38-8f22-49e8-a724-498830b560c9",
"name": "Create/Update HubSpot Contact",
"type": "n8n-nodes-base.hubspot",
"position": [
800,
0
],
"parameters": {
"email": "={{ $('Merge Data & Build Templates').item.json.customerEmail }}",
"options": {},
"authentication": "appToken",
"additionalFields": {
"lastName": "={{ $('Merge Data & Build Templates').item.json.hsLastName }}",
"firstName": "={{ $('Merge Data & Build Templates').item.json.hsFirstName }}"
}
},
"credentials": {
"hubspotAppToken": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "6ad1ee7e-6165-40cf-aeb9-b2d0b684810f",
"name": "Slack Team Notification",
"type": "n8n-nodes-base.slack",
"position": [
1184,
0
],
"parameters": {
"text": "=\u2705 *Bug Successfully Resolved & Customer Notified* \n\n*Issue Details:* \n\u2022 Issue Key: {{ $('Merge Data & Build Templates').item.json.issueKey }} \n\u2022 Summary: {{ $('Merge Data & Build Templates').item.json.summary }} \n\u2022 Priority: {{ $('Merge Data & Build Templates').item.json.priority }} \n\u2022 Component: {{ $('Merge Data & Build Templates').item.json.components }} \n\u2022 Fix Version: {{ $('Merge Data & Build Templates').item.json.fixVersions }} \n\n\ud83d\udc68\u200d\ud83d\udcbb *Resolved By:* {{ $('Merge Data & Build Templates').item.json.assignee }} \n\ud83d\udcc5 *Resolved On:* {{ $('Merge Data & Build Templates').item.json.resolvedDateFormatted }} \n\u23f1 *Time Taken:* {{ $('Merge Data & Build Templates').item.json.daysOpen }} days \n\n\ud83d\udce7 *Customer Notification:* Sent successfully \n\ud83d\udcc7 *HubSpot Status:* {{ $('Merge Data & Build Templates').item.json.hsContactFound ? 'Contact Updated' : 'New Contact Created' }} \n\n\ud83d\udcdd *Latest Comment:* {{ $('Merge Data & Build Templates').item.json.lastComment }} \n\n\ud83d\udd17 *View Issue:* {{ $('Merge Data & Build Templates').item.json.issueUrl }} \n\n--- \n\ud83d\ude80 Great job team! Another issue closed and communicated successfully.",
"select": "",
"channelId": {
"__rl": true,
"mode": "",
"value": "",
"cachedResultName": ""
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "fc6c9840-96ca-48fe-a4df-0e96371ff173",
"name": "Log to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
1376,
0
],
"parameters": {
"columns": {
"value": {
"Sprint": "={{ $('Merge Data & Build Templates').item.json.sprintName }}",
"Summary": "={{ $('Merge Data & Build Templates').item.json.summary }}",
"Priority": "={{ $('Merge Data & Build Templates').item.json.priority }}",
"Component": "={{ $('Merge Data & Build Templates').item.json.components }}",
"Days Open": "={{ $('Merge Data & Build Templates').item.json.daysOpen }}",
"Issue Key": "={{ $('Merge Data & Build Templates').item.json.issueKey }}",
"Issue URL": "={{ $('Merge Data & Build Templates').item.json.issueUrl }}",
"Fix Version": "={{ $('Merge Data & Build Templates').item.json.fixVersions }}",
"Resolved By": "={{ $('Merge Data & Build Templates').item.json.assignee }}",
"Email Status": "=Sent",
"Customer Name": "={{ $('Merge Data & Build Templates').item.json.customerName }}",
"Customer Email": "={{ $('Merge Data & Build Templates').item.json.customerEmail }}",
"HubSpot Status": "={{ $('Merge Data & Build Templates').item.json.hsContactFound ? 'Contact Updated' : 'New Contact Created' }}",
"Resolution Date\t": "={{ $('Merge Data & Build Templates').item.json.resolutionDate }}"
},
"schema": [
{
"id": "Resolution Date\t",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Resolution Date\t",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Issue Key",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Issue Key",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Summary",
"type": "string",
"display": true,
"required": false,
"displayName": "Summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Priority",
"type": "string",
"display": true,
"required": false,
"displayName": "Priority",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Component",
"type": "string",
"display": true,
"required": false,
"displayName": "Component",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Fix Version",
"type": "string",
"display": true,
"required": false,
"displayName": "Fix Version",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Customer Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Customer Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Customer Email",
"type": "string",
"display": true,
"required": false,
"displayName": "Customer Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Resolved By",
"type": "string",
"display": true,
"required": false,
"displayName": "Resolved By",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Days Open",
"type": "string",
"display": true,
"required": false,
"displayName": "Days Open",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Sprint",
"type": "string",
"display": true,
"required": false,
"displayName": "Sprint",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Email Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "HubSpot Status",
"type": "string",
"display": true,
"required": false,
"displayName": "HubSpot Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Issue URL",
"type": "string",
"display": true,
"required": false,
"displayName": "Issue URL",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Issue Key"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"documentId": {
"__rl": true,
"mode": "",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "4e8b1caa-2c35-43d0-a445-6343ddc88488",
"name": "Log: No Email Found",
"type": "n8n-nodes-base.code",
"position": [
-128,
512
],
"parameters": {
"jsCode": "// No customer email on this Jira issue \u2014 flag for manual follow-up\nconst data = $input.first().json;\nconsole.warn(`[NO EMAIL] Issue ${data.issueKey} resolved but no customer email found.`);\nreturn [{ json: { ...data, requiresManualFollowUp: true, warningReason: 'No customer email on Jira issue' } }];\n"
},
"typeVersion": 2
},
{
"id": "58cc4366-67f1-483e-8387-a79a7414a8bd",
"name": "Alert: Manual Follow-Up",
"type": "n8n-nodes-base.slack",
"position": [
80,
512
],
"parameters": {
"text": "=\u26a0\ufe0f *Manual Follow-Up Required*\n\nA bug was marked as *Resolved* in Jira, but we could not notify the customer automatically.\n\n*Issue Details:*\n\u2022 Issue: {{$json.issueKey}}\n\u2022 Summary: {{$json.summary}}\n\u2022 Priority: {{$json.priority}}\n\u2022 Days Open: {{$json.daysOpen}}\n\n\ud83d\udea8 *Reason:*\nNo customer email found in Jira ticket.\n\n\ud83d\udc49 *Action Required:*\nPlease reach out to the customer manually via phone or email.\n\n\ud83d\udd17 {{$json.issueUrl}}",
"select": "",
"channelId": {
"__rl": true,
"mode": "",
"value": "",
"cachedResultName": ""
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "175077a6-2334-4c88-922b-cfbedef17323",
"name": "Search HubSpot Contact",
"type": "n8n-nodes-base.httpRequest",
"position": [
-144,
96
],
"parameters": {
"url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: $('Has Customer Email?1').first().json.customerEmail }] }], properties: ['firstname', 'lastname', 'email', 'company'], limit: 1 }) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "afe8820f-ead2-433f-a9c7-f0060fd602ea",
"name": "Log HubSpot Note",
"type": "n8n-nodes-base.httpRequest",
"position": [
992,
0
],
"parameters": {
"url": "https://api.hubapi.com/crm/v3/objects/notes",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "properties.hs_note_body",
"value": "={{ $('Merge Data & Build Templates1').item.json.hubspotNoteBody }}"
},
{
"name": "properties.hs_timestamp",
"value": "={{ new Date().toISOString() }}"
},
{
"name": "associations.0.to.id",
"value": "={{ $json.vid }}"
},
{
"name": "associations.0.types.0.associationCategory",
"value": "HUBSPOT_DEFINED"
},
{
"name": "associations.0.types.0.associationTypeId",
"value": "202"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "a726f3d9-19ab-445a-814f-10f16c9f8070",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2640,
-288
],
"parameters": {
"width": 464,
"height": 560,
"content": "## Bug Resolution Notification System\nThis workflow works when a problem (bug) is marked as solved. It checks if the problem is really fixed and then informs the customer. It also updates company records and informs the team. If customer details are missing, it alerts the team for manual action.\n\n### How It Works\n\n\t\u2022\tIt detects when a problem is updated.\n\t\u2022\tIt checks if the problem is a bug and marked as solved.\n\t\u2022\tIt prepares all important information about the problem.\n\t\u2022\tIt checks if this problem was already handled before.\n\t\u2022\tIt checks if customer contact details are available.\n\t\u2022\tIf details are available \u2192 sends update to customer.\n\t\u2022\tIf details are missing \u2192 alerts team to handle manually.\n\n### Setup Steps\n\n\t1.\tConnect your bug tracking system.\n\t2.\tConnect email service to send messages.\n\t3.\tConnect customer management system.\n\t4.\tConnect team communication tool.\n\t5.\tConnect a sheet to store records."
},
"typeVersion": 1
},
{
"id": "39dfcd29-422f-4c11-8d75-16840d12c551",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2144,
-288
],
"parameters": {
"color": 7,
"width": 496,
"height": 560,
"content": "## Step 1: Detect & Verify Bug Fix\n\n\t\u2022\tThis step starts the process when a problem is updated. \n\t\u2022\tIt checks whether the issue is actually a bug and marked as solved. \n\t\u2022\tIf both conditions are true, the process continues.\n\t\u2022\tOtherwise, it stops here."
},
"typeVersion": 1
},
{
"id": "228552c9-5917-4534-b904-77a331f6439f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
-288
],
"parameters": {
"color": 7,
"width": 432,
"height": 560,
"content": "## Step 2: Prepare & Clean Information\n\n\t\u2022\tThis step collects all details about the problem.\n\t\u2022\tIt organizes information like name, priority, and description.\n\t\u2022\tIt also fills missing details with default values.\n\t\u2022\tThis makes the data easy to use in later steps."
},
"typeVersion": 1
},
{
"id": "be33c190-aa46-47db-a6e8-e5c629c6824d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1152,
-288
],
"parameters": {
"color": 7,
"width": 496,
"height": 560,
"content": "## Step 3: Avoid Duplicate Work\n\n\t\u2022\tThis step checks if the same problem was already processed before.\n\t\u2022\tIf it was already handled, the workflow stops to avoid repetition.\n\t\u2022\tIf not, it continues further.\n\t\u2022\tThis helps prevent sending duplicate messages."
},
"typeVersion": 1
},
{
"id": "3859a3ed-89ea-43ec-9389-141bd6a1a502",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
-288
],
"parameters": {
"color": 7,
"width": 400,
"height": 560,
"content": "## Step 4: Check Customer Contact\n\n\t\u2022\tThis step checks if customer email is available.\n\t\u2022\tIf email exists, the process continues normally.\n\t\u2022\tIf email is missing, it takes a different path.\n\t\u2022\tThis ensures proper communication handling."
},
"typeVersion": 1
},
{
"id": "71ca7ec6-5439-4386-98db-8baa21d71228",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-192,
-288
],
"parameters": {
"color": 7,
"width": 560,
"height": 560,
"content": "## Step 5: Collect Extra Details & Create Message\n\n\t\u2022\tThis step gathers more information about the problem.\n\t\u2022\tIt prepares a message to send to the customer.\n\t\u2022\tIt also prepares notes for internal records.\n\t\u2022\tEverything is formatted in a clear and readable way."
},
"typeVersion": 1
},
{
"id": "b7d6729e-9cb3-4fe5-9fe3-c4f72c56d2a5",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
-288
],
"parameters": {
"color": 7,
"width": 1152,
"height": 560,
"content": "## Step 6: Send Updates & Notify Everyone\n\n\t\u2022\tThis step sends an email to the customer about the fix.\n\t\u2022\tIt updates the problem with a note saying it is resolved.\n\t\u2022\tIt updates customer records in the system.\n\t\u2022\tIt also informs the team and saves the data in a sheet."
},
"typeVersion": 1
},
{
"id": "6f403c2e-45f5-4245-8798-a71f4321468e",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-192,
304
],
"parameters": {
"color": 7,
"width": 560,
"height": 384,
"content": "## Step 5: Handle Missing Customer Info\n\n\t\u2022\tIf customer email is not available, this step runs.\n\t\u2022\tIt logs the issue and marks it for manual follow-up.\n\t\u2022\tIt sends an alert to the team.\n\t\u2022\tThe team can then contact the customer manually."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "30ed53ed-a85d-4183-b2e2-4144fef2e4c3",
"connections": {
"Jira Trigger": {
"main": [
[
{
"node": "Filter: Bug Resolved?",
"type": "main",
"index": 0
}
]
]
},
"Add Jira Comment": {
"main": [
[
{
"node": "Create/Update HubSpot Contact",
"type": "main",
"index": 0
}
]
]
},
"Log HubSpot Note": {
"main": [
[
{
"node": "Slack Team Notification",
"type": "main",
"index": 0
}
]
]
},
"Already Processed?": {
"main": [
[
{
"node": "Has Customer Email?",
"type": "main",
"index": 0
}
]
]
},
"Deduplication Check": {
"main": [
[
{
"node": "Already Processed?",
"type": "main",
"index": 0
}
]
]
},
"Has Customer Email?": {
"main": [
[
{
"node": "Get Jira Issue Details",
"type": "main",
"index": 0
},
{
"node": "Search HubSpot Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "Log: No Email Found",
"type": "main",
"index": 0
}
]
]
},
"Log: No Email Found": {
"main": [
[
{
"node": "Alert: Manual Follow-Up",
"type": "main",
"index": 0
}
]
]
},
"Filter: Bug Resolved?": {
"main": [
[
{
"node": "Normalize & Enrich Payload",
"type": "main",
"index": 0
}
]
]
},
"Get Jira Issue Details": {
"main": [
[
{
"node": "Merge Jira + HubSpot Data",
"type": "main",
"index": 0
}
]
]
},
"Search HubSpot Contact": {
"main": [
[
{
"node": "Merge Jira + HubSpot Data",
"type": "main",
"index": 1
}
]
]
},
"Send Gmail to Customer": {
"main": [
[
{
"node": "Add Jira Comment",
"type": "main",
"index": 0
}
]
]
},
"Slack Team Notification": {
"main": [
[
{
"node": "Log to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Merge Jira + HubSpot Data": {
"main": [
[
{
"node": "Merge Data & Build Templates",
"type": "main",
"index": 0
}
]
]
},
"Normalize & Enrich Payload": {
"main": [
[
{
"node": "Deduplication Check",
"type": "main",
"index": 0
}
]
]
},
"Merge Data & Build Templates": {
"main": [
[
{
"node": "Send Gmail to Customer",
"type": "main",
"index": 0
}
]
]
},
"Create/Update HubSpot Contact": {
"main": [
[
{
"node": "Log HubSpot Note",
"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.
gmailOAuth2googleSheetsOAuth2ApihttpHeaderAuthhubspotAppTokenjiraSoftwareCloudApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically handles every resolved Jira bug by verifying the fix, notifying the customer, updating HubSpot, commenting on the Jira issue, alerting the team on Slack, and logging everything to Google Sheets — all without any manual effort. If customer contact…
Source: https://n8n.io/workflows/15876/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow triggers when a new deal is created in HubSpot, sends a two-step follow-up sequence via Gmail, checks HubSpot for a recent email reply, then updates the deal stage, alerts a Slack channe
📘 Description
This workflow automatically handles every failed Stripe payment by collecting all details, assessing severity, and taking immediate action — alerting your team, creating recovery tasks, escalating rep
This workflow runs daily to pull cloud spend from a billing API, compare it to a Google Sheets rolling baseline, and alert on cost spikes by creating a Jira incident, posting to Slack, emailing Financ
Fetches all open sprint tickets daily from your Jira project Analyzes each ticket for overdue days and blocked status Routes to the right escalation level: assignee email → team Google Chat alert → ma