{ "name": "Email staff when a caller requests a callback", "nodes": [ { "name": "Setup Instructions", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 0, -1160 ], "parameters": { "content": "## Email staff when a caller requests a callback\n\nTurn an authenticated callback request into a plain-text email to a fixed staff inbox. This workflow validates the caller name, phone number, and message, sends through Gmail, and returns a distinct response for mail-provider acceptance, invalid input, or an unconfirmed send.\n\n### Setup\n1. Import the workflow and leave it inactive while configuring it.\n2. In Receive Request, create a Header Auth credential with your chosen header name and secret value. Set a unique webhook path. Store the authentication secret in credentials and in your calling application, never in the workflow JSON.\n3. In Send Staff Email, connect your Gmail OAuth credential and replace YOUR_STAFF_EMAIL with the receiving staff inbox. Use a test sender and test inbox first. The recipient is fixed in the node; it is not taken from caller input.\n4. Send Content-Type: application/json and the configured authentication header. A synthetic body is {\"name\":\"Alex Example\",\"phone\":\"+1 202-555-0147\",\"message\":\"Please call me back about scheduling.\"}.\n5. Test valid input, a missing message, an invalid phone number, and a Gmail permission failure. For the valid case, verify actual receipt in the test inbox separately from the HTTP response.\n6. Activate only after these checks, then switch your calling application to the production webhook URL shown by n8n.\n\nThe name is required and limited to 120 characters. Phone accepts common formatting characters and requires 7 to 15 digits; this is a format check, not proof that a number is reachable. The message is required and limited to 2,000 characters. HTTP 202 returns accepted_by_mail_provider. It does not confirm delivery, reading, or a completed callback. HTTP 400 returns invalid_request and validation errors. HTTP 502 means acceptance is unconfirmed: inspect the sender and staff inbox before retrying, because the first attempt may have sent. The template has no persistent deduplication, makes no call, and sends no customer receipt. Execution saving is disabled in the export, but Gmail stores sent messages and your instance may keep other logs. Review data handling and retention before using real caller information. Prepared by Benian Technologies: https://benian.ai/integrations/n8n-automation", "width": 1280, "height": 780 }, "id": "template-node-1" }, { "name": "Receive Request", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [ 0, 0 ], "parameters": { "httpMethod": "POST", "path": "template-callback-notification", "authentication": "headerAuth", "responseMode": "responseNode", "options": {} }, "webhookId": "template-callback-notification-entry", "id": "template-node-2" }, { "name": "Validate Request", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 240, 0 ], "parameters": { "jsCode": "const b = $input.first().json.body || {};\nconst name = typeof b.name === 'string' ? b.name.trim() : '';\nconst phone = typeof b.phone === 'string' ? b.phone.trim() : '';\nconst message = typeof b.message === 'string' ? b.message.trim() : '';\nconst errors = [];\nif (!name || name.length > 120) errors.push('name is required and must be at most 120 characters');\nif (!/^[+() .0-9-]+$/.test(phone) || phone.replace(/\\D/g,'').length < 7 || phone.replace(/\\D/g,'').length > 15) errors.push('phone must contain 7 to 15 digits');\nif (!message || message.length > 2000) errors.push('message is required and must be at most 2000 characters');\nreturn [{ json: { valid: errors.length===0, errors, staff_message: ['Callback request','Name: '+name,'Phone: '+phone,'Message: '+message].join('\\n') } }];" }, "id": "template-node-3" }, { "name": "Request Is Valid", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 480, 0 ], "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "condition-valid", "leftValue": "={{ $json.valid }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "template-node-4" }, { "name": "Send Staff Email", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [ 720, 0 ], "parameters": { "resource": "message", "operation": "send", "sendTo": "YOUR_STAFF_EMAIL", "subject": "New callback request", "emailType": "text", "message": "={{ $('Validate Request').first().json.staff_message }}", "options": { "appendAttribution": false } }, "onError": "continueErrorOutput", "id": "template-node-5" }, { "name": "Confirm Mail Acceptance", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 960, 0 ], "parameters": { "jsCode": "if (typeof $json.id !== 'string' || !$json.id.trim()) throw new Error('Mail acceptance was not confirmed');\nreturn [{ json: { status: 'accepted_by_mail_provider' } }];" }, "id": "template-node-6", "onError": "continueErrorOutput" }, { "name": "Return Acceptance", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ 1200, 0 ], "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 202 } }, "id": "template-node-7" }, { "name": "Return Invalid", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ 720, 260 ], "parameters": { "respondWith": "json", "responseBody": "={{ { status: 'invalid_request', errors: $json.errors } }}", "options": { "responseCode": 400 } }, "id": "template-node-8" }, { "name": "Return Unconfirmed", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.4, "position": [ 960, 260 ], "parameters": { "respondWith": "json", "responseBody": "={{ { status: 'unconfirmed', message: 'Email acceptance was not confirmed. Check the sender and staff inbox before retrying.' } }}", "options": { "responseCode": 502 } }, "id": "template-node-9" }, { "parameters": { "content": "## Validate the callback request\n\nRequired JSON fields: name, phone, message. Name: at most 120 characters. Phone: 7 to 15 digits with common formatting. Message: at most 2,000 characters. Invalid requests stop before Gmail. A valid format does not prove a reachable number.", "width": 600, "height": 260 }, "id": "public-guide-callback-notification-1", "name": "Validate the callback request", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 0, -320 ] }, { "parameters": { "content": "## Configure a fixed staff recipient\n\nSelect a Gmail OAuth credential and replace YOUR_STAFF_EMAIL. Use a test inbox first. The email is plain text; callers cannot choose the recipient. No call or customer confirmation is sent.", "width": 600, "height": 240 }, "id": "public-guide-callback-notification-2", "name": "Configure a fixed staff recipient", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 680, -320 ] }, { "parameters": { "content": "## Acceptance is not delivery\n\n202: Gmail returned a message ID.\n400: invalid_request.\n502: acceptance is unconfirmed.\n\nCheck sender and staff inbox before retrying. There is no persistent deduplication. A sent email does not prove delivery, reading, or a completed callback.", "width": 600, "height": 280 }, "id": "public-guide-callback-notification-3", "name": "Acceptance is not delivery", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 680, 500 ] } ], "connections": { "Receive Request": { "main": [ [ { "node": "Validate Request", "type": "main", "index": 0 } ] ] }, "Validate Request": { "main": [ [ { "node": "Request Is Valid", "type": "main", "index": 0 } ] ] }, "Request Is Valid": { "main": [ [ { "node": "Send Staff Email", "type": "main", "index": 0 } ], [ { "node": "Return Invalid", "type": "main", "index": 0 } ] ] }, "Send Staff Email": { "main": [ [ { "node": "Confirm Mail Acceptance", "type": "main", "index": 0 } ], [ { "node": "Return Unconfirmed", "type": "main", "index": 0 } ] ] }, "Confirm Mail Acceptance": { "main": [ [ { "node": "Return Acceptance", "type": "main", "index": 0 } ], [ { "node": "Return Unconfirmed", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "timezone": "UTC", "saveDataSuccessExecution": "none", "saveDataErrorExecution": "none", "saveManualExecutions": false }, "active": false }