{ "kind": "open-flow-workflow", "version": 1, "name": "Newsletter Unsubscribe", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_6d15a01df43898a7": { "name": "Unsubscribe request", "kind": "webhook", "bodyFields": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "ID" }, { "jsonSchema": {}, "nullable": true, "handle": "Reason For Unsubscribe" } ], "method": "POST", "options": { "responseBody": { "kind": "workflow-outputs", "output": "body", "timeoutMs": 60000 }, "responseHeaders": { "Cache-Control": "no-store" } } }, "n_4669f3e553ac82c2": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6d15a01df43898a7", "output": "body" } ] }, "baseId": { "kind": "value", "value": "YOUR_AIRTABLE_BASE_ID" }, "tableIdOrName": { "kind": "value", "value": "YOUR_SUBSCRIBERS_TABLE_ID" } }, "name": "Prepare subscriber update", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "baseId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "tableIdOrName" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1, "description": "Base ID in the format appXXXXXXXXXXXXXX." }, "nullable": false, "handle": "baseId" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "Table ID or table name accepted by the Airtable path parameter." }, "nullable": false, "handle": "tableIdOrName" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "recordId" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "Record ID in the format recXXXXXXXXXXXXXX." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Field values to write into the existing Airtable record." } }, "required": [ "id", "fields" ], "additionalProperties": false, "description": "Record payload used when updating Airtable records." }, "minItems": 1, "maxItems": 10, "description": "Records to update in the Airtable table." }, "nullable": false, "handle": "records" } ], "name": "Prepare subscriber update", "moduleId": "n_b5765dfcaa7aa380", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_01931b5cbe4ec0f7": { "inputs": { "baseId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4669f3e553ac82c2", "output": "baseId" } ] }, "tableIdOrName": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4669f3e553ac82c2", "output": "tableIdOrName" } ] }, "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4669f3e553ac82c2", "output": "records" } ] }, "typecast": { "kind": "value", "value": false }, "returnFieldsByFieldId": { "kind": "value", "value": false } }, "name": "Set subscriber inactive", "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": "Records to update in the Airtable table.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "minLength": 1, "description": "Record ID in the format recXXXXXXXXXXXXXX." }, "fields": { "type": "object", "additionalProperties": { "description": "A record value." }, "description": "Field values to write into the existing Airtable record." } }, "required": [ "id", "fields" ], "additionalProperties": false, "description": "Record payload used when updating Airtable records." }, "minItems": 1, "maxItems": 10, "description": "Records to update in the Airtable table." }, "nullable": false, "handle": "records" }, { "description": "Whether Airtable should coerce incoming values to compatible field types.", "jsonSchema": { "type": "boolean", "description": "Whether Airtable should coerce incoming values to compatible field types." }, "nullable": true, "handle": "typecast", "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 } ], "outputs": [ { "description": "Records returned by Airtable after update.", "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 after update." }, "nullable": false, "handle": "records" } ], "name": "Set subscriber inactive", "executor": { "kind": "connector", "action": "airtable.update_records" } } }, "n_205176285b5f24b3": { "inputs": { "records": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_01931b5cbe4ec0f7", "output": "records" } ] }, "recordId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_4669f3e553ac82c2", "output": "recordId" } ] } }, "name": "Confirm inactive subscription", "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 after update." }, "nullable": false, "handle": "records" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "recordId" } ], "outputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" } ], "name": "Confirm inactive subscription", "moduleId": "n_be92edc0ee4db6d7", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_c1386d31fde33bb3": { "name": "Unsubscribe confirmation", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "body" } ], "outputMappings": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_205176285b5f24b3", "output": "body" } ] } } } }, "edges": [ { "source": "n_6d15a01df43898a7", "target": "n_4669f3e553ac82c2" }, { "source": "n_4669f3e553ac82c2", "target": "n_01931b5cbe4ec0f7" }, { "source": "n_01931b5cbe4ec0f7", "target": "n_205176285b5f24b3" }, { "source": "n_205176285b5f24b3", "target": "n_c1386d31fde33bb3" } ] } }, "modules": { "n_b5765dfcaa7aa380": { "name": "Prepare subscriber update", "imports": [], "source": "export function prepareNewsletterUnsubscribe({ ID, baseId, tableIdOrName }) {\n const text = (value, prefix, name) => {\n if (typeof value !== 'string' || !new RegExp('^' + prefix + '[A-Za-z0-9]+$').test(value.trim())) throw new Error('Configure a valid ' + name + '.');\n return value.trim();\n };\n const recordId = text(ID, 'rec', 'subscriber record ID');\n return { baseId: text(baseId, 'app', 'Airtable base ID'), tableIdOrName: text(tableIdOrName, 'tbl', 'subscribers table ID'), recordId, records: [{ id: recordId, fields: { Status: 'inactive' } }] };\n}\nexport default function newsletterUnsubscribeInput({ body, baseId, tableIdOrName }) {\n return prepareNewsletterUnsubscribe({ ID: body?.ID, baseId, tableIdOrName });\n}\n" }, "n_be92edc0ee4db6d7": { "name": "Confirm inactive subscription", "imports": [], "source": "export default function confirmNewsletterUnsubscribe({ records, recordId }) {\n if (!Array.isArray(records) || records.length !== 1 || records[0]?.id !== recordId || records[0]?.fields?.Status !== 'inactive') throw new Error('Airtable did not confirm the requested subscriber as inactive.');\n return { body: { unsubscribed: true } };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }