Bank reconciliation on SFTP statement
This workflow retrieves the daily bank statement dropped on an SFTP server, enriches each transaction, archives the processed file, then notifies the treasury team.
Loading the diagram…
The need
The treasury team reconciles the bank statement transactions every day. The daily statement is dropped on an SFTP server: it must be read, each line enriched (running balance, abnormal amounts), the processed file archived, then the treasury team notified.
What the workflow does
- New bank statement — the File trigger (source
FTP_SFTP) watches the drop directory and fires the workflow when the file arrives; its name arrives indata.filename. - Read the SFTP statement — the FTP — Read node loads the file content into
context.file(if the file is valid JSON, it is automatically parsed into an object). - Enrich the transactions — the Code node splits the CSV line by line, computes the running balance and flags amounts above €5,000; the result is published in
context.variables(list, balance, count of abnormal amounts). - Archive the statement — the FTP — Move node moves the file from the
relevesdirectory (point A) toarchive(point B), to keep a trace without reprocessing the same file. - Notify the treasury team — an email summarizes the number of transactions, the computed balance and the flagged amounts.
What you need
- A File trigger (source
FTP_SFTP) that watches the drop directory. - The FTP nodes (Read, Move), the Code node and the Send an email node.
Construction
Copy the workflow definition and paste it into the editor (the Trigger node is recreated automatically, it is not in the snippet):
[
{
"id": "read-110",
"name": "Read the SFTP statement",
"config": {
"type": "READ_FILE_FROM_FTP",
"filePath": "/banque/releves/{{ data.filename }}",
"parseAs": {
"type": "RAW"
},
"fileKey": "file",
"stopWorkflowOnFailure": true
},
"parents": [
"trigger"
],
"position": {
"x": 0,
"y": 200
},
"next": "code-110"
},
{
"id": "code-110",
"name": "Enrich the transactions",
"config": {
"type": "CODE",
"code": "const raw = typeof context.file === 'string' ? context.file : JSON.stringify(context.file);\nconst transactions = [];\nlet balance = 0;\nlet flagged = 0;\n\nraw.split('\\n').forEach((line, index) => {\n if (index === 0 || !line.trim()) return;\n const [date, label, amount] = line.split(';');\n const value = Number(amount);\n if (Number.isNaN(value)) return;\n balance += value;\n const transaction = { date, label, amount: value, balance };\n if (Math.abs(value) > 5000) {\n transaction.flagged = true;\n flagged += 1;\n }\n transactions.push(transaction);\n});\n\ncontext.variables.transactions = transactions;\ncontext.variables.balance = balance;\ncontext.variables.flaggedCount = flagged;\n\nreturn { transactions, balance, flaggedCount: flagged };"
},
"parents": [
"read-110"
],
"position": {
"x": 0,
"y": 400
},
"next": "move-110"
},
{
"id": "move-110",
"name": "Archive the statement",
"config": {
"type": "MOVE_FILE_FROM_FTP",
"sourcePath": "/banque/releves/{{ data.filename }}",
"destinationPath": "/banque/archive/{{ data.filename }}",
"stopWorkflowOnFailure": true
},
"parents": [
"code-110"
],
"position": {
"x": 0,
"y": 600
},
"next": "email-110"
},
{
"id": "email-110",
"name": "Notify the treasury team",
"config": {
"type": "SEND_EMAIL",
"to": [
"tresorerie@banque-partenaires.fr"
],
"subject": "Bank statement processed: {{ data.filename }}",
"content": "{{ context.variables.transactions.length }} transactions, balance of {{ context.variables.balance }} €, including {{ context.variables.flaggedCount }} amount(s) above 5,000 €.",
"sendAsHtml": true
},
"parents": [
"move-110"
],
"position": {
"x": 0,
"y": 800
}
}
]
Check that it works
- CSV format: the Code node skips the header then splits each line with
split(';')ondate;label;amount. - Expected data:
filePath(read) andsourcePath(move) use{{ data.filename }}; the content is loaded intocontext.file, the default output key of the FTP — Read node. - Sequence: the email is only sent once the move succeeds; with
stopWorkflowOnFailure: true, an archiving failure stops the flow without notifying. - Deploy the workflow (Deploy a workflow), then trigger the real scenario: every execution generates a trace you can view (View traces).
Variations
- Adapt the separator or the structure of the CSV if the bank statement differs from the
date;label;amountformat. - If the file name is not available, list the directory with an FTP — List node then select the most recent file.
- The output key of the FTP — Read node (
context.file) is customizable in the node configuration.
Going further
See also: Examples and integrations