This workflow corresponds to n8n.io template #8956 — we link there as the canonical source.
This workflow follows the Gmail Trigger → 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": "JRe4TvueoMMH4FVd",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Create Zendesk Tickets from Gmail and Log to Google Sheets with Gmail, Zendesk, and Google Sheets",
"tags": [],
"nodes": [
{
"id": "b03d5eff-2f3b-4e15-a51e-23eb72c9e75c",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
0,
-192
],
"parameters": {
"simple": false,
"filters": {
"labelIds": [
"Label_7215267856143431312"
]
},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "85a31be9-8035-4b6c-b929-ef4f67888658",
"name": "Create Zendesk Ticket",
"type": "n8n-nodes-base.zendesk",
"position": [
432,
-192
],
"parameters": {
"description": "={{ $json.description }}",
"additionalFields": {
"tags": "={{ $json.priority }}",
"subject": "={{ $json.subject }}"
}
},
"credentials": {
"zendeskApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "581ccd96-d11d-436d-86d4-a4162da65f32",
"name": "Format Sheet Data",
"type": "n8n-nodes-base.code",
"position": [
656,
-192
],
"parameters": {
"jsCode": "// Prepare data for Google Sheets logging\nconst inputData = $input.first().json;\nconst ticketData = $node[\"Create Zendesk Ticket\"].json;\n\n// Extract Zendesk subdomain from the API URL or construct the agent URL\nlet agentTicketUrl = '';\nif (ticketData.url) {\n // Extract subdomain from API URL (e.g., https://softwarecompany-66332.zendesk.com/api/v2/tickets/123.json)\n const urlMatch = ticketData.url.match(/https:\\/\\/(.*?)\\.zendesk\\.com/);\n if (urlMatch) {\n const subdomain = urlMatch[1];\n agentTicketUrl = `https://${subdomain}.zendesk.com/agent/tickets/${ticketData.id}`;\n } else {\n // Fallback: construct from ticket ID (replace 'your-subdomain' with actual subdomain)\n agentTicketUrl = `https://softwarecompany-66332.zendesk.com/agent/tickets/${ticketData.id}`;\n }\n} else {\n // Fallback if no URL is provided\n agentTicketUrl = `https://softwarecompany-66332.zendesk.com/agent/tickets/${ticketData.id}`;\n}\n\nreturn {\n ticket_id: ticketData.id,\n ticket_url: agentTicketUrl,\n api_url: ticketData.url, // Keep original API URL for reference\n subject: ticketData.subject,\n requester_name: inputData.requester_name,\n requester_email: inputData.requester_email,\n source_channel: inputData.source,\n original_id: inputData.original_id,\n priority: ticketData.priority,\n status: ticketData.status,\n created_timestamp: new Date().toISOString(),\n zendesk_created_at: ticketData.created_at,\n description_preview: inputData.description.substring(0, 100) + (inputData.description.length > 100 ? '...' : ''),\n tags: ticketData.tags ? ticketData.tags.join(', ') : ''\n};"
},
"typeVersion": 2
},
{
"id": "076cea59-4b20-40b8-ab61-61c23c0e5b42",
"name": "Log to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
880,
-192
],
"parameters": {
"columns": {
"value": {
"Tags": "={{ $json.tags }}",
"Status": "={{ $json.status }}",
"Subject": "={{ $json.subject }}",
"Priority": "={{ $json.priority }}",
"Ticket ID": "={{ $json.ticket_id }}",
"Ticket URL": "={{ $json.ticket_url }}",
"Created Timestamp": "={{ $json.created_timestamp }}",
"Zendesk Created At": "={{ $json.zendesk_created_at }}",
"Description Preview": "={{ $json.description_preview }}"
},
"schema": [
{
"id": "Ticket ID",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Ticket ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Ticket URL",
"type": "string",
"display": true,
"required": false,
"displayName": "Ticket URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Subject",
"type": "string",
"display": true,
"required": false,
"displayName": "Subject",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Requester Name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Requester Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Requester Email",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Requester Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source Channel",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Source Channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Original ID",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Original ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Priority",
"type": "string",
"display": true,
"required": false,
"displayName": "Priority",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created Timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "Created Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Zendesk Created At",
"type": "string",
"display": true,
"required": false,
"displayName": "Zendesk Created At",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description Preview",
"type": "string",
"display": true,
"required": false,
"displayName": "Description Preview",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Tags",
"type": "string",
"display": true,
"required": false,
"displayName": "Tags",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Ticket ID"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pz1aP0OIULFoEJCI6VWged3jq1W7870btV2QOC4vn1o/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1pz1aP0OIULFoEJCI6VWged3jq1W7870btV2QOC4vn1o",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pz1aP0OIULFoEJCI6VWged3jq1W7870btV2QOC4vn1o/edit?usp=drivesdk",
"cachedResultName": "zendesk tickets"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4
},
{
"id": "033dae10-a4ed-435c-9fcf-67c911603189",
"name": "Normalize Gmail Data",
"type": "n8n-nodes-base.code",
"position": [
208,
-192
],
"parameters": {
"jsCode": "// Normalize data from Gmail\nif ($input.first().json.source === undefined) {\n const gmailData = $input.first().json;\n \n // Extract email and name from the 'from' object structure\n let requesterEmail = '';\n let requesterName = '';\n \n if (gmailData.from && gmailData.from.value && gmailData.from.value[0]) {\n requesterEmail = gmailData.from.value[0].address || '';\n requesterName = gmailData.from.value[0].name || gmailData.from.value[0].address || '';\n }\n \n // Use plain text first, fallback to textAsHtml if text not available, avoid html\n let description = '';\n if (gmailData.text) {\n description = gmailData.text;\n } else if (gmailData.textAsHtml) {\n // Strip HTML tags from textAsHtml to get plain text\n description = gmailData.textAsHtml.replace(/<[^>]*>/g, '').replace(/'/g, \"'\").replace(/"/g, '\"').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&');\n } else {\n description = 'No description provided';\n }\n \n // Check if it's urgent based on subject or content\n const isUrgent = (gmailData.subject && gmailData.subject.toLowerCase().includes('urgent')) || \n (description && description.toLowerCase().includes('urgent'));\n \n return {\n source: 'gmail',\n subject: gmailData.subject || 'No Subject',\n description: description,\n requester_email: requesterEmail,\n requester_name: requesterName,\n priority: isUrgent ? 'urgent' : 'normal',\n timestamp: new Date().toISOString(),\n original_id: gmailData.id,\n raw_data: gmailData\n };\n}\n\nreturn $input.first().json;"
},
"typeVersion": 2
},
{
"id": "4543e98f-003c-43ab-8638-24c5e7e4f3e8",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
-240
],
"parameters": {
"width": 272,
"height": 208,
"content": "## Gmail Trigger\nListens for new emails from the configured Gmail account/label and emits each message once with headers, subject, body (plain/HTML), sender, recipients, timestamp, and attachment metadata for downstream processing."
},
"typeVersion": 1
},
{
"id": "895dac87-ef0d-4285-9dcb-5573ad09a273",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
-480
],
"parameters": {
"width": 272,
"height": 208,
"content": "## Normalize Gmail Data\nTransforms the raw Gmail payload into a consistent schema (e.g., requesterEmail, subject, bodyText/bodyHtml, threadId, messageId, labels, attachments[]) to ensure reliable field mapping across subsequent nodes."
},
"typeVersion": 1
},
{
"id": "6c123420-9b3e-403e-81cd-1f731406156e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
352,
-16
],
"parameters": {
"width": 272,
"height": 224,
"content": "## Create Zendesk Ticket (create: ticket)\nCreates a Zendesk ticket using normalized fields: requester (email), subject, description (plain text or HTML), optional priority/tags/group/assignee. Outputs ticketId, ticketUrl, status, and requesterId for logging."
},
"typeVersion": 1
},
{
"id": "c9671dc8-391d-4579-a6b9-853946c169b5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
-496
],
"parameters": {
"width": 272,
"height": 224,
"content": "## Format Sheet Data\nBuilds a tidy row object combining Gmail and Zendesk outputs (e.g., timestamp, requesterEmail, subject, ticketId, ticketUrl, status, threadId, labels, workflowRunId). Ensures column order and default values match the Sheet schema."
},
"typeVersion": 1
},
{
"id": "dff72fb5-c0cb-478e-b9a9-5bde17971ecf",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
800,
-16
],
"parameters": {
"width": 272,
"height": 224,
"content": "## Log to Google Sheets \nWrites the row to the target spreadsheet\u2014appending new records or updating existing ones using a unique key (e.g., messageId or ticketId). Confirms success and surfaces any write conflicts or missing columns."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "4c9e7438-d87c-45ec-b738-a12e69424dc7",
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Normalize Gmail Data",
"type": "main",
"index": 0
}
]
]
},
"Format Sheet Data": {
"main": [
[
{
"node": "Log to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Normalize Gmail Data": {
"main": [
[
{
"node": "Create Zendesk Ticket",
"type": "main",
"index": 0
}
]
]
},
"Create Zendesk Ticket": {
"main": [
[
{
"node": "Format Sheet Data",
"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.
gmailOAuth2googleSheetsOAuth2ApizendeskApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Turn incoming Gmail messages into Zendesk tickets and keep a synchronized log in Google Sheets. Uses Gmail as the trigger, creates Zendesk tickets, and appends or updates a central sheet for tracking. Gain a clean, auditable pipeline from inbox to support queue. ✨ Fetches new…
Source: https://n8n.io/workflows/8956/ — 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.
Automatically convert Gmail emails and Slack messages into Zendesk support tickets with intelligent priority detection, comprehensive Google Sheets tracking, and real-time team notifications. Streamli
Receive any business document via email. The attachment is automatically classified (Invoice, Contract, or Purchase Order) using easybits Extractor, then routed down the correct path where a second Ex
The goal is to reduce inbox noise and automatically organize repetitive types of emails so that imprtant messages remain visible while unsolicited or promotional emails are handled automatically. When
📘 Description
This n8n workflow is designed for IT security professionals, email administrators, and organizations that want to automatically scan URLs received in emails for potential security threats. It provides