Workflow: develop a modular n8n workflow that supports Wait Splitout Create Scheduled
Integrations: code, convertToFile, extractFromFile, httpRequest, noOp, readWriteFile, scheduleTrigger, set, splitInBatches, splitOut, stickyNote, wait

{"nodes": [{"id": "6aa059e4-e78f-4bbd-a707-994a39840f97", "name": "Create Session", "type": "n8n-nodes-base.httpRequest", "position": [-520, 240], "parameters": {"url": "https://bsky.social/xrpc/com.atproto.server.createSession", "method": "POST", "options": {}, "sendBody": true, "bodyParameters": {"parameters": [{"name": "identifier", "value": "youruser.bsky.social"}, {"name": "password", "value": "your-app-passord-here"}]}}, "notesInFlow": true, "typeVersion": 4.1}, {"id": "143e37b0-de79-4329-99a2-51484c9609a8", "name": "List followers", "type": "n8n-nodes-base.httpRequest", "position": [-280, 240], "parameters": {"url": "https://bsky.social/xrpc/app.bsky.graph.getFollowers", "options": {"response": {"response": {"responseFormat": "json"}}, "pagination": {"pagination": {"parameters": {"parameters": [{"name": "cursor", "value": "={{ $response.body.cursor }}"}]}, "maxRequests": 2, "requestInterval": 250, "limitPagesFetched": true}}}, "sendQuery": true, "sendHeaders": true, "queryParameters": {"parameters": [{"name": "actor", "value": "={{ $json.did }}"}, {"name": "limit", "value": "100"}]}, "headerParameters": {"parameters": [{"name": "Authorization", "value": "=Bearer {{ $item(\"0\").$node[\"Create Session\"].json[\"accessJwt\"] }}"}]}}, "typeVersion": 4.2}, {"id": "f1436a63-a23f-4082-9209-12c21a26ad91", "name": "Convert to File", "type": "n8n-nodes-base.convertToFile", "position": [100, 620], "parameters": {"options": {"fileName": "followers-basuracero.json"}, "operation": "toJson"}, "typeVersion": 1.1}, {"id": "f8beea47-6f36-4dfb-b2e7-bf94adb63e66", "name": "Extract from File", "type": "n8n-nodes-base.extractFromFile", "position": [100, 240], "parameters": {"options": {}, "operation": "fromJson"}, "typeVersion": 1}, {"id": "41658372-3054-4909-850b-3bffd1b1b79c", "name": "Split Out", "type": "n8n-nodes-base.splitOut", "position": [520, 240], "parameters": {"options": {"destinationFieldName": "did"}, "fieldToSplitOut": "newDids"}, "typeVersion": 1}, {"id": "c94aa8e9-06db-4b24-a20a-5615b7129023", "name": "Loop Over Items", "type": "n8n-nodes-base.splitInBatches", "position": [740, 240], "parameters": {"options": {}}, "typeVersion": 3}, {"id": "4d1c6a2f-3acd-4783-96d4-693ced06fd97", "name": "Wait", "type": "n8n-nodes-base.wait", "position": [-100, 620], "webhookId": "b1608475-db84-4f23-acd6-d003f5094afd", "parameters": {}, "typeVersion": 1.1}, {"id": "e4125cb8-8eb5-4cf0-b00d-e7ce4ec8236e", "name": "Find new followers", "type": "n8n-nodes-base.code", "position": [280, 240], "parameters": {"jsCode": "// Datos de entrada\nconst listFollowers = $('List followers').all()[0].json.followers;\nconst extractFromFile = $('Extract from File').all()[0].json.data[0].followers;\n\n// Verificar que tenemos acceso a los datos\nconsole.log('listFollowers length:', Array.isArray(listFollowers) ? listFollowers.length : 'no es array');\nconsole.log('extractFromFile length:', Array.isArray(extractFromFile) ? extractFromFile.length : 'no es array');\n\n// Mostrar algunos ejemplos de cada lista\nconsole.log('Ejemplo de listFollowers:', listFollowers?.slice(0, 2));\nconsole.log('Ejemplo de extractFromFile:', extractFromFile?.slice(0, 2));\n\n// Crear conjunto de DIDs del archivo extra\u00eddo\nconst existingDids = new Set(extractFromFile?.map(item => item.did) || []);\nconsole.log('DIDs existentes:', Array.from(existingDids).slice(0, 5));\n\n// Filtrar listFollowers\nconst newFollowers = listFollowers?.filter(follower => !existingDids.has(follower.did)) || [];\n\nreturn {\n  json: {\n    debug: {\n      listFollowersCount: listFollowers?.length || 0,\n      extractFromFileCount: extractFromFile?.length || 0,\n      existingDidsCount: existingDids.size,\n      newFollowersCount: newFollowers.length\n    },\n    newFollowers,\n    newDids: newFollowers.map(follower => follower.did),\n    count: newFollowers.length\n  }\n}"}, "typeVersion": 2, "alwaysOutputData": true}, {"id": "a28accb5-ee47-431f-83e4-376425e9899e", "name": "Read followers from file", "type": "n8n-nodes-base.readWriteFile", "position": [-80, 240], "parameters": {"options": {}, "fileSelector": "=followers-{{ $('Create Session').item.json.handle }}.json"}, "typeVersion": 1}, {"id": "aa2ab5e1-eb6f-4657-9a9a-66417ffa421e", "name": "Save followers to file", "type": "n8n-nodes-base.readWriteFile", "position": [280, 620], "parameters": {"options": {"append": false}, "fileName": "=followers-{{ $('Create Session').item.json.handle }}.json", "operation": "write"}, "typeVersion": 1}, {"id": "9a4fb5e5-f2f6-4aa1-846e-8d6460ad7765", "name": "Define welcome message", "type": "n8n-nodes-base.set", "position": [980, 260], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "afe7fe9b-3bd4-4429-afe9-81e5fe934e07", "name": "text", "type": "string", "value": "Hello, thanks for your follow. You can read more about my over my site:"}, {"id": "97590cd1-9d85-442b-baa3-bad849ff9be0", "name": "link", "type": "string", "value": "https://yoursite.com"}]}}, "typeVersion": 3.4}, {"id": "594ce66f-acbd-4c31-806c-382aa9a98ed0", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [920, 160], "parameters": {"width": 230, "height": 266, "content": "### 2. Define your welcome message and link here"}, "typeVersion": 1}, {"id": "c24a7971-11a7-4164-9f2d-78335264f250", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [220, 460], "parameters": {"width": 231, "height": 338, "content": "### 3. **Important** \n\nYou need to manually run \"Save followers to file\" once before the first time so you populate your list of existing followers"}, "typeVersion": 1}, {"id": "c6e766cf-a118-4db0-8e3c-32662c40737b", "name": "Send message", "type": "n8n-nodes-base.httpRequest", "position": [1360, 260], "parameters": {"url": "={{ $item(\"0\").$node[\"Create Session\"].json.didDoc.service[0].serviceEndpoint }}/xrpc/chat.bsky.convo.sendMessage", "method": "POST", "options": {}, "jsonBody": "={\n  \"convoId\" : \"{{ $json.convo.id }}\",\n  \"message\" : {\n    \"text\" : \"{{ $('Define welcome message').item.json.text }}\\n\\n{{ $('Define welcome message').item.json.link }}\",\n    \"facets\" : [\n     {\n      \"index\" : {\n        \"byteStart\": {{ $('Define welcome message').item.json.text.length }},\n        \"byteEnd\": {{ $('Define welcome message').item.json.text.length + 3 + $('Define welcome message').item.json.link.length}}\n      },\n      \"features\": [\n          {\n            \"$type\": \"app.bsky.richtext.facet#link\",\n            \"uri\": \"{{ $('Define welcome message').item.json.link }}\"\n          }\n        ]\n      }\n    ]\n  }\n}", "sendBody": true, "sendHeaders": true, "specifyBody": "json", "headerParameters": {"parameters": [{"name": "Authorization", "value": "=Bearer {{ $item(\"0\").$node[\"Create Session\"].json[\"accessJwt\"] }}"}, {"name": "Atproto-Proxy", "value": "did:web:api.bsky.chat#bsky_chat"}]}}, "typeVersion": 4.2}, {"id": "33aa7e0c-58fe-4527-a94e-49bec0e06325", "name": "Get conversation ID", "type": "n8n-nodes-base.httpRequest", "position": [1200, 260], "parameters": {"url": "={{ $item(\"0\").$node[\"Create Session\"].json.didDoc.service[0].serviceEndpoint }}/xrpc/chat.bsky.convo.getConvoForMembers", "options": {}, "sendQuery": true, "sendHeaders": true, "queryParameters": {"parameters": [{"name": "members", "value": "={{ $('Split Out').item.json.did }}"}]}, "headerParameters": {"parameters": [{"name": "Authorization", "value": "=Bearer {{ $item(\"0\").$node[\"Create Session\"].json[\"accessJwt\"] }}"}, {"name": "Atproto-Proxy", "value": "did:web:api.bsky.chat#bsky_chat"}]}}, "typeVersion": 4.2}, {"id": "066f1d0d-319a-4676-9c0a-5b00d206ffd2", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [-40, -100], "parameters": {"color": 5, "width": 479, "height": 307, "content": "## Send a welcome private message to your new BlueSky followers\n\nThis flow will save your current followers in a file and check for new ones on the next execution, sending them the Defined message an link as a private message.\n\nOnce messages are sent, the new list of followers will be saved into the file.\n\n**Important: Follow the yellow notes in order before enabling the full flow for the first time**"}, "typeVersion": 1}, {"id": "a61e301e-a0ca-48e9-9a46-05975662aa90", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [-560, 40], "parameters": {"width": 181, "height": 364, "content": "### 1. Define your Bluesky user and app password first\n\nThe App password should have access to private messages"}, "typeVersion": 1}, {"id": "1b1dcd74-7a71-49e4-99e8-c079b692aca5", "name": "Each 60 minutes", "type": "n8n-nodes-base.scheduleTrigger", "position": [-720, 240], "parameters": {"rule": {"interval": [{"field": "minutes", "minutesInterval": 60}]}}, "typeVersion": 1.2}, {"id": "6acf153c-cdc6-42c1-85f9-2692c8777eef", "name": "No Operation, do nothing", "type": "n8n-nodes-base.noOp", "position": [520, 620], "parameters": {}, "typeVersion": 1}], "pinData": {}, "connections": {"Wait": {"main": [[{"node": "Convert to File", "type": "main", "index": 0}]]}, "Split Out": {"main": [[{"node": "Loop Over Items", "type": "main", "index": 0}]]}, "Send message": {"main": [[{"node": "Loop Over Items", "type": "main", "index": 0}]]}, "Create Session": {"main": [[{"node": "List followers", "type": "main", "index": 0}]]}, "List followers": {"main": [[{"node": "Read followers from file", "type": "main", "index": 0}, {"node": "Wait", "type": "main", "index": 0}]]}, "Convert to File": {"main": [[{"node": "Save followers to file", "type": "main", "index": 0}]]}, "Each 60 minutes": {"main": [[{"node": "Create Session", "type": "main", "index": 0}]]}, "Loop Over Items": {"main": [[], [{"node": "Define welcome message", "type": "main", "index": 0}]]}, "Extract from File": {"main": [[{"node": "Find new followers", "type": "main", "index": 0}]]}, "Find new followers": {"main": [[{"node": "Split Out", "type": "main", "index": 0}]]}, "Get conversation ID": {"main": [[{"node": "Send message", "type": "main", "index": 0}]]}, "Define welcome message": {"main": [[{"node": "Get conversation ID", "type": "main", "index": 0}]]}, "Save followers to file": {"main": [[{"node": "No Operation, do nothing", "type": "main", "index": 0}]]}, "Read followers from file": {"main": [[{"node": "Extract from File", "type": "main", "index": 0}]]}}}