{ "kind": "open-flow-workflow", "version": 1, "name": "Import Airtable Contacts into a Lemlist Campaign", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_632cf749cfa51b44": { "name": "Import contacts", "kind": "start", "inputDefs": [] }, "n_e6e918c8a361b7c8": { "inputs": { "baseId": { "kind": "value", "value": "YOUR_AIRTABLE_BASE_ID" }, "tableIdOrName": { "kind": "value", "value": "YOUR_TABLE_NAME" }, "pageSize": { "kind": "value", "value": 100 }, "offset": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_90f44b3fc7a643b9", "output": "offset" } ] } }, "name": "Read Airtable contacts", "kind": "task", "task": { "inputs": [ { "description": "Base ID in the format appXXXXXXXXXXXXXX.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Base ID in the format appXXXXXXXXXXXXXX." }, "nullable": false, "handle": "baseId" }, { "description": "Table ID or table name accepted by the Airtable path parameter.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Table ID or table name accepted by the Airtable path parameter." }, "nullable": false, "handle": "tableIdOrName" }, { "description": "View name or view ID used by Airtable to filter and sort results.", "jsonSchema": { "type": "string", "minLength": 1, "description": "View name or view ID used by Airtable to filter and sort results." }, "nullable": true, "handle": "view", "value": null }, { "description": "Field names or field IDs to include in the Airtable response.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "An array item." }, "minItems": 1, "description": "Field names or field IDs to include in the Airtable response." }, "nullable": true, "handle": "fields", "value": null }, { "description": "Sort rules applied by Airtable in order.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "field": { "type": "string", "minLength": 1, "description": "Field name or field ID used by Airtable for sorting." }, "direction": { "type": "string", "enum": [ "asc", "desc" ], "description": "Sort direction accepted by Airtable." } }, "required": [ "field" ], "additionalProperties": false, "description": "Sort rule accepted by the Airtable list records endpoint." }, "minItems": 1, "description": "Sort rules applied by Airtable in order." }, "nullable": true, "handle": "sort", "value": null }, { "description": "Formula string evaluated by Airtable to filter matching records.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Formula string evaluated by Airtable to filter matching records." }, "nullable": true, "handle": "filterByFormula", "value": null }, { "description": "Maximum total number of records to return before Airtable stops pagination.", "jsonSchema": { "type": "integer", "minimum": 1, "description": "Maximum total number of records to return before Airtable stops pagination." }, "nullable": true, "handle": "maxRecords", "value": null }, { "description": "Number of records to return per page.", "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Number of records to return per page." }, "nullable": true, "handle": "pageSize", "value": null }, { "description": "Opaque pagination cursor returned by a previous Airtable response.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Opaque pagination cursor returned by a previous Airtable response." }, "nullable": true, "handle": "offset", "value": null }, { "description": "Cell format accepted by Airtable for read operations.", "jsonSchema": { "type": "string", "enum": [ "json", "string" ], "description": "Cell format accepted by Airtable for read operations." }, "nullable": true, "handle": "cellFormat", "value": null }, { "description": "Timezone string sent to Airtable when cellFormat is string.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Timezone string sent to Airtable when cellFormat is string." }, "nullable": true, "handle": "timeZone", "value": null }, { "description": "User locale sent to Airtable when cellFormat is string.", "jsonSchema": { "type": "string", "minLength": 1, "description": "User locale sent to Airtable when cellFormat is string." }, "nullable": true, "handle": "userLocale", "value": null }, { "description": "Whether Airtable should return field IDs instead of field names in record objects.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should return field IDs instead of field names in record objects." }, "nullable": true, "handle": "returnFieldsByFieldId", "value": null }, { "description": "Whether Airtable should return date dependency metadata for linked record fields.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should return date dependency metadata for linked record fields." }, "nullable": true, "handle": "includeDateDependencyMetadata", "value": null }, { "description": "Record metadata fields to include in Airtable list records responses.", "jsonSchema": { "type": "array", "items": { "const": "commentCount", "description": "An array item." }, "minItems": 1, "description": "Record metadata fields to include in Airtable list records responses." }, "nullable": true, "handle": "recordMetadata", "value": null } ], "outputs": [ { "description": "Records returned by Airtable.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Record ID." }, "createdTime": { "type": "string", "description": "Record creation timestamp returned by Airtable." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Record fields keyed by field name or field ID." }, "commentCount": { "type": "integer", "description": "Comment count returned by Airtable when enabled on the endpoint." } }, "required": [ "id", "fields" ], "additionalProperties": true, "description": "Airtable record." }, "description": "Records returned by Airtable." }, "nullable": false, "handle": "records" }, { "description": "Pagination cursor for the next page of records, or null when unavailable.", "jsonSchema": { "type": [ "string", "null" ], "description": "Pagination cursor for the next page of records, or null when unavailable." }, "nullable": true, "handle": "offset" } ], "name": "Read Airtable contacts", "executor": { "kind": "connector", "action": "airtable.list_records" } } }, "n_90f44b3fc7a643b9": { "inputs": { "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e6e918c8a361b7c8", "output": "records" } ] }, "offset": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e6e918c8a361b7c8", "output": "offset" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_90f44b3fc7a643b9", "output": "state" } ] } }, "name": "Collect and validate every page", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Record ID." }, "createdTime": { "type": "string", "description": "Record creation timestamp returned by Airtable." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Record fields keyed by field name or field ID." }, "commentCount": { "type": "integer", "description": "Comment count returned by Airtable when enabled on the endpoint." } }, "required": [ "id", "fields" ], "additionalProperties": true, "description": "Airtable record." }, "description": "Records returned by Airtable." }, "nullable": false, "handle": "records" }, { "jsonSchema": { "type": [ "string", "null" ], "description": "Pagination cursor for the next page of records, or null when unavailable." }, "nullable": true, "handle": "offset" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "offset" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" } ], "name": "Collect and validate every page", "moduleId": "n_813f606845349984", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_598da3893f236c91": { "inputs": {}, "name": "More Airtable pages?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_90f44b3fc7a643b9", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_7f34860a83b00729": { "inputs": { "pageState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_90f44b3fc7a643b9", "output": "state" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7f34860a83b00729", "output": "state" } ] }, "campaignId": { "kind": "value", "value": "YOUR_LEMLIST_CAMPAIGN_ID" }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_dc22b50d81099213", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_dc22b50d81099213", "output": "body" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_dc22b50d81099213", "output": "bodyEncoding" } ] } }, "name": "Prepare each campaign lead", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "pageState" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "campaignId" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "payload" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Prepare each campaign lead", "moduleId": "n_1d2c1c6745e993a0", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_2bb7ac79ad5f2b83": { "inputs": {}, "name": "More leads to create?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_7f34860a83b00729", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_dc22b50d81099213": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7f34860a83b00729", "output": "endpoint" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7f34860a83b00729", "output": "payload" } ] } }, "name": "Add the campaign lead", "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 campaign lead", "executor": { "providerId": "lemlist", "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "kind": "connector-proxy" } } }, "n_e9fc43846563b5ca": { "inputs": { "createState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7f34860a83b00729", "output": "state" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e9fc43846563b5ca", "output": "state" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ecdc301a3f60deb1", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ecdc301a3f60deb1", "output": "body" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ecdc301a3f60deb1", "output": "bodyEncoding" } ] } }, "name": "Prepare each lead lookup", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "createState" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Prepare each lead lookup", "moduleId": "n_58dbb6b0595fc3dc", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_a56f28a4e168c0fb": { "inputs": {}, "name": "More leads to look up?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_e9fc43846563b5ca", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_ecdc301a3f60deb1": { "inputs": { "endpoint": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e9fc43846563b5ca", "output": "endpoint" } ] } }, "name": "Look up the lead by email", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" } ], "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": "Look up the lead by email", "executor": { "providerId": "lemlist", "method": "GET", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "kind": "connector-proxy" } } }, "n_98a5c67adb7a6d1c": { "name": "Lead results", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "results" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "outputMappings": { "results": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e9fc43846563b5ca", "output": "state" } ] }, "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e9fc43846563b5ca", "output": "count" } ] } } } }, "edges": [ { "source": "n_632cf749cfa51b44", "target": "n_e6e918c8a361b7c8" }, { "source": "n_e6e918c8a361b7c8", "target": "n_90f44b3fc7a643b9" }, { "source": "n_90f44b3fc7a643b9", "target": "n_598da3893f236c91" }, { "source": "n_598da3893f236c91", "target": "n_e6e918c8a361b7c8", "sourceHandle": "more" }, { "source": "n_598da3893f236c91", "target": "n_7f34860a83b00729", "sourceHandle": "otherwise" }, { "source": "n_7f34860a83b00729", "target": "n_2bb7ac79ad5f2b83" }, { "source": "n_2bb7ac79ad5f2b83", "target": "n_dc22b50d81099213", "sourceHandle": "more" }, { "source": "n_dc22b50d81099213", "target": "n_7f34860a83b00729" }, { "source": "n_2bb7ac79ad5f2b83", "target": "n_e9fc43846563b5ca", "sourceHandle": "otherwise" }, { "source": "n_e9fc43846563b5ca", "target": "n_a56f28a4e168c0fb" }, { "source": "n_a56f28a4e168c0fb", "target": "n_ecdc301a3f60deb1", "sourceHandle": "more" }, { "source": "n_ecdc301a3f60deb1", "target": "n_e9fc43846563b5ca" }, { "source": "n_a56f28a4e168c0fb", "target": "n_98a5c67adb7a6d1c", "sourceHandle": "otherwise" } ] } }, "modules": { "n_813f606845349984": { "name": "Collect and validate every page", "imports": [], "source": "export default function collectUsers({records,offset,previousState}) {\n if (!Array.isArray(records)) throw new Error('Missing Airtable records');\n const state=previousState ?? {records:[],seen:[]};\n if (!Array.isArray(state.records)||!Array.isArray(state.seen)) throw new Error('Invalid pagination state');\n if (offset!==null && (typeof offset!=='string'||!offset||state.seen.includes(offset))) throw new Error('Invalid or repeated Airtable offset');\n const all=[...state.records,...records];\n if(offset===null) for(const r of all){const f=r?.fields;if(typeof f?.Email!=='string'||!f.Email.trim()||typeof f?.Name!=='string'||!f.Name.trim())throw new Error('Airtable record requires text Email and Name');}\n return {state:{records:all,seen:offset===null?state.seen:[...state.seen,offset]},offset:offset??'unused',more:offset!==null};\n}\n" }, "n_1d2c1c6745e993a0": { "name": "Prepare each campaign lead", "imports": [], "source": "export default function next({pageState,previousState,campaignId,body,statusCode,bodyEncoding}) {\n const state=previousState??{index:0,records:pageState.records,results:[]};\n if(!Array.isArray(state.records)||!Array.isArray(state.results))throw Error('Invalid import state');\n if(previousState){if(statusCode!==200||bodyEncoding||!body||typeof body!=='object'||Array.isArray(body))throw Error('Lemlist did not return a completed creation');\n const expected=state.records[state.index]?.fields?.Email;if(typeof body.email!=='string'||body.email.toLowerCase()!==expected.toLowerCase())throw Error('Lemlist response does not match email');state.results.push(body);state.index++;}\n if(state.index===state.records.length)return{state,more:false,endpoint:'unused',payload:{},count:state.results.length};\n if(typeof campaignId!=='string'||!campaignId.trim()||campaignId.startsWith('YOUR_'))throw Error('Configure a campaign ID');\n const f=state.records[state.index].fields;return {state,more:true,endpoint:`/campaigns/${encodeURIComponent(campaignId)}/leads/${encodeURIComponent(f.Email)}`,payload:{firstName:f.Name},count:state.results.length};\n}\n" }, "n_58dbb6b0595fc3dc": { "name": "Prepare each lead lookup", "imports": [], "source": "export default function next({createState,previousState,body,statusCode,bodyEncoding}) {\n const state=previousState??{index:0,records:createState.records,results:[]};\n if(previousState){if(statusCode!==200||bodyEncoding||!Array.isArray(body))throw Error('Lemlist did not return a lead list');state.results.push({email:state.records[state.index].fields.Email,leads:body});state.index++;}\n if(state.index===state.records.length)return{state,more:false,endpoint:'unused',count:state.results.length};\n return {state,more:true,endpoint:`/leads/${encodeURIComponent(state.records[state.index].fields.Email)}`,count:state.results.length};\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }