This workflow follows the Datatable → Gmail 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 →
{
"name": "Track equipment checkouts and returns in Data Tables with Gmail receipts and Slack alerts",
"nodes": [
{
"parameters": {
"formTitle": "Equipment Checkout and Return",
"formDescription": "Check equipment out or back in. For checkouts, set the expected return date. If left blank it defaults to 7 days from today.",
"formFields": {
"values": [
{
"fieldLabel": "Action",
"fieldType": "dropdown",
"fieldOptions": {
"values": [
{
"option": "Checkout"
},
{
"option": "Return"
}
]
},
"requiredField": true
},
{
"fieldLabel": "Asset Tag",
"placeholder": "e.g. LT-0042",
"requiredField": true
},
{
"fieldLabel": "Your Email",
"fieldType": "email",
"placeholder": "you@example.com",
"requiredField": true
},
{
"fieldLabel": "Expected Return Date",
"fieldType": "date"
}
]
},
"options": {
"appendAttribution": false,
"buttonLabel": "Submit",
"path": "equipment-ledger",
"ignoreBots": true
}
},
"id": "7ed85d87-6871-44d8-b4d6-dfc56521e354",
"name": "Equipment Lending Form",
"type": "n8n-nodes-base.formTrigger",
"typeVersion": 2.6,
"position": [
64,
-128
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "cfg-slack-channel",
"name": "slackChannelId",
"value": "<__PLACEHOLDER_VALUE__SLACK_CHANNEL_ID__>",
"type": "string"
},
{
"id": "cfg-team-email",
"name": "assetTeamEmail",
"value": "<__PLACEHOLDER_VALUE__ASSET_TEAM_EMAIL__>",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"id": "1f61616b-9708-4bd1-94d6-427deb7bf060",
"name": "Workflow Configuration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
352,
-128
]
},
{
"parameters": {
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "Lending History",
"cachedResultName": null
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"assetTag": "={{ $('Equipment Lending Form').item.json['Asset Tag'] }}",
"submittedAction": "={{ $('Equipment Lending Form').item.json.Action }}",
"personEmail": "={{ $('Equipment Lending Form').item.json['Your Email'] }}",
"expectedReturnDate": "={{ $('Equipment Lending Form').item.json['Expected Return Date'] || '' }}",
"submittedAt": "={{ $now.toISO() }}"
},
"schema": [
{
"id": "assetTag",
"displayName": "assetTag",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "submittedAction",
"displayName": "submittedAction",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "personEmail",
"displayName": "personEmail",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "expectedReturnDate",
"displayName": "expectedReturnDate",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "submittedAt",
"displayName": "submittedAt",
"required": false,
"defaultMatch": false,
"display": true,
"type": "date",
"canBeUsedToMatch": true
}
]
},
"options": {}
},
"id": "8037485a-dd0e-4978-bbfa-650588518888",
"name": "Log Attempt to History",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
624,
-128
]
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "Asset Registry",
"cachedResultName": null
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "assetTag",
"keyValue": "={{ $('Equipment Lending Form').item.json['Asset Tag'] }}"
}
]
},
"returnAll": true
},
"id": "d69714a6-46f5-479f-a483-aa96c0e3cb10",
"name": "Read Asset Registry Row",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1024,
-128
],
"alwaysOutputData": true
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose",
"version": 1
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $('Equipment Lending Form').item.json.Action }}",
"operator": {
"type": "string",
"operation": "equals"
},
"rightValue": "Checkout"
},
{
"leftValue": "={{ $json.holderEmail }}",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
]
},
"renameOutput": true,
"outputKey": "Already Out"
},
{
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose",
"version": 1
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $('Equipment Lending Form').item.json.Action }}",
"operator": {
"type": "string",
"operation": "equals"
},
"rightValue": "Checkout"
}
]
},
"renameOutput": true,
"outputKey": "Checkout OK"
},
{
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose",
"version": 1
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $('Equipment Lending Form').item.json.Action }}",
"operator": {
"type": "string",
"operation": "equals"
},
"rightValue": "Return"
},
{
"leftValue": "={{ $json.holderEmail }}",
"operator": {
"type": "string",
"operation": "equals"
},
"rightValue": "={{ $('Equipment Lending Form').item.json['Your Email'] }}"
}
]
},
"renameOutput": true,
"outputKey": "Return Match"
},
{
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose",
"version": 1
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $('Equipment Lending Form').item.json.Action }}",
"operator": {
"type": "string",
"operation": "equals"
},
"rightValue": "Return"
}
]
},
"renameOutput": true,
"outputKey": "Return Mismatch"
}
]
},
"options": {
"ignoreCase": true,
"allMatchingOutputs": false
}
},
"id": "5f67d8a1-e18f-40bc-868d-d024b26376c7",
"name": "Route Lending Outcome",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
1264,
-160
]
},
{
"parameters": {
"operation": "completion",
"completionTitle": "Asset already checked out",
"completionMessage": "={{ $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] }} is currently out with {{ $(\"Read Asset Registry Row\").item.json.holderEmail }} and due back {{ String($(\"Read Asset Registry Row\").item.json.dueDate).slice(0, 10) }}. It was NOT issued to you. If you need it sooner, contact {{ $(\"Workflow Configuration\").item.json.assetTeamEmail }}.",
"options": {}
},
"id": "5e42cd0a-bdc3-4397-88dc-2f2ba91cbfab",
"name": "Show Already Out Rejection",
"type": "n8n-nodes-base.form",
"typeVersion": 2.5,
"position": [
1792,
-272
]
},
{
"parameters": {
"operation": "upsert",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "Asset Registry",
"cachedResultName": null
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "assetTag",
"keyValue": "={{ $('Equipment Lending Form').item.json['Asset Tag'] }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"assetTag": "={{ $('Equipment Lending Form').item.json['Asset Tag'] }}",
"holderEmail": "={{ $('Equipment Lending Form').item.json['Your Email'] }}",
"dueDate": "={{ $('Equipment Lending Form').item.json['Expected Return Date'] ? DateTime.fromISO($('Equipment Lending Form').item.json['Expected Return Date']).toISO() : $today.plus({ days: 7 }).toISO() }}",
"status": "out"
},
"schema": [
{
"id": "assetTag",
"displayName": "assetTag",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "holderEmail",
"displayName": "holderEmail",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "dueDate",
"displayName": "dueDate",
"required": false,
"defaultMatch": false,
"display": true,
"type": "date",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
]
},
"options": {}
},
"id": "93e91426-e6e3-405b-9ef8-d7a65e3b38da",
"name": "Record Checkout in Registry",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1792,
-64
]
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "Asset Registry",
"cachedResultName": null
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "holderEmail",
"keyValue": "={{ $('Equipment Lending Form').item.json['Your Email'] }}"
},
{
"keyName": "dueDate",
"condition": "lt",
"keyValue": "={{ $today.toISO() }}"
}
]
},
"returnAll": true
},
"id": "7b74e717-3b33-4019-9b12-7147b3e66b66",
"name": "Find Overdue Items for Person",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
2368,
-64
],
"alwaysOutputData": true
},
{
"parameters": {
"sendTo": "={{ $('Equipment Lending Form').item.json['Your Email'] }}",
"subject": "={{ $(\"Equipment Lending Form\").item.json.Action === \"Checkout\" ? \"Checkout receipt\" : \"Return confirmation\" }}: {{ $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] }}",
"emailType": "text",
"message": "=Hello,\n\n{{ $(\"Equipment Lending Form\").item.json.Action === \"Checkout\" ? \"Asset \" + $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] + \" is checked out to you. Due back: \" + ($(\"Equipment Lending Form\").item.json[\"Expected Return Date\"] || $today.plus({ days: 7 }).toISODate()) + \".\" : \"Asset \" + $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] + \" has been returned. Thank you.\" }}\n\nOther items still out to you and past due:\n{{ $(\"Find Overdue Items for Person\").all().filter(function(i){ return i.json.assetTag }).map(function(i){ return \"- \" + i.json.assetTag + \", due \" + String(i.json.dueDate).slice(0, 10) }).join(\"\\n\") || \"Nothing else you hold is past due.\" }}\n\nQuestions: {{ $(\"Workflow Configuration\").item.json.assetTeamEmail }}",
"options": {
"appendAttribution": false,
"senderName": "Equipment Desk"
}
},
"id": "ce57fdc0-bd25-4451-8d8a-0e559b5f55b8",
"name": "Email Receipt with Overdue List",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
2640,
-64
],
"executeOnce": true,
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"operation": "completion",
"completionTitle": "={{ $(\"Equipment Lending Form\").item.json.Action === \"Checkout\" ? \"Checkout recorded\" : \"Return recorded\" }}",
"completionMessage": "={{ $(\"Equipment Lending Form\").item.json.Action === \"Checkout\" ? \"Asset \" + $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] + \" is now checked out to you.\" : \"Asset \" + $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] + \" is checked back in.\" }} A receipt was emailed to {{ $(\"Equipment Lending Form\").item.json[\"Your Email\"] }}, including anything you still hold past due.",
"options": {}
},
"id": "1b696761-876c-4292-aef8-da0f1ac11270",
"name": "Show Completion Confirmation",
"type": "n8n-nodes-base.form",
"typeVersion": 2.5,
"position": [
2928,
-64
]
},
{
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "Asset Registry",
"cachedResultName": null
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "assetTag",
"keyValue": "={{ $('Equipment Lending Form').item.json['Asset Tag'] }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"holderEmail": "",
"status": "available"
},
"schema": [
{
"id": "holderEmail",
"displayName": "holderEmail",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
]
},
"options": {}
},
"id": "12e51877-19a3-45b1-9d21-f1c414d9eb54",
"name": "Clear Holder in Registry",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
1792,
160
]
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Configuration').item.json.slackChannelId }}"
},
"text": "=Return mismatch on asset {{ $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] }}.\nAttempted by: {{ $(\"Equipment Lending Form\").item.json[\"Your Email\"] }}\nRecorded holder: {{ $(\"Read Asset Registry Row\").item.json.holderEmail || \"no holder on record\" }}\nThe attempt is already logged in Lending History. The registry row was not changed.",
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"id": "9e0c8fc6-79c3-4ec8-ab60-a5446217c9a2",
"name": "Alert Return Mismatch in Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.5,
"position": [
1696,
384
],
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"operation": "completion",
"completionTitle": "Return could not be verified",
"completionMessage": "=The recorded holder of {{ $(\"Equipment Lending Form\").item.json[\"Asset Tag\"] }} does not match {{ $(\"Equipment Lending Form\").item.json[\"Your Email\"] }}. The attempt was logged and the asset team has been alerted in Slack. Contact {{ $(\"Workflow Configuration\").item.json.assetTeamEmail }} to resolve it.",
"options": {}
},
"id": "50d0e77c-0424-49a0-a675-7c56263cf760",
"name": "Show Mismatch Notice",
"type": "n8n-nodes-base.form",
"typeVersion": 2.5,
"position": [
1936,
384
]
},
{
"parameters": {
"content": "# Equipment Checkout and Return Ledger\n\nOne form handles both sides of equipment lending. A submitter picks Checkout or Return, enters the asset tag, their email, and an expected return date. The workflow checks the Asset Registry data table before issuing anything: if the asset is already out, the submitter is rejected on the spot and shown who holds it and when it is due back. Clean checkouts write the holder and due date to the registry and email a receipt. Returns are verified against the recorded holder. Every receipt email lists anything else the person still holds past due, so the chase for overdue gear is built into normal traffic.\n\n**How it works**\n1. The form collects the action, asset tag, submitter email, and expected return date.\n2. Every submission is appended to the Lending History table before any decision is made, so the audit trail has no holes.\n3. The Asset Registry row for the tag is read to see who, if anyone, holds the asset.\n4. A switch routes to one of four outcomes: checkout rejected, checkout recorded, return matched, or return mismatched.\n5. Rejections show the current holder and due back date on the form completion page.\n6. Clean checkouts upsert the registry row. Matched returns clear the holder from the row. Mismatched returns alert Slack and leave the registry untouched.\n7. Receipts go out by Gmail, with a list of anything else the submitter holds past due appended.\n\n**Setup**\n1. Fill SLACK_CHANNEL_ID in Workflow Configuration with the channel for mismatch alerts.\n2. Fill ASSET_TEAM_EMAIL in Workflow Configuration with your equipment desk address.\n3. Connect your Gmail and Slack credentials on the two send nodes.\n4. Seed the Asset Registry table with one row per asset: assetTag and assetName filled, holderEmail empty, status set to available.\n5. Share the production form URL with your team and activate the workflow.",
"height": 692,
"width": 1240
},
"id": "7e95d1dc-c2b8-4346-9288-d3a0c7637bc6",
"name": "Overview And Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-48,
-1200
]
},
{
"parameters": {
"content": "## 1. Intake and audit log\n\nThe form is the single entry point for both actions. Workflow Configuration centralizes the Slack channel and team email placeholders. Every attempt is written to Lending History before any check runs, including returns that later fail verification, so the audit trail has no holes.",
"height": 420,
"width": 900,
"color": 7
},
"id": "a1d4efa8-9311-44fb-a476-78b32ccb38b0",
"name": "Section Intake And Audit Log",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-48,
-352
]
},
{
"parameters": {
"content": "## 2. Registry read and routing\n\nThe Asset Registry row for the submitted tag is read with Always Output Data on, so an unknown tag still flows through and becomes a first checkout. The switch resolves four outcomes from that one read: already out, checkout OK, return matched, return mismatched.",
"height": 516,
"width": 576,
"color": 7
},
"id": "f10db99b-cb1c-4137-8d79-eb64b0e03ede",
"name": "Section Registry Read And Routing",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
928,
-432
]
},
{
"parameters": {
"content": "## 3. Outcomes\n\nEvery branch ends on a form completion page so the browser never hangs waiting on a response. Rejections show the current holder and due back date. Matched returns clear the registry row. Mismatches alert Slack and leave the registry untouched.",
"height": 1108,
"width": 636,
"color": 7
},
"id": "412163cf-3f46-4c92-97e5-1996c432b798",
"name": "Section Outcomes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1568,
-512
]
},
{
"parameters": {
"content": "## 4. Receipt and overdue chase\n\nBoth successful paths converge here. The overdue query, holder equals submitter and dueDate before today as an ISO string, pulls anything else the person still holds past due, and the Gmail receipt appends that list. The completion page confirms the action.",
"height": 452,
"width": 868,
"color": 7
},
"id": "b178ba8e-933a-4130-8e14-7f48c5d99b78",
"name": "Section Receipt And Overdue Chase",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
2272,
-288
]
},
{
"parameters": {
"content": "## Double submit race\n\nData Tables have no transactions. Two submissions for the same asset arriving at nearly the same moment can both pass the availability read before either one writes, which would double issue the asset. The read and the conditional write sit adjacent to keep that window narrow, but it cannot be zero. If you need a hard guarantee, funnel submissions through a single queue or re-check the registry right after writing.",
"height": 244,
"width": 760,
"color": 3
},
"id": "a270eaff-79e4-4902-9cb4-01a34162943b",
"name": "Warning Double Submit Race",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1264,
-816
]
}
],
"connections": {
"Equipment Lending Form": {
"main": [
[
{
"node": "Workflow Configuration",
"type": "main",
"index": 0
}
]
]
},
"Workflow Configuration": {
"main": [
[
{
"node": "Log Attempt to History",
"type": "main",
"index": 0
}
]
]
},
"Log Attempt to History": {
"main": [
[
{
"node": "Read Asset Registry Row",
"type": "main",
"index": 0
}
]
]
},
"Read Asset Registry Row": {
"main": [
[
{
"node": "Route Lending Outcome",
"type": "main",
"index": 0
}
]
]
},
"Route Lending Outcome": {
"main": [
[
{
"node": "Show Already Out Rejection",
"type": "main",
"index": 0
}
],
[
{
"node": "Record Checkout in Registry",
"type": "main",
"index": 0
}
],
[
{
"node": "Clear Holder in Registry",
"type": "main",
"index": 0
}
],
[
{
"node": "Alert Return Mismatch in Slack",
"type": "main",
"index": 0
}
]
]
},
"Record Checkout in Registry": {
"main": [
[
{
"node": "Find Overdue Items for Person",
"type": "main",
"index": 0
}
]
]
},
"Find Overdue Items for Person": {
"main": [
[
{
"node": "Email Receipt with Overdue List",
"type": "main",
"index": 0
}
]
]
},
"Email Receipt with Overdue List": {
"main": [
[
{
"node": "Show Completion Confirmation",
"type": "main",
"index": 0
}
]
]
},
"Clear Holder in Registry": {
"main": [
[
{
"node": "Find Overdue Items for Person",
"type": "main",
"index": 0
}
]
]
},
"Alert Return Mismatch in Slack": {
"main": [
[
{
"node": "Show Mismatch Notice",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": []
}
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.
gmailOAuth2slackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Track equipment checkouts and returns in Data Tables with Gmail receipts and Slack alerts. Uses formTrigger, dataTable, form, gmail. Event-driven trigger; 19 nodes.
Source: https://github.com/exekyute/n8n-exekyute-templates/blob/main/pending-review/n8n-equipment-checkout-ledger/workflow.json — 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 implements a lightweight authentication system for n8n web portals using Google Sheets as a simple user and session store. It supports email-based registration, username and password log
This workflow captures new client inquiries via an n8n form, logs them to an n8n data table, creates or updates a lead in Pipedrive, notifies a Slack channel, and emails a Cal.com booking link; when C
Pair new hires with onboarding buddies from a Google Sheets mentor pool using Gmail and Slack. Uses formTrigger, googleSheets, gmail, slack. Event-driven trigger; 18 nodes.
This workflow is triggered when the contact form is submitted.
Automate event registration with capacity management, a waitlist, and multi-tier PDF ticket generation using PDF Generator API. When attendees register, the workflow checks available spots, routes by