{ "kind": "open-flow-workflow", "version": 1, "name": "Notion Mentioned To-Dos to Slack DM", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_56a250d704677aaf": { "name": "Every day at 08:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 8 * * *", "timezone": "UTC" } ] }, "n_1ac23d298a41c968": { "inputs": { "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1ac23d298a41c968", "output": "state" } ] }, "targetUserId": { "kind": "value", "value": "YOUR_NOTION_USER_ID" }, "page": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d122432d7343e5f6", "output": "output" } ] } }, "name": "Collect matching to-dos", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "object", "properties": { "object": { "const": "list", "description": "The Notion object type for a paginated response." }, "results": { "type": "array", "items": { "type": "object", "properties": { "object": { "const": "block", "description": "The Notion object type." }, "id": { "type": "string", "description": "The block ID." }, "parent": { "type": "object", "properties": { "type": { "type": "string", "description": "The parent type, such as workspace, page_id, data_source_id, database_id, or block_id." } }, "additionalProperties": true, "description": "The parent object that contains this Notion object." }, "created_time": { "type": "string", "format": "date-time", "description": "The time when the block was created." }, "last_edited_time": { "type": "string", "format": "date-time", "description": "The time when the block was last edited." }, "created_by": { "type": "object", "properties": { "object": { "const": "user", "description": "The Notion object type." }, "id": { "type": "string", "description": "The Notion user ID." }, "name": { "type": [ "string", "null" ], "description": "The user's display name, or null when unavailable." }, "avatar_url": { "type": [ "string", "null" ], "description": "The user's avatar URL, or null when unavailable." }, "type": { "type": "string", "enum": [ "person", "bot" ], "description": "The user type returned by Notion." }, "person": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The person's email address." } }, "additionalProperties": true, "description": "Details about a person user." }, "bot": { "type": "object", "additionalProperties": { "description": "A Notion API field value." }, "description": "Details about a bot user." } }, "additionalProperties": true, "description": "A Notion user reference." }, "last_edited_by": { "type": "object", "properties": { "object": { "const": "user", "description": "The Notion object type." }, "id": { "type": "string", "description": "The Notion user ID." }, "name": { "type": [ "string", "null" ], "description": "The user's display name, or null when unavailable." }, "avatar_url": { "type": [ "string", "null" ], "description": "The user's avatar URL, or null when unavailable." }, "type": { "type": "string", "enum": [ "person", "bot" ], "description": "The user type returned by Notion." }, "person": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The person's email address." } }, "additionalProperties": true, "description": "Details about a person user." }, "bot": { "type": "object", "additionalProperties": { "description": "A Notion API field value." }, "description": "Details about a bot user." } }, "additionalProperties": true, "description": "A Notion user reference." }, "has_children": { "type": "boolean", "description": "Whether the block has child blocks." }, "in_trash": { "type": "boolean", "description": "Whether the block is in the trash." }, "type": { "type": "string", "description": "The block type, such as paragraph, heading_1, child_page, or to_do." } }, "additionalProperties": true, "description": "A Notion block object returned by the API." }, "description": "The list of result objects." }, "next_cursor": { "type": [ "string", "null" ], "description": "The cursor for the next page of results." }, "has_more": { "type": "boolean", "description": "Whether more results are available." }, "type": { "const": "block", "description": "The type-specific pagination metadata field name." }, "block": { "type": "object", "properties": {}, "additionalProperties": false, "description": "Pagination metadata for block results." }, "request_status": { "type": "object", "properties": { "type": { "type": "string", "description": "The request status type returned by Notion." }, "incomplete_reason": { "type": "string", "description": "The reason the response is incomplete." } }, "additionalProperties": true, "description": "The pagination request status returned by Notion." } }, "required": [ "object", "results", "next_cursor", "has_more", "type", "block" ], "additionalProperties": true, "description": "The official Notion block children list response." }, "nullable": true, "handle": "page" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "targetUserId" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": [ "string", "null" ] }, "nullable": true, "handle": "cursor" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" } ], "name": "Collect matching to-dos", "moduleId": "n_13e0f6830b9f251a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_02ea1c67a7d89d82": { "inputs": {}, "name": "Read another page", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_1ac23d298a41c968", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_d122432d7343e5f6": { "inputs": { "blockId": { "kind": "value", "value": "YOUR_PARENT_BLOCK_ID" }, "pageSize": { "kind": "value", "value": 100 }, "startCursor": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1ac23d298a41c968", "output": "cursor" } ] } }, "name": "Read Notion child blocks", "kind": "task", "task": { "inputs": [ { "description": "The parent block ID.", "jsonSchema": { "type": "string", "description": "The parent block ID." }, "nullable": false, "handle": "blockId" }, { "description": "The number of results per page.", "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 100, "description": "The number of results per page." }, "nullable": true, "handle": "pageSize", "value": null }, { "description": "The cursor for pagination.", "jsonSchema": { "type": "string", "description": "The cursor for pagination." }, "nullable": true, "handle": "startCursor", "value": null } ], "outputs": [ { "description": "The official Notion block children list response.", "jsonSchema": { "type": "object", "properties": { "object": { "const": "list", "description": "The Notion object type for a paginated response." }, "results": { "type": "array", "items": { "type": "object", "properties": { "object": { "const": "block", "description": "The Notion object type." }, "id": { "type": "string", "description": "The block ID." }, "parent": { "type": "object", "properties": { "type": { "type": "string", "description": "The parent type, such as workspace, page_id, data_source_id, database_id, or block_id." } }, "additionalProperties": true, "description": "The parent object that contains this Notion object." }, "created_time": { "type": "string", "format": "date-time", "description": "The time when the block was created." }, "last_edited_time": { "type": "string", "format": "date-time", "description": "The time when the block was last edited." }, "created_by": { "type": "object", "properties": { "object": { "const": "user", "description": "The Notion object type." }, "id": { "type": "string", "description": "The Notion user ID." }, "name": { "type": [ "string", "null" ], "description": "The user's display name, or null when unavailable." }, "avatar_url": { "type": [ "string", "null" ], "description": "The user's avatar URL, or null when unavailable." }, "type": { "type": "string", "enum": [ "person", "bot" ], "description": "The user type returned by Notion." }, "person": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The person's email address." } }, "additionalProperties": true, "description": "Details about a person user." }, "bot": { "type": "object", "additionalProperties": { "description": "A Notion API field value." }, "description": "Details about a bot user." } }, "additionalProperties": true, "description": "A Notion user reference." }, "last_edited_by": { "type": "object", "properties": { "object": { "const": "user", "description": "The Notion object type." }, "id": { "type": "string", "description": "The Notion user ID." }, "name": { "type": [ "string", "null" ], "description": "The user's display name, or null when unavailable." }, "avatar_url": { "type": [ "string", "null" ], "description": "The user's avatar URL, or null when unavailable." }, "type": { "type": "string", "enum": [ "person", "bot" ], "description": "The user type returned by Notion." }, "person": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The person's email address." } }, "additionalProperties": true, "description": "Details about a person user." }, "bot": { "type": "object", "additionalProperties": { "description": "A Notion API field value." }, "description": "Details about a bot user." } }, "additionalProperties": true, "description": "A Notion user reference." }, "has_children": { "type": "boolean", "description": "Whether the block has child blocks." }, "in_trash": { "type": "boolean", "description": "Whether the block is in the trash." }, "type": { "type": "string", "description": "The block type, such as paragraph, heading_1, child_page, or to_do." } }, "additionalProperties": true, "description": "A Notion block object returned by the API." }, "description": "The list of result objects." }, "next_cursor": { "type": [ "string", "null" ], "description": "The cursor for the next page of results." }, "has_more": { "type": "boolean", "description": "Whether more results are available." }, "type": { "const": "block", "description": "The type-specific pagination metadata field name." }, "block": { "type": "object", "properties": {}, "additionalProperties": false, "description": "Pagination metadata for block results." }, "request_status": { "type": "object", "properties": { "type": { "type": "string", "description": "The request status type returned by Notion." }, "incomplete_reason": { "type": "string", "description": "The reason the response is incomplete." } }, "additionalProperties": true, "description": "The pagination request status returned by Notion." } }, "required": [ "object", "results", "next_cursor", "has_more", "type", "block" ], "additionalProperties": true, "description": "The official Notion block children list response." }, "nullable": false, "handle": "output" } ], "name": "Read Notion child blocks", "executor": { "kind": "connector", "action": "notion.list_block_children", "outputMode": "value" } } }, "n_ab900f607c20c886": { "inputs": { "items": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1ac23d298a41c968", "output": "items" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ab900f607c20c886", "output": "state" } ] }, "lastChannelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_cef8139f292e20b1", "output": "channelId" } ] } }, "name": "Open a DM for each to-do", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "string", "description": "The opened Slack conversation ID." }, "nullable": true, "handle": "lastChannelId" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "channels" } ], "name": "Open a DM for each to-do", "moduleId": "n_848f4647f0a3fc72", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_7b1fa7f5a66c7210": { "inputs": {}, "name": "More conversations to open", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_ab900f607c20c886", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_cef8139f292e20b1": { "inputs": { "userIds": { "kind": "value", "value": [ "YOUR_SLACK_USER_ID" ] } }, "name": "Open a Slack direct message", "kind": "task", "task": { "inputs": [ { "description": "The single Slack user to include in the DM.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "The Slack user ID." }, "minItems": 1, "maxItems": 1, "description": "The single Slack user to include in the DM." }, "nullable": false, "handle": "userIds" }, { "description": "Whether Slack should avoid creating a new conversation.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should avoid creating a new conversation." }, "nullable": true, "handle": "preventCreation", "value": null } ], "outputs": [ { "description": "The opened Slack conversation ID.", "jsonSchema": { "type": "string", "description": "The opened Slack conversation ID." }, "nullable": false, "handle": "channelId" }, { "description": "A normalized Slack conversation record.", "jsonSchema": { "type": "object", "properties": { "channelId": { "type": "string", "description": "The unique identifier of the conversation." }, "name": { "type": [ "string", "null" ], "description": "The name of the conversation when available." }, "type": { "type": "string", "enum": [ "public_channel", "private_channel", "im", "mpim", "unknown" ], "description": "The normalized Slack conversation type." }, "isArchived": { "type": [ "boolean", "null" ], "description": "Whether the conversation is archived." }, "isPrivate": { "type": [ "boolean", "null" ], "description": "Whether the conversation is private." }, "isMember": { "type": [ "boolean", "null" ], "description": "Whether the connected Slack identity is a member." }, "memberCount": { "type": "integer", "description": "The member count when Slack provides it." }, "topic": { "type": [ "string", "null" ], "description": "The conversation topic." }, "purpose": { "type": [ "string", "null" ], "description": "The conversation purpose." }, "userId": { "type": "string", "description": "The linked user identifier for IM conversations." }, "locale": { "type": "string", "description": "The locale returned by Slack when requested." }, "created": { "type": "integer", "description": "Creation time as a Unix timestamp in seconds when Slack provides it." }, "updated": { "type": "integer", "description": "Last settings update time, passed through as Slack returns it. Slack documents epoch milliseconds for channels (unlike created) and a Unix timestamp for legacy IM and MPIM objects." }, "creatorId": { "type": "string", "description": "The user who created the conversation when Slack provides it." }, "isShared": { "type": "boolean", "description": "Whether the conversation is shared with another workspace." }, "isExtShared": { "type": "boolean", "description": "Whether the conversation is shared with an external organization." }, "isOrgShared": { "type": "boolean", "description": "Whether the conversation is shared between workspaces of the same Enterprise organization." }, "contextTeamId": { "type": "string", "description": "The ID of the workspace the conversation is within when Slack provides it." }, "lastRead": { "type": "string", "description": "The last-read message timestamp when Slack provides it." }, "unreadCount": { "type": "integer", "description": "The unread message count when Slack provides it." } }, "required": [ "channelId", "name", "type", "isArchived", "isPrivate", "isMember", "topic", "purpose" ], "additionalProperties": false, "description": "A normalized Slack conversation record." }, "nullable": false, "handle": "conversation" } ], "name": "Open a Slack direct message", "executor": { "kind": "connector", "action": "slack.open_conversation" } } }, "n_5dc2960207f163f4": { "inputs": { "items": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1ac23d298a41c968", "output": "items" } ] }, "channels": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ab900f607c20c886", "output": "channels" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5dc2960207f163f4", "output": "state" } ] }, "lastTimestamp": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_123791ca1aae4221", "output": "ts" } ] } }, "name": "Prepare each to-do message", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" }, { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "channels" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "string", "description": "The timestamp identifier of the posted message." }, "nullable": true, "handle": "lastTimestamp" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack legacy attachment object. Prefer blocks for new messages when possible." }, "minItems": 1, "description": "Slack legacy attachments to include in the message." }, "nullable": true, "handle": "attachments" } ], "name": "Prepare each to-do message", "moduleId": "n_35e77cba8feb8a20", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_904f062d2fedebf8": { "inputs": {}, "name": "More to-dos to send", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_5dc2960207f163f4", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_123791ca1aae4221": { "inputs": { "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5dc2960207f163f4", "output": "channelId" } ] }, "text": { "kind": "value", "value": "# TO DO" }, "attachments": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5dc2960207f163f4", "output": "attachments" } ] } }, "name": "Send a Slack direct message", "kind": "task", "task": { "inputs": [ { "description": "The Slack conversation or channel ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text.", "jsonSchema": { "type": "string", "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text." }, "nullable": true, "handle": "text", "value": null }, { "description": "Slack Block Kit blocks to render in the message.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack Block Kit block object. Pass the block exactly as Slack documents it." }, "minItems": 1, "description": "Slack Block Kit blocks to render in the message." }, "nullable": true, "handle": "blocks", "value": null }, { "description": "Slack legacy attachments to include in the message.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack legacy attachment object. Prefer blocks for new messages when possible." }, "minItems": 1, "description": "Slack legacy attachments to include in the message." }, "nullable": true, "handle": "attachments", "value": null }, { "description": "Whether Slack should unfurl links in the message.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should unfurl links in the message." }, "nullable": true, "handle": "unfurlLinks", "value": null }, { "description": "Whether Slack should unfurl media in the message.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should unfurl media in the message." }, "nullable": true, "handle": "unfurlMedia", "value": null }, { "description": "Slack message metadata to attach to the message.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Slack message metadata to attach to the message." }, "nullable": true, "handle": "metadata", "value": null } ], "outputs": [ { "description": "The timestamp identifier of the posted message.", "jsonSchema": { "type": "string", "description": "The timestamp identifier of the posted message." }, "nullable": false, "handle": "ts" }, { "description": "The channel ID where the message was posted.", "jsonSchema": { "type": "string", "description": "The channel ID where the message was posted." }, "nullable": false, "handle": "channelId" } ], "name": "Send a Slack direct message", "executor": { "kind": "connector", "action": "slack.post_message" } } } }, "edges": [ { "source": "n_56a250d704677aaf", "target": "n_1ac23d298a41c968" }, { "source": "n_1ac23d298a41c968", "target": "n_02ea1c67a7d89d82" }, { "source": "n_02ea1c67a7d89d82", "target": "n_d122432d7343e5f6", "sourceHandle": "yes" }, { "source": "n_d122432d7343e5f6", "target": "n_1ac23d298a41c968" }, { "source": "n_02ea1c67a7d89d82", "target": "n_ab900f607c20c886", "sourceHandle": "otherwise" }, { "source": "n_ab900f607c20c886", "target": "n_7b1fa7f5a66c7210" }, { "source": "n_7b1fa7f5a66c7210", "target": "n_cef8139f292e20b1", "sourceHandle": "yes" }, { "source": "n_cef8139f292e20b1", "target": "n_ab900f607c20c886" }, { "source": "n_7b1fa7f5a66c7210", "target": "n_5dc2960207f163f4", "sourceHandle": "otherwise" }, { "source": "n_5dc2960207f163f4", "target": "n_904f062d2fedebf8" }, { "source": "n_904f062d2fedebf8", "target": "n_123791ca1aae4221", "sourceHandle": "yes" }, { "source": "n_123791ca1aae4221", "target": "n_5dc2960207f163f4" } ] } }, "modules": { "n_13e0f6830b9f251a": { "name": "Collect matching to-dos", "imports": [], "source": "export default function collect({previousState,page,targetUserId}) {\n if(typeof targetUserId!=='string'||!targetUserId.trim()||targetUserId==='YOUR_NOTION_USER_ID')throw Error('Set the Notion user ID.');\n const normalize=s=>s.replace(/-/g,'').toLowerCase(),target=normalize(targetUserId);\n const state=previousState??{items:[],seen:[],cursor:null,started:false};\n if(!state.started){if(page!=null)throw Error('Unexpected first page');return {state:{...state,started:true},cursor:null,more:true,items:[]};}\n if(!page||page.object!=='list'||!Array.isArray(page.results)||typeof page.has_more!=='boolean'||!Object.hasOwn(page,'next_cursor'))throw Error('Invalid Notion response');\n if(page.request_status?.type==='incomplete')throw Error('Notion returned an incomplete page');\n if(page.has_more&&(typeof page.next_cursor!=='string'||!page.next_cursor||state.seen.includes(page.next_cursor)))throw Error('Invalid or repeated Notion cursor');\n if(!page.has_more&&page.next_cursor!=null)throw Error('Unexpected Notion cursor');\n const items=[...state.items];for(const block of page.results){if(!block||typeof block!=='object'||typeof block.type!=='string')throw Error('Invalid block');if(block.type!=='to_do')continue;const todo=block.to_do;if(!todo||typeof todo.checked!=='boolean'||!Array.isArray(todo.rich_text))throw Error('Invalid to-do');if(todo.checked)continue;const mention=todo.rich_text[1];if(mention?.type!=='mention'||mention.mention?.type!=='user')continue;const user=mention.mention.user;if(!user||typeof user.id!=='string')throw Error('Missing mentioned user ID');if(normalize(user.id)!==target)continue;const first=todo.rich_text[0];if(first?.type!=='text'||typeof first.text?.content!=='string'||!first.text.content.trim())throw Error('Missing to-do title');const title=first.text.content;if(title.length>2900)throw Error('To-do title is too long');items.push({id:block.id,title});}\n const seen=page.has_more?[...state.seen,page.next_cursor]:state.seen;return {state:{items,seen,cursor:page.next_cursor,started:true},cursor:page.next_cursor,more:page.has_more,items};\n}\n" }, "n_848f4647f0a3fc72": { "name": "Open a DM for each to-do", "imports": [], "source": "export default function nextOpen({items,previousState,lastChannelId}) {\n let state=previousState??{index:0,channels:[]};if(previousState){if(typeof lastChannelId!=='string'||!lastChannelId)throw Error('Slack did not return a conversation ID');state={index:state.index+1,channels:[...state.channels,lastChannelId]};}\n if(state.index>items.length)throw Error('Invalid conversation state');return {state,more:state.indexitems.length||channels.length!==items.length)throw Error('Invalid report state');const more=state.index