{ "nodes": [ { "name": "Every minute", "type": "n8n-nodes-base.cron", "typeVersion": 1, "parameters": { "triggerTimes": { "item": [ { "mode": "everyMinute" } ] } }, "id": "validate-sheet-email-deliverability-0" }, { "name": "Read the email sheet", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "method": "GET", "url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + encodeURIComponent('YOUR_SPREADSHEET_ID') + '/values/' + encodeURIComponent(\"'Emails'!A:Z\") }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleSheetsOAuth2Api", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "validate-sheet-email-deliverability-1" }, { "name": "Find changed email rows", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function plan({values,previous}) {\n if (!Array.isArray(values) || !Array.isArray(values[0])) throw Error('Sheet needs headers');\n const headers=values[0];\n const column=name=>{const matches=headers.flatMap((v,i)=>v===name?[i]:[]);if(matches.length!==1)throw Error('Missing or duplicate '+name+' header');return matches[0]};\n const emailColumn=column('Email'), statusColumn=column('Status');\n const rows=values.slice(1).map((row,i)=>{if(!Array.isArray(row))throw Error('Invalid row');return {row:i+2,email:String(row[emailColumn]??'').trim(),status:String(row[statusColumn]??''),cells:row}});\n const snapshot=rows.map(r=>r.cells);\n if(previous==null)return {snapshot,items:[]};\n if(!Array.isArray(previous))throw Error('Invalid previous snapshot');\n const seen=new Set(),items=[];\n for(let i=0;i({json:item}));" }, "id": "validate-sheet-email-deliverability-2" }, { "name": "Select the next email", "type": "n8n-nodes-base.splitInBatches", "typeVersion": 3, "parameters": { "batchSize": 1, "options": {} }, "id": "validate-sheet-email-deliverability-3" }, { "name": "Validate the email", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "method": "GET", "url": "={{ 'https://api.mails.so/v1/validate?email=' + encodeURIComponent($json.email) }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "validate-sheet-email-deliverability-4" }, { "name": "Recheck the email sheet", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "method": "GET", "url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + encodeURIComponent('YOUR_SPREADSHEET_ID') + '/values/' + encodeURIComponent(\"'Emails'!A:Z\") }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleSheetsOAuth2Api", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "validate-sheet-email-deliverability-5" }, { "name": "Prepare the status cell", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function match({values,current,sheetName,result,returnedEmail}){if(!Array.isArray(values)||!Array.isArray(values[0])||!current||!Number.isInteger(current.row)||current.row<2)throw Error('Invalid row');if(returnedEmail!==current.email)throw Error('Validation result does not match email');if(!['deliverable','undeliverable','risky','unknown'].includes(result))throw Error('Invalid validation result');const h=values[0],find=n=>{const a=h.flatMap((x,i)=>x===n?[i]:[]);if(a.length!==1)throw Error('Missing or duplicate '+n+' header');return a[0]};const e=find('Email'),s=find('Status'),row=values[current.row-1];if(!Array.isArray(row)||String(row[e]??'').trim()!==current.email||String(row[s]??'').trim())throw Error('Row changed before update');let col=s+1,letters='';while(col){col--;letters=String.fromCharCode(65+col%26)+letters;col=Math.floor(col/26)}if(typeof sheetName!=='string'||!sheetName.trim())throw Error('Missing sheet name');return{range:\"'\"+sheetName.replace(/'/g,\"''\")+\"'!\"+letters+current.row,values:[[result]]}}\n\nconst current=$('Select the next email').item.json;const response=$('Validate the email').item.json;if(response.error||!response.data)throw Error('Validation failed');return [{json:match({values:$input.first().json.values??[],current,sheetName:'Emails',result:response.data.result,returnedEmail:response.data.email})}];" }, "id": "validate-sheet-email-deliverability-6" }, { "name": "Write the deliverability status", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "method": "PUT", "url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + encodeURIComponent('YOUR_SPREADSHEET_ID') + '/values/' + encodeURIComponent($json.range) }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleSheetsOAuth2Api", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "valueInputOption", "value": "RAW" } ] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify({values:$json.values}) }}", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "validate-sheet-email-deliverability-7" }, { "name": "Confirm the status update", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "if($input.first().json.updatedCells!==1)throw Error('Status write not confirmed');return $input.all();" }, "id": "validate-sheet-email-deliverability-8" } ], "pinData": {}, "connections": { "Every minute": { "main": [ [ { "node": "Read the email sheet", "type": "main", "index": 0 } ] ] }, "Read the email sheet": { "main": [ [ { "node": "Find changed email rows", "type": "main", "index": 0 } ] ] }, "Find changed email rows": { "main": [ [ { "node": "Select the next email", "type": "main", "index": 0 } ] ] }, "Validate the email": { "main": [ [ { "node": "Recheck the email sheet", "type": "main", "index": 0 } ] ] }, "Recheck the email sheet": { "main": [ [ { "node": "Prepare the status cell", "type": "main", "index": 0 } ] ] }, "Prepare the status cell": { "main": [ [ { "node": "Write the deliverability status", "type": "main", "index": 0 } ] ] }, "Write the deliverability status": { "main": [ [ { "node": "Confirm the status update", "type": "main", "index": 0 } ] ] }, "Confirm the status update": { "main": [ [ { "node": "Select the next email", "type": "main", "index": 0 } ] ] }, "Select the next email": { "main": [ [], [ { "node": "Validate the email", "type": "main", "index": 0 } ] ] } }, "name": "Validate Sheet Email Deliverability", "active": false, "settings": { "timezone": "UTC", "executionOrder": "v1" } }