{ "kind": "open-flow-workflow", "version": 1, "name": "Notify Upcoming Daylight Saving Changes", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_2a3f36ceb396e6fd": { "name": "Run on demand", "kind": "start", "inputDefs": [] }, "n_9eb3ab1748159764": { "name": "Every day at 00:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 0 * * *", "timezone": "UTC" } ] }, "n_b1c2728285e6a958": { "inputs": { "zones": { "kind": "value", "value": [ "America/New_York", "Europe/Warsaw" ] }, "instant": { "kind": "value", "value": null } }, "name": "Check tomorrow in each zone", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string" } }, "nullable": false, "handle": "zones" }, { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "instant" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "alerts" } ], "name": "Check tomorrow in each zone", "moduleId": "n_1f8a5a3187e8005e", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_59540d83042c2b3f": { "inputs": { "alerts": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b1c2728285e6a958", "output": "alerts" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59540d83042c2b3f", "output": "state" } ] } }, "name": "Prepare the next alert", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "alerts" }, { "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" }, "nullable": false, "handle": "message" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "subject" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Prepare the next alert", "moduleId": "n_7fa94832d451605a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_7535786bda3d76fe": { "inputs": {}, "name": "Another change to report?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "send", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_59540d83042c2b3f", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_62a5496b6522c39a": { "inputs": { "channelId": { "kind": "value", "value": "YOUR_SLACK_CHANNEL_ID" }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59540d83042c2b3f", "output": "message" } ] } }, "name": "Send the Slack notification", "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 the Slack notification", "executor": { "kind": "connector", "action": "slack.post_message" } } }, "n_2f858bcb5135b5e0": { "inputs": { "to": { "kind": "value", "value": [ "recipient@example.com" ] }, "subject": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59540d83042c2b3f", "output": "subject" } ] }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59540d83042c2b3f", "output": "message" } ] } }, "name": "Send the email notification", "kind": "task", "task": { "inputs": [ { "description": "The primary recipient email addresses.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "minItems": 1, "description": "The primary recipient email addresses." }, "nullable": false, "handle": "to" }, { "description": "The carbon-copy recipient email addresses.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "minItems": 1, "description": "The carbon-copy recipient email addresses." }, "nullable": true, "handle": "cc", "value": null }, { "description": "The blind-carbon-copy recipient email addresses.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "minItems": 1, "description": "The blind-carbon-copy recipient email addresses." }, "nullable": true, "handle": "bcc", "value": null }, { "description": "An email address.", "jsonSchema": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "nullable": true, "handle": "replyTo", "value": null }, { "description": "The email subject.", "jsonSchema": { "type": "string", "description": "The email subject." }, "nullable": false, "handle": "subject" }, { "description": "The plain text email body.", "jsonSchema": { "type": "string", "description": "The plain text email body." }, "nullable": true, "handle": "text", "value": null }, { "description": "The HTML email body.", "jsonSchema": { "type": "string", "description": "The HTML email body." }, "nullable": true, "handle": "html", "value": null }, { "description": "Attachments to fetch and include in the outgoing email.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "filename": { "type": "string", "minLength": 1, "description": "The filename to use for the outgoing attachment." }, "contentType": { "type": "string", "minLength": 1, "description": "The MIME content type of the attachment." }, "contentUrl": { "type": "string", "format": "uri", "description": "The public HTTP or HTTPS URL to fetch attachment content from." } }, "required": [ "filename", "contentUrl" ], "additionalProperties": false, "description": "An attachment to fetch and include in an outgoing email." }, "minItems": 1, "maxItems": 10, "description": "Attachments to fetch and include in the outgoing email." }, "nullable": true, "handle": "attachments", "value": null } ], "outputs": [ { "description": "The SMTP Message-ID returned by the mail client.", "jsonSchema": { "type": [ "string", "null" ], "description": "The SMTP Message-ID returned by the mail client." }, "nullable": true, "handle": "messageId" }, { "description": "Recipient addresses accepted by the SMTP server.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses accepted by the SMTP server." }, "nullable": false, "handle": "accepted" }, { "description": "Recipient addresses rejected by the SMTP server.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses rejected by the SMTP server." }, "nullable": false, "handle": "rejected" }, { "description": "The SMTP response text returned by the server.", "jsonSchema": { "type": "string", "description": "The SMTP response text returned by the server." }, "nullable": false, "handle": "response" } ], "name": "Send the email notification", "executor": { "kind": "connector", "action": "generic_imap.send_email" } } }, "n_810f85a55746b3d5": { "inputs": { "accepted": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2f858bcb5135b5e0", "output": "accepted" } ] }, "rejected": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2f858bcb5135b5e0", "output": "rejected" } ] }, "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2f858bcb5135b5e0", "output": "response" } ] } }, "name": "Confirm SMTP acceptance", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses accepted by the SMTP server." }, "nullable": false, "handle": "accepted" }, { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "format": "email", "description": "An email address." }, "description": "Recipient addresses rejected by the SMTP server." }, "nullable": false, "handle": "rejected" }, { "jsonSchema": { "type": "string", "description": "The SMTP response text returned by the server." }, "nullable": false, "handle": "response" } ], "outputs": [], "name": "Confirm SMTP acceptance", "moduleId": "n_36159eaaa56c53dd", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_fb7b22ed5318efaf": { "name": "Reported change count", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "outputMappings": { "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_59540d83042c2b3f", "output": "count" } ] } } } }, "edges": [ { "source": "n_2a3f36ceb396e6fd", "target": "n_b1c2728285e6a958" }, { "source": "n_9eb3ab1748159764", "target": "n_b1c2728285e6a958" }, { "source": "n_b1c2728285e6a958", "target": "n_59540d83042c2b3f" }, { "source": "n_59540d83042c2b3f", "target": "n_7535786bda3d76fe" }, { "source": "n_7535786bda3d76fe", "target": "n_62a5496b6522c39a", "sourceHandle": "send" }, { "source": "n_62a5496b6522c39a", "target": "n_2f858bcb5135b5e0" }, { "source": "n_2f858bcb5135b5e0", "target": "n_810f85a55746b3d5" }, { "source": "n_810f85a55746b3d5", "target": "n_59540d83042c2b3f" }, { "source": "n_7535786bda3d76fe", "target": "n_fb7b22ed5318efaf", "sourceHandle": "otherwise" } ] } }, "modules": { "n_1f8a5a3187e8005e": { "name": "Check tomorrow in each zone", "imports": [], "source": "export default function planDst({zones,instant}){\n if(!Array.isArray(zones)||zones.some(z=>typeof z!=='string'||!z.trim())||new Set(zones).size!==zones.length)throw Error('Configure distinct IANA time zones');\n const now=instant===null||instant===undefined?Date.now():Date.parse(instant);if(!Number.isFinite(now))throw Error('Invalid time');\n const alerts=[];\n for(const zone of zones){\n let fmt,off;try{fmt=new Intl.DateTimeFormat('en-CA-u-ca-iso8601',{timeZone:zone,year:'numeric',month:'2-digit',day:'2-digit'});off=new Intl.DateTimeFormat('en-US',{timeZone:zone,timeZoneName:'shortOffset'});}catch{throw Error('Invalid time zone: '+zone)}\n const dateKey=t=>{let p=Object.fromEntries(fmt.formatToParts(new Date(t)).map(x=>[x.type,x.value]));return p.year+'-'+p.month+'-'+p.day};\n const keyAt=d=>new Date(d).toISOString().slice(0,10), today=dateKey(now),base=Date.parse(today+'T00:00:00Z'),tomorrow=keyAt(base+86400000),after=keyAt(base+172800000);\n function boundary(key,center){let lo=center-36*3600000,hi=center+36*3600000;while(lo{let v=off.formatToParts(new Date(t)).find(x=>x.type==='timeZoneName')?.value;if(v==='GMT')return 0;let m=/^GMT([+-])(\\d{1,2})(?::(\\d{2}))?$/.exec(v);if(!m)throw Error('Unsupported time zone offset');return (m[1]==='-'?-1:1)*(Number(m[2])*60+Number(m[3]||0));};\n function isDst(t){const d=dateKey(t),[y,m,day]=d.split('-').map(Number),jan=Date.UTC(y,0,2),may=Date.UTC(y,4,day+1);return offset(t)>offset(boundary(keyAt(jan),jan)-1000)||offset(t)>offset(boundary(keyAt(may),may)-1000);}\n const start=boundary(tomorrow,base+86400000),end=boundary(after,base+172800000);if(dateKey(start)!==tomorrow||dateKey(end-1)!==tomorrow)throw Error('Cannot identify tomorrow in '+zone);\n if(isDst(start-1000)!==isDst(end-1000)){const message='Tomorrow is Daylight Saving Time change in zone '+zone+' - remember to adjust meeting times!';alerts.push({zone,message,subject:'DST change tomorrow in '+zone});}\n }\n return {alerts};\n}\n" }, "n_7fa94832d451605a": { "name": "Prepare the next alert", "imports": [], "source": "export default function nextDst({alerts,previous}){if(!Array.isArray(alerts))throw Error('Invalid alerts');const i=previous?.index??0;if(!Number.isInteger(i)||i<0)throw Error('Invalid alert state');if(i>=alerts.length)return {state:{index:i},more:false,message:'',subject:'',count:i};const a=alerts[i];if(!a||typeof a.message!=='string'||typeof a.subject!=='string')throw Error('Invalid alert');return{state:{index:i+1},more:true,message:a.message,subject:a.subject,count:i+1};}\n" }, "n_36159eaaa56c53dd": { "name": "Confirm SMTP acceptance", "imports": [], "source": "export default function confirmEmail({accepted,rejected,response}){if(!Array.isArray(accepted)||!accepted.length||!Array.isArray(rejected)||rejected.length||typeof response!=='string')throw Error('Email was not accepted by SMTP');return {};}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }