{ "name": "Create and Update a SendGrid Contact", "nodes": [ { "name": "On clicking 'execute'", "type": "n8n-nodes-base.manualTrigger", "position": [ 270, 300 ], "parameters": {}, "typeVersion": 1, "id": "create-update-sendgrid-contact-0" }, { "name": "Set contact details", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function input({email,firstName,lastName}){if(typeof email!=='string'||email.length>254||!/^\\S+@\\S+\\.\\S+$/.test(email.trim()))throw Error('Enter a valid email');if(typeof firstName!=='string'||!firstName.trim()||firstName.length>50||typeof lastName!=='string'||!lastName.trim()||lastName.length>50)throw Error('Enter first and last names (up to 50 characters)');email=email.trim().toLowerCase();return {email,firstName:firstName.trim(),lastName:lastName.trim(),first:{contacts:[{email,first_name:firstName.trim()}]},second:{contacts:[{email,last_name:lastName.trim()}]},lookup:{identifiers:[email]}};}\n\nreturn [{json:input({email:'',firstName:'',lastName:''})}];" }, "id": "create-update-sendgrid-contact-1" }, { "name": "SendGrid", "type": "n8n-nodes-base.httpRequest", "position": [ 470, 300 ], "parameters": { "method": "PUT", "url": "https://api.sendgrid.com/v3/marketing/contacts", "authentication": "predefinedCredentialType", "nodeCredentialType": "sendGridApi", "sendBody": true, "contentType": "json", "specifyBody": "json", "jsonBody": "={{ JSON.stringify($(\"Set contact details\").first().json.first) }}", "options": { "timeout": 30000, "response": { "response": { "fullResponse": true, "responseFormat": "json" } } } }, "typeVersion": 4.2, "id": "create-update-sendgrid-contact-2" }, { "name": "Prepare job 1", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function job({body,statusCode,bodyEncoding}){if(statusCode!==202||bodyEncoding||typeof body?.job_id!=='string'||!body.job_id)throw Error('Contact write did not return a job ID');return {jobId:body.job_id,endpoint:'/v3/marketing/contacts/imports/'+encodeURIComponent(body.job_id)};}\n\nconst response=$input.first().json;const j=job({body:response.body,statusCode:response.statusCode,bodyEncoding:null});return [{json:{jobId:j.jobId,count:0}}];" }, "id": "create-update-sendgrid-contact-3" }, { "name": "Wait for job 1", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "parameters": { "amount": 5, "unit": "seconds" }, "id": "create-update-sendgrid-contact-4" }, { "name": "Read job 1", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "method": "GET", "url": "={{ \"https://api.sendgrid.com/v3/marketing/contacts/imports/\" + encodeURIComponent($('Prepare job 1').first().json.jobId) }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "sendGridApi", "options": { "timeout": 30000 } }, "id": "create-update-sendgrid-contact-5" }, { "name": "Evaluate job 1", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function check({body,statusCode,bodyEncoding,previous}){if(statusCode!==200||bodyEncoding||!body||typeof body!=='object')throw Error('Could not read contact job');const n=(previous?.count??0)+1;if(!Number.isInteger(n)||n<1||n>12)throw Error('Invalid job state');if(body.status==='completed')return {state:{count:n},done:true};if(body.status==='pending'){if(n>=12)throw Error('Contact job still pending after 12 checks');return {state:{count:n},done:false};}throw Error('Contact job failed or returned an unknown status');}\n\nconst r=check({body:$input.first().json,statusCode:200,bodyEncoding:null,previous:{count:$runIndex}});return [{json:{done:r.done}}];" }, "id": "create-update-sendgrid-contact-6" }, { "name": "Job 1 complete?", "type": "n8n-nodes-base.if", "typeVersion": 2, "parameters": { "conditions": { "options": { "caseSensitive": true, "typeValidation": "strict" }, "conditions": [ { "id": "done", "leftValue": "={{ $json.done }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "create-update-sendgrid-contact-7" }, { "name": "SendGrid1", "type": "n8n-nodes-base.httpRequest", "position": [ 670, 300 ], "parameters": { "method": "PUT", "url": "https://api.sendgrid.com/v3/marketing/contacts", "authentication": "predefinedCredentialType", "nodeCredentialType": "sendGridApi", "sendBody": true, "contentType": "json", "specifyBody": "json", "jsonBody": "={{ JSON.stringify($(\"Set contact details\").first().json.second) }}", "options": { "timeout": 30000, "response": { "response": { "fullResponse": true, "responseFormat": "json" } } } }, "typeVersion": 4.2, "id": "create-update-sendgrid-contact-8" }, { "name": "Prepare job 2", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function job({body,statusCode,bodyEncoding}){if(statusCode!==202||bodyEncoding||typeof body?.job_id!=='string'||!body.job_id)throw Error('Contact write did not return a job ID');return {jobId:body.job_id,endpoint:'/v3/marketing/contacts/imports/'+encodeURIComponent(body.job_id)};}\n\nconst response=$input.first().json;const j=job({body:response.body,statusCode:response.statusCode,bodyEncoding:null});return [{json:{jobId:j.jobId,count:0}}];" }, "id": "create-update-sendgrid-contact-9" }, { "name": "Wait for job 2", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "parameters": { "amount": 5, "unit": "seconds" }, "id": "create-update-sendgrid-contact-10" }, { "name": "Read job 2", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "parameters": { "method": "GET", "url": "={{ \"https://api.sendgrid.com/v3/marketing/contacts/imports/\" + encodeURIComponent($('Prepare job 2').first().json.jobId) }}", "authentication": "predefinedCredentialType", "nodeCredentialType": "sendGridApi", "options": { "timeout": 30000 } }, "id": "create-update-sendgrid-contact-11" }, { "name": "Evaluate job 2", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function check({body,statusCode,bodyEncoding,previous}){if(statusCode!==200||bodyEncoding||!body||typeof body!=='object')throw Error('Could not read contact job');const n=(previous?.count??0)+1;if(!Number.isInteger(n)||n<1||n>12)throw Error('Invalid job state');if(body.status==='completed')return {state:{count:n},done:true};if(body.status==='pending'){if(n>=12)throw Error('Contact job still pending after 12 checks');return {state:{count:n},done:false};}throw Error('Contact job failed or returned an unknown status');}\n\nconst r=check({body:$input.first().json,statusCode:200,bodyEncoding:null,previous:{count:$runIndex}});return [{json:{done:r.done}}];" }, "id": "create-update-sendgrid-contact-12" }, { "name": "Job 2 complete?", "type": "n8n-nodes-base.if", "typeVersion": 2, "parameters": { "conditions": { "options": { "caseSensitive": true, "typeValidation": "strict" }, "conditions": [ { "id": "done", "leftValue": "={{ $json.done }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "create-update-sendgrid-contact-13" }, { "name": "SendGrid2", "type": "n8n-nodes-base.httpRequest", "position": [ 870, 300 ], "parameters": { "method": "POST", "url": "https://api.sendgrid.com/v3/marketing/contacts/search/identifiers/email", "authentication": "predefinedCredentialType", "nodeCredentialType": "sendGridApi", "sendBody": true, "contentType": "json", "specifyBody": "json", "jsonBody": "={{ JSON.stringify($(\"Set contact details\").first().json.lookup) }}", "options": { "timeout": 30000, "response": { "response": { "fullResponse": true, "responseFormat": "json" } } } }, "typeVersion": 4.2, "id": "create-update-sendgrid-contact-14" }, { "name": "Verify the contact", "type": "n8n-nodes-base.code", "typeVersion": 2, "parameters": { "mode": "runOnceForAllItems", "jsCode": "function confirm({body,statusCode,bodyEncoding,email,firstName,lastName}){if(statusCode!==200||bodyEncoding)throw Error('Could not read contact');const c=body?.result?.[email]?.contact;if(!c||typeof c.id!=='string'||c.email?.toLowerCase()!==email||c.first_name!==firstName||c.last_name!==lastName)throw Error('Contact does not match the requested update');return {contact:c};}\n\nconst wanted=$('Set contact details').first().json,r=$input.first().json;return [{json:confirm({body:r.body,statusCode:r.statusCode,bodyEncoding:null,...wanted}).contact}];" }, "id": "create-update-sendgrid-contact-15" } ], "active": false, "settings": { "executionOrder": "v1" }, "connections": { "On clicking 'execute'": { "main": [ [ { "node": "Set contact details", "type": "main", "index": 0 } ] ] }, "Set contact details": { "main": [ [ { "node": "SendGrid", "type": "main", "index": 0 } ] ] }, "SendGrid": { "main": [ [ { "node": "Prepare job 1", "type": "main", "index": 0 } ] ] }, "Prepare job 1": { "main": [ [ { "node": "Wait for job 1", "type": "main", "index": 0 } ] ] }, "Wait for job 1": { "main": [ [ { "node": "Read job 1", "type": "main", "index": 0 } ] ] }, "Read job 1": { "main": [ [ { "node": "Evaluate job 1", "type": "main", "index": 0 } ] ] }, "Evaluate job 1": { "main": [ [ { "node": "Job 1 complete?", "type": "main", "index": 0 } ] ] }, "SendGrid1": { "main": [ [ { "node": "Prepare job 2", "type": "main", "index": 0 } ] ] }, "Prepare job 2": { "main": [ [ { "node": "Wait for job 2", "type": "main", "index": 0 } ] ] }, "Wait for job 2": { "main": [ [ { "node": "Read job 2", "type": "main", "index": 0 } ] ] }, "Read job 2": { "main": [ [ { "node": "Evaluate job 2", "type": "main", "index": 0 } ] ] }, "Evaluate job 2": { "main": [ [ { "node": "Job 2 complete?", "type": "main", "index": 0 } ] ] }, "SendGrid2": { "main": [ [ { "node": "Verify the contact", "type": "main", "index": 0 } ] ] }, "Job 1 complete?": { "main": [ [ { "node": "SendGrid1", "type": "main", "index": 0 } ], [ { "node": "Wait for job 1", "type": "main", "index": 0 } ] ] }, "Job 2 complete?": { "main": [ [ { "node": "SendGrid2", "type": "main", "index": 0 } ], [ { "node": "Wait for job 2", "type": "main", "index": 0 } ] ] } }, "pinData": {} }