This workflow follows the Gmail → HTTP Request 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": "Coffee & Peppers - Contact Form AI",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 1
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-600,
0
],
"id": "schedule-trigger",
"name": "Every Minute"
},
{
"parameters": {
"method": "GET",
"url": "https://coffeeandpeppers.com/wp-json/fluentform/v1/submissions?form_id=3&per_page=10&sort_column=id&sort_by=DESC&status=unread",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-380,
0
],
"id": "fetch-submissions",
"name": "Fetch Unread Submissions",
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Parse the Fluent Forms API response\n// It returns {total, data: [...]} structure\nconst response = $input.first().json;\n\nlet submissions = [];\ntry {\n // Response may be a string (JSON-encoded)\n const parsed = typeof response === 'string' ? JSON.parse(response) : response;\n submissions = parsed.data || parsed || [];\n} catch(e) {\n return [];\n}\n\nif (!submissions.length) return [];\n\n// Parse each submission's response field\nconst items = submissions.map(sub => {\n let formData = {};\n try {\n formData = JSON.parse(sub.response);\n } catch(e) {}\n \n const names = formData.names || {};\n const firstName = names.first_name || '';\n const lastName = names.last_name || '';\n \n return {\n json: {\n submissionId: sub.id,\n customerName: `${firstName} ${lastName}`.trim() || 'there',\n customerEmail: formData.email || '',\n subject: formData.subject || '',\n message: formData.message || '',\n createdAt: sub.created_at,\n source: 'fluent_forms'\n }\n };\n});\n\nreturn items;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-160,
0
],
"id": "parse-submissions",
"name": "Parse Submissions"
},
{
"parameters": {
"jsCode": "// Extract order number from subject and message\nconst data = $input.first().json;\nconst subject = data.subject || '';\nconst message = data.message || '';\nconst searchText = subject + ' ' + message;\n\nconst orderMatch = searchText.match(/(?:order\\s*#?\\s*|#)(\\d{4,6})\\b/i)\n || searchText.match(/\\b(\\d{5,6})\\b/);\nconst orderNumber = orderMatch ? orderMatch[1] : null;\n\n// Check for system emails / skip non-customer messages\nconst isSystemEmail = !data.customerEmail || data.customerEmail.includes('mailer-daemon');\nif (isSystemEmail) return [];\n\nreturn [{ json: { ...data, orderNumber } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
60,
0
],
"id": "extract-order",
"name": "Extract Order Number"
},
{
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.orderNumber }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "isNotEmpty"
}
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
280,
0
],
"id": "has-order",
"name": "Has Order Number?"
},
{
"parameters": {
"method": "GET",
"url": "=https://coffeeandpeppers.com/wp-json/wc/v3/orders/{{ $json.orderNumber }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
500,
-120
],
"id": "get-order-by-number",
"name": "Get Order by Number",
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"method": "GET",
"url": "=https://coffeeandpeppers.com/wp-json/wc/v3/orders?email={{ $json.customerEmail }}&per_page=3&orderby=date&order=desc",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
500,
120
],
"id": "get-orders-by-email",
"name": "Get Orders by Email",
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const formData = $('Extract Order Number').first().json;\nconst orderData = $input.first().json;\n\nlet orderSummary = 'No order data available.';\ntry {\n if (Array.isArray(orderData) && orderData.length > 0) {\n orderSummary = orderData.slice(0, 3).map(o => {\n const items = (o.line_items || []).map(i => `${i.quantity}x ${i.name}`).join(', ');\n const tracking = o.meta_data?.find(m => m.key === '_wc_shipment_tracking_items')?.value?.[0]?.tracking_number;\n return `Order #${o.id} | ${o.status} | ${items} | $${o.total}` + (tracking ? ` | Tracking: ${tracking}` : '');\n }).join('\\n');\n } else if (orderData && orderData.id) {\n const items = (orderData.line_items || []).map(i => `${i.quantity}x ${i.name}`).join(', ');\n const tracking = orderData.meta_data?.find(m => m.key === '_wc_shipment_tracking_items')?.value?.[0]?.tracking_number;\n orderSummary = `Order #${orderData.id} | ${orderData.status} | ${items} | $${orderData.total}` + (tracking ? ` | Tracking: ${tracking}` : '');\n }\n} catch(e) {\n orderSummary = 'Could not retrieve order data.';\n}\n\nreturn [{ json: { ...formData, orderSummary } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
0
],
"id": "format-order",
"name": "Format Order Data"
},
{
"parameters": {
"model": "gpt-5",
"messages": {
"values": [
{
"role": "system",
"content": "You are a friendly, casual customer service AI for Coffee & Peppers (coffeeandpeppers.com), a peptide and supplement company. Your job is to:\n\n1. CLASSIFY the email into one of these categories:\n - order_status (asking where their order is)\n - missing_items (received incomplete kit or wrong items)\n - wrong_address (address error on order)\n - dosing_medical (ANY dosing, medical, or health advice questions)\n - product_question (general product info, availability, batch tests)\n - refund_return (requesting refund or return)\n - other (anything that doesn't fit above)\n\n2. Determine a CONFIDENCE score (0.0 to 1.0):\n - High confidence (0.8+): Simple factual replies, dosing deflections, order status with full data\n - Low confidence (below 0.8): Missing items, refunds, complaints, anything requiring a business decision\n\n3. Draft a RESPONSE that is casual and friendly. Sign off as 'The C&P Team'.\n\nSpecial rules:\n- For dosing_medical: Respond with: \"Hey [name]!\\n\\nWe totally get the curiosity, but we're not able to give medical or dosing advice - that's something to chat with your healthcare provider about! Is there anything else we can help you with?\\n\\n- The C&P Team\"\n- For missing_items or wrong_address: Keep confidence LOW (0.4)\n- For refund_return: Keep confidence LOW (0.3)\n- Never promise specific outcomes without human approval\n- Format: greeting on its own line, blank line, then body. Use \\n\\n between greeting and body.\n\nReturn ONLY valid JSON:\n{\n \"category\": \"<category>\",\n \"confidence\": <number>,\n \"response\": \"<full email response>\",\n \"summary\": \"<one sentence summary>\"\n}"
},
{
"role": "user",
"content": "=Customer: {{ $json.customerName }}\nEmail: {{ $json.customerEmail }}\nSubject: {{ $json.subject }}\nMessage: {{ $json.message }}\n\nOrder Data: {{ $json.orderSummary }}\nSource: Contact Form (structured data, no thread contamination)\n\nClassify and draft a response."
}
]
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.7,
"position": [
940,
0
],
"id": "ai-node",
"name": "AI - Classify & Draft",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const raw = $input.first().json.message?.content || $input.first().json.choices?.[0]?.message?.content || '{}';\nlet parsed;\ntry {\n const cleaned = raw.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n parsed = JSON.parse(cleaned);\n} catch(e) {\n parsed = { category: 'other', confidence: 0.1, response: raw, summary: 'Parse error' };\n}\n\nconst formData = $('Format Order Data').first().json;\n\nreturn [{ json: {\n ...formData,\n category: parsed.category,\n confidence: parsed.confidence,\n aiResponse: parsed.response,\n summary: parsed.summary,\n draftSubject: `[AI] Re: ${formData.subject || 'Your inquiry'}`,\n submissionId: formData.submissionId\n} }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1160,
0
],
"id": "parse-ai",
"name": "Parse AI Response"
},
{
"parameters": {
"resource": "draft",
"operation": "create",
"subject": "={{ $json.draftSubject }}",
"emailType": "text",
"message": "={{ $json.aiResponse }}",
"options": {
"sendTo": "={{ $json.customerEmail }}"
}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1380,
0
],
"id": "create-draft",
"name": "Create Gmail Draft",
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "=https://coffeeandpeppers.com/wp-json/fluentform/v1/submission/{{ $json.submissionId }}/status",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "status",
"value": "read"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1600,
0
],
"id": "mark-read",
"name": "Mark Submission Read",
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
}
],
"connections": {
"Every Minute": {
"main": [
[
{
"node": "Fetch Unread Submissions",
"type": "main",
"index": 0
}
]
]
},
"Fetch Unread Submissions": {
"main": [
[
{
"node": "Parse Submissions",
"type": "main",
"index": 0
}
]
]
},
"Parse Submissions": {
"main": [
[
{
"node": "Extract Order Number",
"type": "main",
"index": 0
}
]
]
},
"Extract Order Number": {
"main": [
[
{
"node": "Has Order Number?",
"type": "main",
"index": 0
}
]
]
},
"Has Order Number?": {
"main": [
[
{
"node": "Get Order by Number",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Orders by Email",
"type": "main",
"index": 0
}
]
]
},
"Get Order by Number": {
"main": [
[
{
"node": "Format Order Data",
"type": "main",
"index": 0
}
]
]
},
"Get Orders by Email": {
"main": [
[
{
"node": "Format Order Data",
"type": "main",
"index": 0
}
]
]
},
"Format Order Data": {
"main": [
[
{
"node": "AI - Classify & Draft",
"type": "main",
"index": 0
}
]
]
},
"AI - Classify & Draft": {
"main": [
[
{
"node": "Parse AI Response",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Response": {
"main": [
[
{
"node": "Create Gmail Draft",
"type": "main",
"index": 0
}
]
]
},
"Create Gmail Draft": {
"main": [
[
{
"node": "Mark Submission Read",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
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.
gmailOAuth2httpBasicAuthopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Coffee & Peppers - Contact Form AI. Uses httpRequest, openAi, gmail. Scheduled trigger; 12 nodes.
Source: https://gist.github.com/aidanvalero/f7e92c26896311042967bdf8c80475ff — 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.
A scheduled process aggregates content from eight distinct data sources and standardizes all inputs into a unified format. AI models perform sentiment scoring, detect conspiracy or misinformation sign
This workflow monitors filesystem sync and backup jobs by validating their execution logs, not by running or inspecting the jobs themselves.
Stop wasting billable hours on manual time-tracking. AutoTimesheet Pro uses AI to collect emails, meetings, and GitHub work, then writes a clean timesheet straight into Google Sheets. Perfect for deve
Imagine a dedicated financial expert tirelessly working behind the scenes, sifting through every transaction, every investment move, and every accounting entry. That's exactly what this automated syst
Who is this for? AI creators, marketers, agencies, and researchers tracking YouTube trends who need weekly high-signal insights without 4+ hours manual research.