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 →
{
"name": "WF5 \u2014 SecureVault Password Reset OTP",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "securevault/send-otp",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-otp",
"name": "OTP Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
220,
300
]
},
{
"parameters": {
"conditions": {
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $json.headers['x-api-key'] }}",
"rightValue": "={{ $env.SV_API_KEY }}",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
"id": "api-key-otp",
"name": "Validate API Key",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
440,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"status\": \"error\", \"message\": \"Unauthorized\" }",
"options": {
"responseCode": 401
}
},
"id": "reject-otp",
"name": "Reject",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
600,
500
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body;\nconst { email, userId, name, otp, expiresIn } = body;\n\nif (!email || !otp) {\n throw new Error('Missing required: email, otp');\n}\n\n// Validate email format\nif (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) {\n throw new Error('Invalid email format');\n}\n\n// Validate OTP is 6 digits\nif (!/^\\d{6}$/.test(otp)) {\n throw new Error('OTP must be 6 digits');\n}\n\nreturn [{\n json: {\n email: email.toLowerCase().trim(),\n userId: userId || 'unknown',\n name: name || 'User',\n otp,\n expiresIn: expiresIn || '5 minutes',\n timestamp: new Date().toISOString(),\n executionId: $execution.id\n }\n}];"
},
"id": "validate-otp",
"name": "Validate OTP Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO sv_otp_log (user_id, email, action, ip_address, expires_at, created_at)\nVALUES ($1, $2, 'requested', $3, NOW() + INTERVAL '5 minutes', NOW());",
"options": {
"queryReplacement": "={{ [$json.userId, $json.email, 'n8n-server'] }}"
}
},
"id": "rds-otp-log",
"name": "RDS: Log OTP Request",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
900,
200
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"sendTo": "={{ $json.email }}",
"subject": "\ud83d\udd10 SecureVault \u2014 Your Reset Code: {{ $json.otp }}",
"emailType": "html",
"message": "<!DOCTYPE html><html><body style=\"margin:0;padding:0;background:#06030f;font-family:'Segoe UI',Arial,sans-serif\"><div style=\"max-width:500px;margin:0 auto;padding:40px 20px\"><div style=\"text-align:center;padding:24px;background:linear-gradient(135deg,#7c3aed 0%,#3b82f6 100%);border-radius:16px 16px 0 0\"><h1 style=\"color:#fff;font-size:24px;margin:0;letter-spacing:2px\">\ud83d\udd10 SecureVault</h1><p style=\"color:rgba(255,255,255,0.7);font-size:13px;margin:6px 0 0\">Password Reset</p></div><div style=\"background:#0e0825;padding:36px 28px;border:1px solid rgba(124,58,237,0.2);border-top:0\"><p style=\"color:#94a3b8;font-size:15px;margin:0 0 8px\">Hello {{ $json.name }},</p><p style=\"color:#94a3b8;font-size:14px;line-height:1.5;margin:0 0 24px\">You requested a password reset. Use the code below to verify your identity:</p><div style=\"background:#060212;border:1px solid #3a1a6e;border-radius:12px;padding:28px;text-align:center;margin:0 0 24px\"><span style=\"font-size:42px;font-weight:700;letter-spacing:14px;color:#fff;font-family:'Courier New',monospace\">{{ $json.otp }}</span></div><p style=\"color:#f87171;font-size:13px;margin:0 0 8px\">\u23f1 Expires in {{ $json.expiresIn }}. Never share this code with anyone.</p><p style=\"color:#64748b;font-size:13px;margin:0 0 4px\">Maximum 3 verification attempts allowed.</p><div style=\"margin-top:24px;padding-top:20px;border-top:1px solid rgba(124,58,237,0.15)\"><p style=\"color:#475569;font-size:12px;margin:0\">If you didn't request this, your account is safe \u2014 just ignore this email.</p></div></div><div style=\"padding:16px;text-align:center;background:#060212;border-radius:0 0 16px 16px;border:1px solid rgba(124,58,237,0.1);border-top:0\"><p style=\"color:#334155;font-size:11px;margin:0\">\ud83d\udd12 Protected by AES-256 encryption | SecureVault</p></div></div></body></html>",
"options": {
"replyTo": "noreply@securevault.io"
}
},
"id": "gmail-otp",
"name": "Gmail: Send OTP Email",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
900,
400
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
},
"sheetName": {
"__rl": true,
"value": "OTP Requests"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Timestamp": "={{ $json.timestamp }}",
"Email": "={{ $json.email }}",
"UserId": "={{ $json.userId }}",
"Status": "sent",
"ExecutionId": "={{ $json.executionId }}"
}
},
"options": {}
},
"id": "sheets-otp-log",
"name": "Sheets: Log OTP Request",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
900,
600
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={ \"status\": \"success\", \"message\": \"OTP sent successfully.\" }",
"options": {
"responseCode": 200
}
},
"id": "respond-otp",
"name": "Respond: OTP Sent",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1160,
400
]
}
],
"connections": {
"OTP Webhook": {
"main": [
[
{
"node": "Validate API Key",
"type": "main",
"index": 0
}
]
]
},
"Validate API Key": {
"main": [
[
{
"node": "Validate OTP Request",
"type": "main",
"index": 0
}
],
[
{
"node": "Reject",
"type": "main",
"index": 0
}
]
]
},
"Validate OTP Request": {
"main": [
[
{
"node": "RDS: Log OTP Request",
"type": "main",
"index": 0
},
{
"node": "Gmail: Send OTP Email",
"type": "main",
"index": 0
},
{
"node": "Sheets: Log OTP Request",
"type": "main",
"index": 0
}
]
]
},
"Gmail: Send OTP Email": {
"main": [
[
{
"node": "Respond: OTP Sent",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"errorWorkflow": "WF8-error-monitor"
},
"tags": [
{
"name": "SecureVault"
},
{
"name": "OTP"
},
{
"name": "Security"
}
],
"triggerCount": 1
}
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.
gmailOAuth2googleSheetsOAuth2Apipostgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WF5 — SecureVault Password Reset OTP. Uses postgres, gmail, googleSheets. Webhook trigger; 8 nodes.
Source: https://github.com/Aditya15059/SecureVault/blob/main/n8n-workflows/WF5_password_reset_otp.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.
WF6 — SecureVault Security Alert Notifications. Uses gmail, telegram, postgres, googleSheets. Webhook trigger; 12 nodes.
WF1 — SecureVault Registration Trigger. Uses postgres, googleSheets, gmail, telegram. Webhook trigger; 10 nodes.
This workflow automates raw materials inventory management for businesses, eliminating manual stock updates, delayed material issue approvals, and missed low stock alerts. It ensures real-time stock t
This workflow monitors customer health by combining payment behavior, complaint signals, and AI-driven feedback analysis. It runs on daily and weekly schedules to evaluate risk levels, escalate high-r
Receive request via webhook with customer question Analyze sentiment and detect urgency using JavaScript Send urgent alerts to Slack for critical cases Search knowledge base and fetch conversation his