{ "kind": "open-flow-workflow", "version": 1, "name": "Import Squarespace Signups to Mailchimp", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_ddfc0a560184c608": { "name": "Import signups now", "kind": "start", "inputDefs": [] }, "n_89404c7db16a1787": { "name": "Every day at 00:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 0 * * *", "timezone": "UTC" } ] }, "n_0dd2db4757048015": { "inputs": { "spreadsheetId": { "kind": "value", "value": "YOUR_SPREADSHEET_ID" }, "sheetName": { "kind": "value", "value": "Signups" }, "listId": { "kind": "value", "value": "YOUR_MAILCHIMP_AUDIENCE_ID" } }, "name": "Configure sheet and audience", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "spreadsheetId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "sheetName" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "listId" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "spreadsheetId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "sheetName" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "listId" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "range" } ], "name": "Configure sheet and audience", "moduleId": "n_d9c34b51d40dbdeb", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_4d40279230307bad": { "inputs": { "spreadsheetId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0dd2db4757048015", "output": "spreadsheetId" } ] }, "range": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0dd2db4757048015", "output": "range" } ] }, "valueRenderOption": { "kind": "value", "value": "FORMATTED_VALUE" } }, "name": "Read newsletter signups", "kind": "task", "task": { "inputs": [ { "description": "The spreadsheet ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The spreadsheet ID." }, "nullable": true, "handle": "spreadsheetId", "value": null }, { "description": "The A1 notation of the range to retrieve.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The A1 notation of the range to retrieve." }, "nullable": false, "handle": "range" }, { "description": "The major dimension for the returned values.", "jsonSchema": { "type": "string", "enum": [ "DIMENSION_UNSPECIFIED", "ROWS", "COLUMNS" ], "description": "The major dimension for the returned values." }, "nullable": true, "handle": "majorDimension", "value": null }, { "description": "How values should be rendered in the output.", "jsonSchema": { "type": "string", "enum": [ "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA" ], "description": "How values should be rendered in the output." }, "nullable": true, "handle": "valueRenderOption", "value": null }, { "description": "How dates and times should be rendered in the output.", "jsonSchema": { "type": "string", "enum": [ "SERIAL_NUMBER", "FORMATTED_STRING" ], "description": "How dates and times should be rendered in the output." }, "nullable": true, "handle": "dateTimeRenderOption", "value": null } ], "outputs": [ { "description": "The A1 notation of the range.", "jsonSchema": { "type": "string", "description": "The A1 notation of the range." }, "nullable": false, "handle": "range" }, { "description": "The major dimension of the values (ROWS or COLUMNS).", "jsonSchema": { "type": "string", "description": "The major dimension of the values (ROWS or COLUMNS)." }, "nullable": false, "handle": "majorDimension" }, { "description": "The data values in the range.", "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." }, "nullable": false, "handle": "values" } ], "name": "Read newsletter signups", "executor": { "kind": "connector", "action": "googlesheets.values_get" } } }, "n_7139841b1881b95f": { "inputs": { "values": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4d40279230307bad", "output": "values" } ] } }, "name": "Validate all signups", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "array", "items": { "anyOf": [ { "type": "string", "description": "A union variant." }, { "type": "number", "description": "A union variant." }, { "type": "boolean", "description": "A union variant." }, { "const": null, "description": "A union variant." } ], "description": "The cell value schema." }, "description": "An array item." }, "description": "The data values in the range." }, "nullable": false, "handle": "values" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" } ], "name": "Validate all signups", "moduleId": "n_51cf12d190849663", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_107397dd55120899": { "inputs": { "items": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7139841b1881b95f", "output": "items" } ] }, "listId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0dd2db4757048015", "output": "listId" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_107397dd55120899", "output": "state" } ] } }, "name": "Prepare the next subscriber", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "listId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "expected" } ], "name": "Prepare the next subscriber", "moduleId": "n_95e366b8ed05f69a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_c4bf2536aedb345b": { "inputs": {}, "name": "More signups?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_107397dd55120899", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_8443607bd5d23e08": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_107397dd55120899", "output": "endpoint" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_107397dd55120899", "output": "body" } ] } }, "name": "Add the Mailchimp subscriber", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Add the Mailchimp subscriber", "executor": { "providerId": "mailchimp", "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "kind": "connector-proxy" } } }, "n_134e8c5813f80069": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8443607bd5d23e08", "output": "body" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8443607bd5d23e08", "output": "statusCode" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8443607bd5d23e08", "output": "bodyEncoding" } ] }, "expected": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_107397dd55120899", "output": "expected" } ] } }, "name": "Confirm the subscriber", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "expected" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "member" } ], "name": "Confirm the subscriber", "moduleId": "n_1484ef3c9ca9d62b", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } } }, "edges": [ { "source": "n_ddfc0a560184c608", "target": "n_0dd2db4757048015" }, { "source": "n_89404c7db16a1787", "target": "n_0dd2db4757048015" }, { "source": "n_0dd2db4757048015", "target": "n_4d40279230307bad" }, { "source": "n_4d40279230307bad", "target": "n_7139841b1881b95f" }, { "source": "n_7139841b1881b95f", "target": "n_107397dd55120899" }, { "source": "n_107397dd55120899", "target": "n_c4bf2536aedb345b" }, { "source": "n_c4bf2536aedb345b", "target": "n_8443607bd5d23e08", "sourceHandle": "more" }, { "source": "n_8443607bd5d23e08", "target": "n_134e8c5813f80069" }, { "source": "n_134e8c5813f80069", "target": "n_107397dd55120899" } ] } }, "modules": { "n_d9c34b51d40dbdeb": { "name": "Configure sheet and audience", "imports": [], "source": "export default function config({spreadsheetId,sheetName,listId}){for(const[k,v]of Object.entries({spreadsheetId,sheetName,listId}))if(typeof v!=='string'||!v.trim()||v.startsWith('YOUR_'))throw Error('Configure '+k);return{spreadsheetId,sheetName,listId,range:\"'\"+sheetName.replace(/'/g,\"''\")+\"'\"}}\n" }, "n_51cf12d190849663": { "name": "Validate all signups", "imports": [], "source": "export default function validate({values}){if(!Array.isArray(values))throw Error('Invalid sheet response');if(!values.length)return{items:[]};const h=values[0];if(!Array.isArray(h))throw Error('Invalid header');const fields=['Email Address','Name','Submitted On'],idx=fields.map(x=>h.indexOf(x));if(idx.some(x=>x<0)||fields.some(x=>h.filter(y=>y===x).length!==1))throw Error('Required headers are missing or duplicated');const seen=new Set(),items=[];for(const row of values.slice(1)){if(!Array.isArray(row)||row.every(x=>x===null||x===undefined||x===''))continue;const [email,name,time]=idx.map(i=>row[i]);if(typeof email!=='string'||!/^\\S+@\\S+\\.\\S+$/.test(email.trim())||typeof name!=='string'||!name.trim()||typeof time!=='string'||!/^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(?:\\.\\d+)?(?:Z|[+-]\\d\\d:\\d\\d)$/.test(time)||!Number.isFinite(Date.parse(time)))throw Error('Each signup requires an email, name and ISO timestamp with time zone');const m=time.match(/^(\\d{4})-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?(Z|([+-])(\\d\\d):(\\d\\d))$/),d=new Date(Date.UTC(+m[1],+m[2]-1,+m[3]));if(d.getUTCFullYear()!==+m[1]||d.getUTCMonth()!==+m[2]-1||d.getUTCDate()!==+m[3]||+m[4]>23||+m[5]>59||+m[6]>59||(m[7]!=='Z'&&(+m[9]>23||+m[10]>59)))throw Error('Invalid signup timestamp');const key=email.trim().toLowerCase();if(seen.has(key))throw Error('Duplicate email in the sheet');seen.add(key);items.push({email:email.trim(),name:name.trim(),timestamp:new Date(time).toISOString()})}return{items}}\n" }, "n_95e366b8ed05f69a": { "name": "Prepare the next subscriber", "imports": [], "source": "export default function next({items,listId,previous}){const i=previous?.index??0;if(!Array.isArray(items)||!Number.isInteger(i)||i<0||i>items.length)throw Error('Invalid signup list');if(i===items.length)return{state:{index:i},more:false,endpoint:'/unused',body:{},expected:'-'};const x=items[i];return{state:{index:i+1},more:true,endpoint:'/lists/'+encodeURIComponent(listId)+'/members',body:{email_address:x.email,status:'subscribed',merge_fields:{FNAME:x.name},timestamp_signup:x.timestamp},expected:x.email}}\n" }, "n_1484ef3c9ca9d62b": { "name": "Confirm the subscriber", "imports": [], "source": "export default function confirm({body,statusCode,bodyEncoding,expected}){if(statusCode!==200||bodyEncoding||!body||typeof body.id!=='string'||!body.id||typeof body.email_address!=='string'||body.email_address.toLowerCase()!==expected.toLowerCase()||body.status!=='subscribed')throw Error('Mailchimp did not confirm the subscriber');return{member:body}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }