AutomationFlowsEmail & Gmail › 14310 Send Overdue Invoice Payment Reminders with Ifirma Gmail Postgrid and…

14310 Send Overdue Invoice Payment Reminders with Ifirma Gmail Postgrid and…

Original n8n title: 14310 Send Overdue Invoice Payment Reminders with Ifirma Gmail Postgrid and Slack

14310 Send Overdue Invoice Payment Reminders With Ifirma Gmail Postgrid And Slack. Uses httpRequest, stopAndError, slack, gmail. Scheduled trigger; 53 nodes.

Cron / scheduled trigger★★★★★ complexity53 nodesHTTP RequestStop And ErrorSlackGmail
Email & Gmail Trigger: Cron / scheduled Nodes: 53 Complexity: ★★★★★ Added:

This workflow follows the Gmail → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "_templateId": 14310,
  "_templateName": "Send overdue invoice payment reminders with iFirma, Gmail, PostGrid and Slack",
  "_templateDescription": "Overdue Invoice Payment Reminder Workflow Documentation\n\nWhat Is This?\n\nThis workflow is an automated invoice payment tracking and reminder system for the Polish accounting service iFirma.pl. It monitors unpaid and overdue invoices, then automatically sends escalating reminders to contractors based on configurable time thresholds. The system handles three escalation levels: payment reminders before/at due date, pre-trial summons, and formal legal action notices.\n\nWho Is It For?\n\nDesigned for Polish businesses, accounting departments, and financial controllers using iFirma.pl for invoice management. This workflow is essential for companies struggling with late payments and cash flow management, particularly those needing to enforce payment terms professionally and systematically.\n\nB2B service providers, consultancies, software development agencies, and any business issuing invoices to other companies will benefit from automated payment enforcement. The workflow eliminates manual tracking of payment deadlines and ensures consistent, professional follow-up with delinquent clients.\n\nWhether managing a handful of high-value invoices or processing hundreds of transactions monthly, this automation delivers timely notifications without manual calendar monitoring or spreadsheet management.\n\nHow Does It Work?\n\nThis end-to-end invoice monitoring automation consists of four main stages:\n\n1. Configuration & Authentication  \nSets up user credentials (email/login, API key), defines escalation timeframes (X days before due date, Y days after, Z days after), and prepares company details for legal correspondence. The workflow then constructs a cryptographic authentication signature using HMAC-SHA1 algorithm, which iFirma requires for API access.\n\n\n2. Invoice Retrieval & Filtering  \nConnects to iFirma API to fetch all unpaid, partially paid, and overdue invoices, then filters them based on payment deadline dates. Only invoices that are either overdue or approaching their due date (within X days) proceed to the next stage.\n\n\n3. Contractor Data Enrichment  \nFor each qualifying invoice, the workflow fetches complete contractor information from iFirma (since invoice records contain only partial contractor data). This includes email addresses, company names, and addresses needed for sending reminders. The workflow deduplicates contractors to avoid redundant API calls.\n\n\n4. Escalation Logic & Notification Dispatch  \nInvoices are categorized into three groups based on how overdue they are:\n\nPayment Reminder**: Due today or X days before due date \u2192 Sends reminder via iFirma's built-in notification system\nPre-Trial Summons**: Y days after due date \u2192 Sends formal legal warning via email and optionally physical mail (PostGrid)\nLegal Action Notice**: Z days after due date \u2192 Sends notice of commenced legal proceedings via email and optionally physical mail\n\n\nEach action triggers a Slack notification to keep your team informed.\n\nHow To Set It Up?\n\nPrerequisites:\n\nAn active N8N account or self-hosted instance\nAn iFirma.pl account with API access enabled\nA Slack workspace with appropriate bot permissions\n(Optional) PostGrid account for physical mail delivery\n(Optional) Gmail account configured for sending emails\n\nRequired Configuration:\n\nIn the \"Configuration\" node, set the following parameters:\n\nEmail/Login: Your iFirma.pl account email or login username\nAPI Key Invoice: API key from iFirma.pl (found at: Start > Data and Configuration > Extensions and Integrations > API)\nX days before due date: How many days before payment deadline to send the first reminder (default: 7)\nY days after due date: When to send pre-trial summons (default: 7 days overdue)\nZ days after due date: When to send legal action notice (default: 14 days overdue)\n\nYour Company Details Configuration:\n\nIn the \"Your Company Details\" node, provide:\n\nCompany Name, Email, Phone\nFull address (Street, City, Postal Code, Country Code)\nBank details (Bank Name, Account Number, SWIFT Code)\nTax Identification Number (TIN/NIP)\n\nThis information is used for professional letterheads in legal correspondence and PostGrid physical mail delivery.\n\nCredentials Setup:\n\nConfigure Slack OAuth2 credentials for notifications\nSet up Gmail OAuth2 credentials for email sending (if using email route)\nConfigure PostGrid API credentials for physical letter delivery (if using mail route)\n\nScheduling:  \nThe workflow runs automatically every 24 hours via the Schedule Trigger node. For testing, execute manually using the \"Execute workflow\" button.\n\nWhat's More?\n\nCustom Authentication Implementation:  \niFirma.pl requires HMAC-SHA1 authentication, which N8N doesn't provide natively. The workflow includes a complete JavaScript implementation of the SHA-1 cryptographic hash function and HMAC (Hash-based Message Authentication Code) algorithm. This ensures secure API access without external dependencies.\n\nEndpoint Mapping Intelligence:  \nThe workflow automatically maps invoice types (Rodzaj) to the correct iFirma API endpoints. Different invoice types (domestic, foreign, construction, advance payments, etc.) require different API paths for sending reminders.\n\nProfessional HTML Templates:  \nPre-trial summons and legal action notices use professionally formatted HTML templates with proper legal language, payment details, deadlines, and consequences clearly outlined. These templates maintain consistent branding and meet legal communication standards.\n\n\n\nMulti-Channel Notification:  \nReminders can be sent via multiple channels simultaneously: iFirma's e-invoice system, email, physical mail (PostGrid), and internal Slack notifications for team awareness.\n\nUnderstanding the HMAC-SHA1 Authentication\n\nThe \"Encode API Key\" nodes implement a critical security function that N8N cannot perform natively. Here's what happens and why:\n\nWhat the iFirma API Requires:\n\niFirma.pl uses HMAC-SHA1 for request authentication. Each API request must include an Authentication header formatted as:\n\nIAPIS user={userLogin}, hmac-sha1={hash}\n\nThe hash is calculated from: URL + userLogin + keyName + requestBody\n\nWhy Custom Implementation is Needed:\n\nN8N's crypto functions don't include SHA-1 (it's considered outdated for modern security, though still required by some legacy APIs). Therefore, the workflow implements the entire SHA-1 and HMAC algorithms in pure JavaScript.\n\nHow the Algorithm Works:\n\nMessage Construction: Combines the API endpoint URL, user login, key name (\"faktura\"), and request body (if any) into a single string\nHex Key Conversion: The API key from iFirma is provided in hexadecimal format and must be converted to raw bytes\nHMAC Process:\n    Creates inner and outer padding arrays using XOR operations with specific constants (0x36 and 0x5C)\n    Hashes the inner padded message using SHA-1\n    Hashes the outer padding combined with the inner hash result\nSHA-1 Implementation: Uses bitwise operations, rotating functions, and four rounds of 20 operations each to produce a 160-bit hash\nHeader Generation: Formats the final hash as a hex string and constructs the authentication header\n\nWhy Each Step Matters:\n\nDeterministic Hashing**: The same input always produces the same hash, allowing iFirma to verify request authenticity\nBinary Key Conversion**: The hex-to-bytes conversion ensures the secret key is interpreted correctly\nHMAC Security**: Using both inner and outer hashing with different padding prevents length extension attacks\nMessage Integrity**: Including the URL and request body in the hash ensures the request hasn't been tampered with\n\nThank You, Perfect!\n\nVisit my profile for other free business automations. And if you're looking for dedicated software development or custom n8n workflow solutions, don't hesitate to reach out at developers@sailingbyte.com or on sailingbyte.com!\n",
  "_templateCreatedAt": "2026-03-25T10:37:00.891Z",
  "_templateTotalViews": 0,
  "_templateCategory": [],
  "_fetchedAt": "2026-04-05T05:03:43.405Z",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "633ea179-d323-4cad-a9e9-8be577dae54d",
      "name": "Encode API Key",
      "type": "n8n-nodes-base.code",
      "position": [
        -80,
        752
      ],
      "parameters": {
        "jsCode": "const apiKeyHex = $('Configuration').first().json[\"API Key Invoice\"];\nconst url = $input.first().json.url ?? \"\";\nconst userLogin =  $('Configuration').first().json[\"Email/Login\"];\nconst keyName = \"faktura\";\nconst requestBody = $input.first().json.requestBody ?? \"\" \nconst message = (url + userLogin + keyName + requestBody).trim();\n\nfunction hexToBytes(hex) {\n    let bytes = \"\";\n    for (let i = 0; i < hex.length; i += 2) {\n        bytes += String.fromCharCode(parseInt(hex.substr(i, 2), 16));\n    }\n    return bytes;\n}\n\nfunction hmacSha1(key, message) {\n    var blocksize = 64;\n    if (key.length > blocksize) {\n        key = sha1_bin(key);\n    }\n    \n    var ipad = [], opad = [];\n    for (var i = 0; i < blocksize; i++) {\n        var k = (i < key.length) ? key.charCodeAt(i) : 0;\n        ipad[i] = k ^ 0x36;\n        opad[i] = k ^ 0x5C;\n    }\n\n    var hash = sha1_bin(String.fromCharCode.apply(null, ipad) + message);\n    return sha1_hex(String.fromCharCode.apply(null, opad) + hash);\n\n    function sha1_hex(s) { return bin2hex(sha1_bin(s)); }\n    \n    function bin2hex(bin) {\n        var hex = \"0123456789abcdef\";\n        var str = \"\";\n        for (var i = 0; i < bin.length; i++) {\n            var c = bin.charCodeAt(i);\n            str += hex.charAt((c >> 4) & 0xF) + hex.charAt(c & 0xF);\n        }\n        return str;\n    }\n\n    function sha1_bin(s) {\n        var nblk = ((s.length + 8) >> 6) + 1, blks = new Array(nblk * 16);\n        for (var i = 0; i < nblk * 16; i++) blks[i] = 0;\n        for (var i = 0; i < s.length; i++) blks[i >> 2] |= s.charCodeAt(i) << (24 - (i % 4) * 8);\n        blks[s.length >> 2] |= 0x80 << (24 - (s.length % 4) * 8);\n        blks[nblk * 16 - 1] = s.length * 8;\n        var w = new Array(80), a = 1732584193, b = -271733879, c = -1732584194, d = 271733878, e = -1+1234567890;\n        for (var i = 0; i < blks.length; i += 16) {\n            var olda = a, oldb = b, oldc = c, oldd = d, olde = e;\n            for (var j = 0; j < 80; j++) {\n                if (j < 16) w[j] = blks[i + j];\n                else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                var t = add(add(rol(a, 5), ft(j, b, c, d)), add(add(e, w[j]), kt(j)));\n                e = d; d = c; c = rol(b, 30); b = a; a = t;\n            }\n            a = add(a, olda); b = add(b, oldb); c = add(c, oldc); d = add(d, oldd); e = add(e, olde);\n        }\n        return String.fromCharCode((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,\n                                   (b >> 24) & 0xFF, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF,\n                                   (c >> 24) & 0xFF, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF,\n                                   (d >> 24) & 0xFF, (d >> 16) & 0xFF, (d >> 8) & 0xFF, d & 0xFF,\n                                   (e >> 24) & 0xFF, (e >> 16) & 0xFF, (e >> 8) & 0xFF, e & 0xFF);\n    }\n\n    function ft(t, b, c, d) {\n        if (t < 20) return (b & c) | ((~b) & d);\n        if (t < 40) return b ^ c ^ d;\n        if (t < 60) return (b & c) | (b & d) | (c & d);\n        return b ^ c ^ d;\n    }\n    function kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; }\n    function add(x, y) {\n        var l = (x & 0xFFFF) + (y & 0xFFFF), m = (x >> 16) + (y >> 16) + (l >> 16);\n        return (m << 16) | (l & 0xFFFF);\n    }\n    function rol(n, c) { return (n << c) | (n >>> (32 - c)); }\n}\n\nconst binaryKey = hexToBytes(apiKeyHex);\nconst generatedHash = hmacSha1(binaryKey, message);\nconst authHeader = `IAPIS user=${userLogin}, hmac-sha1=${generatedHash}`;\n// console.log(hmacSha1('111111', '222222')===\"1558ab6c5ab2b0d1cd129b9ad11527cf33486705\")\nreturn { authHeader };"
      },
      "typeVersion": 2
    },
    {
      "id": "d0196ab5-bb90-436d-8112-5fd5bb35c3bb",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1072,
        752
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e5c314b8-5915-4d18-855d-c201954d0d4a",
              "name": "Email/Login",
              "type": "string",
              "value": ""
            },
            {
              "id": "138ffe50-4a20-4eee-b7ac-1466ca5d6f54",
              "name": "API Key Invoice",
              "type": "string",
              "value": ""
            },
            {
              "id": "e0e86492-3e24-4848-88bb-3e547c889348",
              "name": "X days before due date",
              "type": "number",
              "value": 7
            },
            {
              "id": "efcbc7dc-5fc9-4a2a-a88f-36d7623876f2",
              "name": "Y days after due date",
              "type": "number",
              "value": 7
            },
            {
              "id": "a2862444-9426-4f37-9baa-62cdf08a6d7a",
              "name": "Z days after due date",
              "type": "number",
              "value": 14
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "85c2afd6-cafb-41b0-8ff3-48a2218f7615",
      "name": "URL to Fetch Invoices",
      "type": "n8n-nodes-base.set",
      "position": [
        -240,
        752
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "400dd6ee-1ded-48d8-8dfc-b9e17bc35e8f",
              "name": "url",
              "type": "string",
              "value": "=https://www.ifirma.pl/iapi/faktury.json"
            },
            {
              "id": "6c174c47-5d70-40b7-8ad9-be9f55233118",
              "name": "requestBody",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f96ad6a2-042e-4535-a0dc-140b7104c4dd",
      "name": "Fetch Invoices from System",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Gets all issued (unpaid) invoices from ifirma.pl",
      "position": [
        80,
        752
      ],
      "parameters": {
        "url": "={{ $('URL to Fetch Invoices').first().json.url }}",
        "options": {
          "pagination": {
            "pagination": {
              "parameters": {
                "parameters": [
                  {
                    "name": "strona",
                    "value": "=1"
                  }
                ]
              }
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "status",
              "value": "przeterminowane,nieoplacone,oplaconeCzesciowo"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authentication",
              "value": "={{ $json.authHeader }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "a4284dbd-0447-48ef-8018-77fa8a923d1b",
      "name": "Filter Unpaid Invoices Due Today or Earlier",
      "type": "n8n-nodes-base.filter",
      "position": [
        656,
        752
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "ffa36a46-67a0-4d40-89f5-ea08125e6114",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              },
              "leftValue": "={{new Date($json.TerminPlatnosci) }}",
              "rightValue": "={{ $now }}"
            },
            {
              "id": "4e0d94d3-878a-4e0e-aa1f-5789a98a479c",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.TerminPlatnosci }}",
              "rightValue": "={{ $now.minus($('Configuration').first().json[\"X days before due date\"], 'days').format('yyyy-MM-dd') }}"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "77da7546-bd92-4c09-aca4-39569c1d075a",
      "name": "Z Days After Due Date",
      "type": "n8n-nodes-base.filter",
      "position": [
        2352,
        912
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "556037c9-d033-4f89-b23d-f7c15c203d97",
              "operator": {
                "type": "dateTime",
                "operation": "equals"
              },
              "leftValue": "={{ new Date($json.TerminPlatnosci).format('yyyy-LL-dd') }}",
              "rightValue": "={{ $now.minus($('Configuration').first().json[\"Z days after due date\"], 'days').format('yyyy-LL-dd') }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ccfdb67a-9cd1-43ee-a16a-c8cfca698115",
      "name": "Y Days After Due Date",
      "type": "n8n-nodes-base.filter",
      "position": [
        2352,
        768
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "556037c9-d033-4f89-b23d-f7c15c203d97",
              "operator": {
                "type": "dateTime",
                "operation": "equals"
              },
              "leftValue": "={{ new Date($json.TerminPlatnosci).format('yyyy-LL-dd') }}",
              "rightValue": "={{ $now.minus($('Configuration').first().json[\"Y days after due date\"], 'days').format('yyyy-LL-dd') }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b9ff5bb2-132c-456e-8d58-617558365696",
      "name": "Assign Endpoint to Invoice Type",
      "type": "n8n-nodes-base.code",
      "position": [
        848,
        752
      ],
      "parameters": {
        "jsCode": "const endpointsMap = {\n    // Standard Domestic & Specialized\n    prz_faktura_kraj: \"fakturakraj\",\n    prz_faktura_wysylka: \"fakturawysylka\",\n    prz_faktura_paragon: \"fakturakraj\",\n    prz_faktura_szczeg_obow: \"fakturakraj\",\n    prz_faktura_met_kasowa: \"fakturakraj\",\n    prz_faktura_odwr_obciaz: \"fakturakraj\",\n    prz_faktura_odwr_obciaz_budowa: \"fakturakraj\",\n    prz_faktura_budowa: \"fakturakraj\",\n    \n    // Foreign Currency / Specialized Domestic\n    prz_faktura_wys_ter_kraj: \"fakturawaluta\",\n\n    // International & EU\n    prz_dostawa_ue_towarow: \"fakturawdt\",\n    prz_eksport_towarow: \"fakturaeksporttowarow\",\n    prz_eksport_dost_uslug_ue: \"fakturaeksportuslugue\",\n    prz_eksport_dost_uslug_nie_ue: \"fakturaeksportuslug\",\n\n    // Proforma (Note: Rodzaj for proforma is often prz_faktura_proforma)\n    prz_faktura_proforma: \"fakturaproformakraj\",\n\n    // Advance & Final (Mapping to the most likely send endpoints)\n    prz_faktura_zal: \"fakturazaliczka\",\n    prz_faktura_kon: \"fakturakoncowa\",\n\n    // Accounts / Non-VAT\n    prz_rachunek_kraj: \"rachunekkraj\",\n    prz_rachunek_zagr: \"rachunekue\"\n};\nfor (const item of $input.all()) {\n  item.json.endpoint = endpointsMap[item.json.Rodzaj];\n}\n\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "c851d834-3d5d-4731-b560-92cc43f4d006",
      "name": "Payment Reminder",
      "type": "n8n-nodes-base.set",
      "position": [
        2688,
        624
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d1103dc3-bb9a-431e-8fa3-c29059c966e6",
              "name": "Reminder Text",
              "type": "string",
              "value": "=This is a reminder about payment for invoice {{ $input.item.json.PelnyNumer }} issued on {{ $input.item.json.DataWystawienia }} for the amount of {{ $input.item.json.Brutto }}"
            },
            {
              "id": "186999e4-c7f5-493c-88cb-20e1fb1e5d6e",
              "name": "Can Pay by Transfer",
              "type": "boolean",
              "value": true
            },
            {
              "id": "e4b7c469-2357-466a-b7d2-4d430ddcba41",
              "name": "Can Pay Cash on Delivery",
              "type": "boolean",
              "value": false
            },
            {
              "id": "0d992cc6-f75a-4f23-b73f-984fff383ea3",
              "name": "Can Pay MTransfer",
              "type": "boolean",
              "value": false
            },
            {
              "id": "c9c45b01-bcb6-465b-95c3-ff196b98d579",
              "name": "Email Address to Send From",
              "type": "string",
              "value": ""
            },
            {
              "id": "3ef65fcd-b002-4424-a7d6-f076e8782570",
              "name": "Email Template",
              "type": "string",
              "value": ""
            },
            {
              "id": "25f97d49-730e-4224-85b5-dbfba2d391ca",
              "name": "Send by Traditional Mail",
              "type": "boolean",
              "value": false
            },
            {
              "id": "dad319d9-1709-4482-b790-8815adca5067",
              "name": "Send E-Invoice",
              "type": "boolean",
              "value": true
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4,
      "alwaysOutputData": false
    },
    {
      "id": "03443c2d-5ce1-4e78-84f5-a6d51c185105",
      "name": "Encode API Key for Sending Reminder",
      "type": "n8n-nodes-base.code",
      "position": [
        3248,
        432
      ],
      "parameters": {
        "jsCode": "const apiKeyHex = $('Configuration').first().json[\"API Key Invoice\"];\nconst url = $input.first().json.url ?? \"\";\nconst userLogin =  $('Configuration').first().json[\"Email/Login\"];\nconst keyName = \"faktura\";\nconst requestBody = $input.first().json.requestBody ?? \"\" \nconst message = url + userLogin + keyName + requestBody;\n\nfunction hexToBytes(hex) {\n    let bytes = \"\";\n    for (let i = 0; i < hex.length; i += 2) {\n        bytes += String.fromCharCode(parseInt(hex.substr(i, 2), 16));\n    }\n    return bytes;\n}\n\nfunction hmacSha1(key, message) {\n    var blocksize = 64;\n    if (key.length > blocksize) {\n        key = sha1_bin(key);\n    }\n    \n    var ipad = [], opad = [];\n    for (var i = 0; i < blocksize; i++) {\n        var k = (i < key.length) ? key.charCodeAt(i) : 0;\n        ipad[i] = k ^ 0x36;\n        opad[i] = k ^ 0x5C;\n    }\n\n    var hash = sha1_bin(String.fromCharCode.apply(null, ipad) + message);\n    return sha1_hex(String.fromCharCode.apply(null, opad) + hash);\n\n    function sha1_hex(s) { return bin2hex(sha1_bin(s)); }\n    \n    function bin2hex(bin) {\n        var hex = \"0123456789abcdef\";\n        var str = \"\";\n        for (var i = 0; i < bin.length; i++) {\n            var c = bin.charCodeAt(i);\n            str += hex.charAt((c >> 4) & 0xF) + hex.charAt(c & 0xF);\n        }\n        return str;\n    }\n\n    function sha1_bin(s) {\n        var nblk = ((s.length + 8) >> 6) + 1, blks = new Array(nblk * 16);\n        for (var i = 0; i < nblk * 16; i++) blks[i] = 0;\n        for (var i = 0; i < s.length; i++) blks[i >> 2] |= s.charCodeAt(i) << (24 - (i % 4) * 8);\n        blks[s.length >> 2] |= 0x80 << (24 - (s.length % 4) * 8);\n        blks[nblk * 16 - 1] = s.length * 8;\n        var w = new Array(80), a = 1732584193, b = -271733879, c = -1732584194, d = 271733878, e = -1+1234567890;\n        for (var i = 0; i < blks.length; i += 16) {\n            var olda = a, oldb = b, oldc = c, oldd = d, olde = e;\n            for (var j = 0; j < 80; j++) {\n                if (j < 16) w[j] = blks[i + j];\n                else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                var t = add(add(rol(a, 5), ft(j, b, c, d)), add(add(e, w[j]), kt(j)));\n                e = d; d = c; c = rol(b, 30); b = a; a = t;\n            }\n            a = add(a, olda); b = add(b, oldb); c = add(c, oldc); d = add(d, oldd); e = add(e, olde);\n        }\n        return String.fromCharCode((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,\n                                   (b >> 24) & 0xFF, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF,\n                                   (c >> 24) & 0xFF, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF,\n                                   (d >> 24) & 0xFF, (d >> 16) & 0xFF, (d >> 8) & 0xFF, d & 0xFF,\n                                   (e >> 24) & 0xFF, (e >> 16) & 0xFF, (e >> 8) & 0xFF, e & 0xFF);\n    }\n\n    function ft(t, b, c, d) {\n        if (t < 20) return (b & c) | ((~b) & d);\n        if (t < 40) return b ^ c ^ d;\n        if (t < 60) return (b & c) | (b & d) | (c & d);\n        return b ^ c ^ d;\n    }\n    function kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; }\n    function add(x, y) {\n        var l = (x & 0xFFFF) + (y & 0xFFFF), m = (x >> 16) + (y >> 16) + (l >> 16);\n        return (m << 16) | (l & 0xFFFF);\n    }\n    function rol(n, c) { return (n << c) | (n >>> (32 - c)); }\n}\n\nconst binaryKey = hexToBytes(apiKeyHex);\nconst generatedHash = hmacSha1(binaryKey, message);\nconst authHeader = `IAPIS user=${userLogin}, hmac-sha1=${generatedHash}`;\nconsole.log(hmacSha1('111111', '222222')===\"1558ab6c5ab2b0d1cd129b9ad11527cf33486705\")\nreturn $input.all().map((i)=>{\n  return ({...i.json, authHeader})\n})\nreturn { authHeader };"
      },
      "typeVersion": 2
    },
    {
      "id": "6753a503-ed6f-42dc-bcad-237b2843d268",
      "name": "Send Reminder",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3440,
        432
      ],
      "parameters": {
        "url": "={{ $(\"URL for Sending Reminder\").first().json.url }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendQuery": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "Tekst",
              "value": "={{ $json['Reminder Text'] }}"
            },
            {
              "name": "Przelew",
              "value": "={{ $json['Can Pay by Transfer'] }}"
            },
            {
              "name": "Pobranie",
              "value": "={{ $json['Can Pay Cash on Delivery'] }}"
            },
            {
              "name": "MTransfer",
              "value": "={{ $json['Can Pay MTransfer'] }}"
            },
            {
              "name": "SkrzynkaEmail ",
              "value": "={{ $json['Email Address to Send From'] }}"
            },
            {
              "name": "SzablonEmail ",
              "value": "={{ $json['Email Template'] }}"
            },
            {
              "name": "SkrzynkaEmailOdbiorcy ",
              "value": "={{ $json['EmailDlaFaktury'] }}"
            }
          ]
        },
        "queryParameters": {
          "parameters": [
            {
              "name": "wyslijEfaktura",
              "value": "={{ $json['Send E-Invoice'] }}"
            },
            {
              "name": "wyslijPoczta",
              "value": "={{ $json['Send by Traditional Mail'] }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authentication",
              "value": "={{$json.authHeader}}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "e1d6f5f6-998e-41c4-b13b-9542086b8318",
      "name": "URL for Sending Reminder",
      "type": "n8n-nodes-base.set",
      "position": [
        3056,
        336
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "400dd6ee-1ded-48d8-8dfc-b9e17bc35e8f",
              "name": "url",
              "type": "string",
              "value": "=https://www.ifirma.pl/iapi/{{$input.item.json.endpoint}}/send/{{$input.item.json.FakturaId}}.json"
            },
            {
              "id": "6c174c47-5d70-40b7-8ad9-be9f55233118",
              "name": "requestBody",
              "type": "object",
              "value": "{}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "ea1944d1-9c42-44e1-b63f-828912fdf3ff",
      "name": "Error Fetching Invoices",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        432,
        608
      ],
      "parameters": {
        "errorType": "errorObject",
        "errorObject": "={\n  \"message\": \"Error fetching invoices\",\n  \"body\": {{ JSON.stringify($(\"Fetch Invoices from System\").last().json.response) }}\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "f1a99040-3958-4763-889f-8e93747ed08c",
      "name": "Encode API Key to Fetch Contractor Information",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        848
      ],
      "parameters": {
        "jsCode": "const apiKeyHex = $('Configuration').first().json[\"API Key Invoice\"];\nconst url = $input.first().json.url ?? \"\";\nconst userLogin =  $('Configuration').first().json[\"Email/Login\"];\nconst keyName = \"faktura\";\nconst requestBody = $input.first().json.requestBody ?? \"\" \nconst message = url + userLogin + keyName + requestBody;\n\nfunction hexToBytes(hex) {\n    let bytes = \"\";\n    for (let i = 0; i < hex.length; i += 2) {\n        bytes += String.fromCharCode(parseInt(hex.substr(i, 2), 16));\n    }\n    return bytes;\n}\n\nfunction hmacSha1(key, message) {\n    var blocksize = 64;\n    if (key.length > blocksize) {\n        key = sha1_bin(key);\n    }\n    \n    var ipad = [], opad = [];\n    for (var i = 0; i < blocksize; i++) {\n        var k = (i < key.length) ? key.charCodeAt(i) : 0;\n        ipad[i] = k ^ 0x36;\n        opad[i] = k ^ 0x5C;\n    }\n\n    var hash = sha1_bin(String.fromCharCode.apply(null, ipad) + message);\n    return sha1_hex(String.fromCharCode.apply(null, opad) + hash);\n\n    function sha1_hex(s) { return bin2hex(sha1_bin(s)); }\n    \n    function bin2hex(bin) {\n        var hex = \"0123456789abcdef\";\n        var str = \"\";\n        for (var i = 0; i < bin.length; i++) {\n            var c = bin.charCodeAt(i);\n            str += hex.charAt((c >> 4) & 0xF) + hex.charAt(c & 0xF);\n        }\n        return str;\n    }\n\n    function sha1_bin(s) {\n        var nblk = ((s.length + 8) >> 6) + 1, blks = new Array(nblk * 16);\n        for (var i = 0; i < nblk * 16; i++) blks[i] = 0;\n        for (var i = 0; i < s.length; i++) blks[i >> 2] |= s.charCodeAt(i) << (24 - (i % 4) * 8);\n        blks[s.length >> 2] |= 0x80 << (24 - (s.length % 4) * 8);\n        blks[nblk * 16 - 1] = s.length * 8;\n        var w = new Array(80), a = 1732584193, b = -271733879, c = -1732584194, d = 271733878, e = -1+1234567890;\n        for (var i = 0; i < blks.length; i += 16) {\n            var olda = a, oldb = b, oldc = c, oldd = d, olde = e;\n            for (var j = 0; j < 80; j++) {\n                if (j < 16) w[j] = blks[i + j];\n                else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                var t = add(add(rol(a, 5), ft(j, b, c, d)), add(add(e, w[j]), kt(j)));\n                e = d; d = c; c = rol(b, 30); b = a; a = t;\n            }\n            a = add(a, olda); b = add(b, oldb); c = add(c, oldc); d = add(d, oldd); e = add(e, olde);\n        }\n        return String.fromCharCode((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,\n                                   (b >> 24) & 0xFF, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF,\n                                   (c >> 24) & 0xFF, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF,\n                                   (d >> 24) & 0xFF, (d >> 16) & 0xFF, (d >> 8) & 0xFF, d & 0xFF,\n                                   (e >> 24) & 0xFF, (e >> 16) & 0xFF, (e >> 8) & 0xFF, e & 0xFF);\n    }\n\n    function ft(t, b, c, d) {\n        if (t < 20) return (b & c) | ((~b) & d);\n        if (t < 40) return b ^ c ^ d;\n        if (t < 60) return (b & c) | (b & d) | (c & d);\n        return b ^ c ^ d;\n    }\n    function kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; }\n    function add(x, y) {\n        var l = (x & 0xFFFF) + (y & 0xFFFF), m = (x >> 16) + (y >> 16) + (l >> 16);\n        return (m << 16) | (l & 0xFFFF);\n    }\n    function rol(n, c) { return (n << c) | (n >>> (32 - c)); }\n}\n\nconst binaryKey = hexToBytes(apiKeyHex);\nconst generatedHash = hmacSha1(binaryKey, message);\nconst authHeader = `IAPIS user=${userLogin}, hmac-sha1=${generatedHash}`;\nconsole.log(hmacSha1('111111', '222222')===\"1558ab6c5ab2b0d1cd129b9ad11527cf33486705\")\nreturn { authHeader };"
      },
      "typeVersion": 2
    },
    {
      "id": "0736637b-ce5b-475c-85fb-5b49bc67de84",
      "name": "URL to Fetch Contractor Information",
      "type": "n8n-nodes-base.set",
      "position": [
        1200,
        848
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "400dd6ee-1ded-48d8-8dfc-b9e17bc35e8f",
              "name": "url",
              "type": "string",
              "value": "=https://www.ifirma.pl/iapi/kontrahenci/id/{{$json.IdentyfikatorKontrahenta}}.json"
            },
            {
              "id": "6c174c47-5d70-40b7-8ad9-be9f55233118",
              "name": "requestBody",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "70190ca0-612d-4f65-b448-a4e559961525",
      "name": "Fetch Contractor Information",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1536,
        848
      ],
      "parameters": {
        "url": "={{ $(\"URL to Fetch Contractor Information\").item.json }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authentication",
              "value": "={{ $json.authHeader }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "cd406348-87ab-4daa-8262-c2dd68cdd8aa",
      "name": "Error Fetching Contractor Data",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        1888,
        656
      ],
      "parameters": {
        "errorType": "errorObject",
        "errorObject": "={\n  \"message\": \"Error fetching contractor data\",\n  \"body\": {{ JSON.stringify($(\"Fetch Invoices from System\").last().json.response) }}\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "e3d10c56-c354-43db-8801-c7faef9a2ac2",
      "name": "Contains Error?",
      "type": "n8n-nodes-base.if",
      "position": [
        256,
        752
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3413047a-8e5d-4d8b-8370-a5c57ecd4468",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.response.Kod }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b62dcb1a-b06e-4890-927b-3f3da7f60ff4",
      "name": "Contains Error? 2",
      "type": "n8n-nodes-base.if",
      "position": [
        1712,
        848
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3413047a-8e5d-4d8b-8370-a5c57ecd4468",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.response.Kod }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "49e7ef49-da66-49f1-af06-34718c2aac40",
      "name": "Extract Contractor Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1888,
        848
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{ JSON.stringify($input.item.json.response.Wynik[0], null, 2) }}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "b851e357-b230-4603-85b0-bbd1b851c7f8",
      "name": "Deduplicate Contractors",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        1040,
        848
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {},
        "fieldsToCompare": "IdentyfikatorKontrahenta"
      },
      "typeVersion": 2
    },
    {
      "id": "0293ccfd-73d1-4d7d-bb0e-82a75f03c3db",
      "name": "Invoices for Reminder",
      "type": "n8n-nodes-base.filter",
      "position": [
        2352,
        624
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "556037c9-d033-4f89-b23d-f7c15c203d97",
              "operator": {
                "type": "dateTime",
                "operation": "equals"
              },
              "leftValue": "={{ $json.TerminPlatnosci }}",
              "rightValue": "={{ $now.format('yyyy-LL-dd') }}"
            },
            {
              "id": "92dd91d3-a39e-460a-adf8-61a3ecdc41a4",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.TerminPlatnosci }}",
              "rightValue": "={{ $now.minus($('Configuration').first().json[\"X days before due date\"], 'days').format('yyyy-LL-dd') }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "09a28451-a524-469d-902d-b80e24ff79f6",
      "name": "Extract Invoices",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        432,
        752
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "response.Wynik"
      },
      "typeVersion": 1
    },
    {
      "id": "846b1115-0094-4f7a-aca5-0549ccaafd65",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2112,
        192
      ],
      "parameters": {
        "width": 672,
        "height": 976,
        "content": "## iFirma Overdue Invoice Vindication\n\n**This workflow automates payment recovery for overdue invoices issued in** [iFirma](https://ifirma.pl)**. It fetches unpaid invoices daily, enriches them with full contractor data, and sends the appropriate notification \u2014 payment reminder, pre-trial summons, or court summons \u2014 via Gmail and/or physical mail through** [PostGrid](https://postgrid.com)**. Every action is reported to a Slack channel.**\n\n### How it works\n\nEach day the workflow fetches all unpaid invoices from iFirma using HMAC-SHA1 authenticated API calls, then enriches each invoice with full contractor details. Invoices are split into three escalation tiers based on configurable day thresholds:\n\n- **Reminder** \u2014 due today or X days before due date\n- **Pre-trial summons** \u2014 Y days after due date\n- **Court summons** \u2014 Z days after due date\n\nEach tier can independently send email, a physical letter via PostGrid, or both.\n\n### Setup\n1. Fill out the **Configuration** node with your iFirma login, API key (found under *Data and Configuration \u2192 Extensions and Integrations \u2192 API*), and the X/Y/Z day thresholds\n2. Fill out **Your Company Details** \u2014 used in letter and email templates and as the PostGrid sender address\n3. Configure PostGrid auth in both PostGrid nodes using Custom Auth:\n```json\n{ \"headers\": { \"x-api-key\": \"\" } }\n```\n\n### How to customize\nThe **Payment Reminder**, **Pre-trial summon Reminder**, and **Summons to court Reminder** nodes each have `Send by Traditional Mail` and `Send by Email` toggles \u2014 flip these to control delivery channel per escalation tier. HTML templates can be freely edited in the corresponding **Prepare Mail and Letter HTML Contents** nodes.\n\n\n\n\n\n\n\n\n\n\nNeed help? Contact us at [developers@sailingbyte.com](mailto:developers@sailingbyte.com) or visit [sailingbyte.com](https://sailingbyte.com)!\n\nHappy hacking!"
      },
      "typeVersion": 1
    },
    {
      "id": "66183aef-61c0-4bbe-a606-30df3e1e6759",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1424,
        432
      ],
      "parameters": {
        "color": 3,
        "width": 560,
        "height": 736,
        "content": "## Fill out config\n1. Email/Login - Email or login used to log into the iFirma service\n2. API Key Invoice - API key that can be found at: \nStart > \nData and Configuration > \nExtensions and Integrations > \nAPI\n3. X days before due date - Number of days before payment deadline for reminder - How many days before payment deadline to send reminder (default 7)\n4. Y days after due date - Number of days after payment deadline to send notification about intent to pursue legal action\n5. Z days after due date - Number of days after payment deadline to send notification about pursuing legal action"
      },
      "typeVersion": 1
    },
    {
      "id": "1430302c-976e-4f8f-91f5-2f40eea877f3",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 736,
        "content": "## Step 1: Fetching Invoice List\nFetching the invoice list from the service requires creating an encryption key that is not available to generate using standard N8N tools because it uses SHA-1 algorithm. Because iFirma returns error in response body, we first check if response contains an error, if not we extract items from the body to loop over them."
      },
      "typeVersion": 1
    },
    {
      "id": "d77d322a-b8a5-4b86-818b-de59b80f3477",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        608,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 736,
        "content": "## Step 2: Filtering paid invoices, map appropriate endpoints\nInvoices are filtered based on the `TerminPlatnosci` (Payment Deadline) value. Based on `Rodzaj` value from the response we map appropriate endpoint to our object."
      },
      "typeVersion": 1
    },
    {
      "id": "ecccd537-3efd-4322-8ce7-6fc5e7fd4861",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2576,
        432
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 736,
        "content": "## Configuration\nThese are values that are going to be added to the requests sent to iFirma\n\nValues you can fill in can be found [here](https://api.ifirma.pl/wysylanie-faktur-poczta-tradycyjna-oraz-na-adres-e-mail-kontrahenta/)"
      },
      "typeVersion": 1
    },
    {
      "id": "94810f38-69c1-4718-97ce-ab8874d0f6f2",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 1200,
        "height": 736,
        "content": "## Step 3: Fetch Contractor Data and Merge with Invoice Data\nBecause iFirma sends only partial data about contractor we need to make additional fetches so we have complete object with all necessary data. For these we use `IdentyfikatorKontrahenta`"
      },
      "typeVersion": 1
    },
    {
      "id": "a2383336-11ef-4049-afc9-3471e66bfeae",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2224,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 736,
        "content": "## Step 4: Logic Separation\nAt this point, the logic separates invoices into:\n1. Payment reminder\n2. Notification about intent to pursue legal action\n3. Notification about pursuing legal action"
      },
      "typeVersion": 1
    },
    {
      "id": "14d852be-0d05-4a88-a380-c8efb030eabc",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3024,
        144
      ],
      "parameters": {
        "color": 2,
        "width": 592,
        "height": 464,
        "content": "## Notify Contractor About Approaching Payment Deadline"
      },
      "typeVersion": 1
    },
    {
      "id": "0689d199-a955-4560-8762-7e73b36e301b",
      "name": "Schedule Daily Check",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Runs every day at 9:00 AM",
      "position": [
        -1328,
        752
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a1a49e9a-792e-4b83-ab29-269472412e0f",
      "name": "Your Company Details",
      "type": "n8n-nodes-base.set",
      "position": [
        -624,
        752
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "61884a08-afef-4e5d-9e73-860e5bebe76a",
              "name": "Company Name",
              "type": "string",
              "value": ""
            },
            {
              "id": "03b6e8b6-a48a-4e06-939d-6fd32f0c3234",
              "name": "Email",
              "type": "string",
              "value": ""
            },
            {
              "id": "e5c56d54-f4cb-4856-9cc8-1e99483b1dbe",
              "name": "Phone",
              "type": "string",
              "value": ""
            },
            {
              "id": "e383c8f2-1c46-406e-a079-82228e65859b",
              "name": "Country Code",
              "type": "string",
              "value": ""
            },
            {
              "id": "c7d26305-a13e-43a6-a9c3-025b6f5929fa",
              "name": "City",
              "type": "string",
              "value": ""
            },
            {
              "id": "7c59db00-640e-411f-9ff0-a0aa05e97488",
              "name": "Street",
              "type": "string",
              "value": ""
            },
            {
              "id": "c2bd2535-112b-4766-a4fb-6117fc44d56a",
              "name": "Postal Code",
              "type": "string",
              "value": ""
            },
            {
              "id": "1ef4679e-421a-428a-94d8-93efa37dd951",
              "name": "Swift Code",
              "type": "string",
              "value": ""
            },
            {
              "id": "1c48a217-2fe0-40ff-bdc6-2d31da4ea887",
              "name": "Bank Name",
              "type": "string",
              "value": ""
            },
            {
              "id": "6d70aba1-efe4-4818-be1d-f130538ba642",
              "name": "Bank Account Number",
              "type": "string",
              "value": ""
            },
            {
              "id": "da47e651-5c03-410b-9600-3846411ebafd",
              "name": "TIN (Tax Identification Number)",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "483d1bc0-42f9-4abc-86ff-199591dc1fdf",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -848,
        432
      ],
      "parameters": {
        "color": 3,
        "width": 544,
        "height": 736,
        "content": "## Your company details (Needed for PostGrid)\nThis data is needed for PostGrid API to work. It will be used to send letters in your name for Pre-trial summons and Summons to court when invoice is not paid"
      },
      "typeVersion": 1
    },
    {
      "id": "fc3a4b45-97c8-4fad-93bb-16fa014c08f1",
      "name": "Send pre-trial summon letter via PostGrid",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3440,
        688
      ],
      "parameters": {
        "url": "https://api.postgrid.com/print-mail/v1/letters",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"to\": {\n    \"companyName\": \"{{ $json['KontrahentNazwa'] }}\",\n    \"addressLine1\": \"{{$json['Ulica']}}\",\n    \"city\": \"{{$json['Miejscowosc']}}\",\n    \"provinceOrState\": \"\",\n    \"postalOrZip\": \"{{$json['KodPocztowy']}}\",\n    \"countryCode\": \"{{$json['PrefiksUE']}}\"\n  },\n  \"from\": {\n    \"companyName\": \"{{$('Your Company Details').first().json['Company Name']}}\",\n    \"addressLine1\": \"{{$('Your Company Details').first().json['Street']}}\",\n    \"city\": \"{{$('Your Company Details').first().json['City']}}\",\n    \"postalOrZip\": \"{{$('Your Company Details').first().json['Postal Code']}}\",\n    \"countryCode\": \"{{$('Your Company Details').first().json['Country Code']}}\"\n  },\n  \"html\": {{JSON.stringify($json['Message Content'])}},\n  \"description\": \"Overdue Invoice Reminder - {{$json['FakturaId']}}\",\n  \"color\": false,\n  \"doubleSided\": false,\n  \"addressPlacement\": \"top_first_page\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpCustomAuth"
      },
      "credentials": {
        "httpCustomAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "461a7137-e6a1-46e7-b835-e2eb56e804fc",
      "name": "Payment Reminder Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        3248,
        240
      ],
      "parameters": {
        "select": "channel",
        "blocksUi": "={\n\t\"blocks\": [\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \"Payment reminder has been sent:\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"fields\": [\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"*Invoice No:*\\n{{$json.PelnyNumer}}\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"*Overdue:*\\n{{$now.diffTo($json.TerminPlatnosci.toDateTime(), 'days').floor()}} days\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"*Contractor:*\\n{{$json.KontrahentNazwa}}\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"*Payment Deadline:*\\n{{$json.TerminPlatnosci}}\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"*Amount:*\\n{{$json.Brutto}} {{$json.Waluta}}\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"*Issue Date:*\\n{{$json.DataWystawienia}}\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"type\": \"divider\"\n\t\t},\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \"*<fakeLink.toEmployeeProfile.com|View>*\"\n\t\t\t}\n\t\t}\n\t]\n}",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "channel-to-send-invoice-reminders",
          "cachedResultName": "channel-to-send-invoice-reminders"
        },
        "messageType": "block",
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "80a48c4c-5f8a-415a-8586-2f172f726ca5",
      "name": "Prepare Mail and Letter HTML Contents for Pre-trial summon",
      "type": "n8n-nodes-base.set",
      "position": [
        3056,
        848
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "393b8e7b-fc27-4d99-bd11-152e16609e25",
              "name": "Message Content",
              "type": "string",
              "value": "=<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <style>\n    body {\n      font-family: Arial, Helvetica, sans-serif;\n      line-height: 1.6;\n      color: #333333;\n      margin: 0;\n      padding: 0;\n      background-color: #ffffff;\n    }\n    .container {\n      max-width: 650px;\n      margin: 0 auto;\n      padding: 40px 30px;\n    }\n    .header {\n      border-bottom: 3px solid #c62828;\n      padding-bottom: 20px;\n      margin-bottom: 30px;\n    }\n    .company-name {\n      font-size: 24px;\n      font-weight: bold;\n      color: #c62828;\n      margin: 0 0 5px 0;\n    }\n    .company-tagline {\n      font-size: 14px;\n      color: #666666;\n      margin: 0;\n    }\n    h1 {\n      color: #c62828;\n      font-size: 22px;\n      margin: 0 0 20px 0;\n      text-transform: uppercase;\n    }\n    .urgent-notice {\n      background-color: #ffebee;\n      border-left: 4px solid #c62828;\n      padding: 15px 20px;\n      margin: 25px 0;\n    }\n    .urgent-notice p {\n      margin: 5px 0;\n      font-size: 15px;\n      font-weight: bold;\n      color: #c62828;\n    }\n    .invoice-details {\n      background-color: #f5f5f5;\n      padding: 20px;\n      border-radius: 5px;\n      margin: 25px 0;\n    }\n    .invoice-details table {\n      width: 100%;\n      border-collapse: collapse;\n    }\n    .invoice-details td {\n      padding: 8px 0;\n      font-size: 15px;\n    }\n    .invoice-details td:first-child {\n      color: #666666;\n      width: 45%;\n    }\n    .invoice-details td:last-child {\n      font-weight: bold;\n      text-align: right;\n    }\n    .amount-due {\n      font-size: 20px;\n      color: #c62828;\n    }\n    .overdue-status {\n      color: #c62828;\n      font-weight: bold;\n    }\n    .legal-warning {\n      background-color: #fff3cd;\n      border: 2px solid #ff6f00;\n      padding: 20px;\n      margin: 25px 0;\n      border-radius: 5px;\n    }\n    .legal-warning h2 {\n      color: #c62828;\n      font-size: 18px;\n      margin: 0 0 15px 0;\n      text-transform: uppercase;\n    }\n    .legal-warning p {\n      margin: 10px 0;\n      font-size: 14px;\n      line-height: 1.8;\n    }\n    .legal-warning ul {\n      margin: 15px 0;\n      padding-left: 20px;\n    }\n    .legal-warning li {\n      margin: 8px 0;\n    }\n    .payment-section {\n      background-color: #f5f5f5;\n      border-left: 4px solid #666666;\n      padding: 20px;\n      margin: 25px 0;\n    }\n    .payment-section h2 {\n      color: #333333;\n      font-size: 18px;\n      margin: 0 0 15px 0;\n    }\n    .payment-details {\n      font-size: 14px;\n      line-height: 1.8;\n    }\n    .payment-details strong {\n      display: inline-block;\n      width: 140px;\n      color: #666666;\n    }\n    .deadline-box {\n      background-color: #c62828;\n      color: #ffffff;\n      padding: 20px;\n      text-align: center;\n      border-radius: 5px;\n      margin: 25px 0;\n    }\n    .deadline-box h2 {\n      margin: 0 0 10px 0;\n      font-size: 20px;\n    }\n    .deadline-box p {\n      margin: 5px 0;\n      font-size: 16px;\n    }\n    .deadline-date {\n      font-size: 24px;\n      font-weight: bold;\n      margin: 10px 0;\n    }\n    .footer {\n      margin-top: 40px;\n      padding-top: 20px;\n      border-top: 1px solid #e0e0e0;\n      font-size: 13px;\n      color: #666666;\n      text-align: center;\n    }\n    .formal-signature {\n      margin-top: 30px;\n      font-size: 14px;\n    }\n    @media print {\n      body { background-color: #ffffff; }\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <!-- Header -->\n    <div class=\"header\">\n      <p class=\"company-name\">{{$('Your Company Details').first().json['Company Name']}}</p>\n      <p class=\"company-tagline\">Software Development & Automation Solutions</p>\n    </div>\n\n    <!-- Main Content -->\n    <h1>Pre-Trial Summons - Notice of Intent to Pursue Legal Action</h1>\n    \n    <p>Dear {{ $json.KontrahentNazwa }},</p>\n    \n    <p><strong>RE: OVERDUE INVOICE - FORMAL NOTICE OF INTENT TO COMMENCE LEGAL PROCEEDINGS</strong></p>\n\n    <!-- Urgent Notice Box -->\n    <div class=\"urgent-notice\">\n      <p>URGENT: Invoice #{{ $json.FakturaId }} is now {{$now.diffTo($json['TerminPlatnosci'].toDateTime(), 'days').abs().floor()}} days overdue</p>\n      <p>Immediate action required to avoid legal proceedings</p>\n    </div>\n\n    <p>This is a formal notice regarding your outstanding payment obligation. Despite our previous communications, payment for the following invoice remains unpaid:</p>\n\n    <!-- Invoice Details -->\n    <div class=\"invoice-details\">\n      <table>\n        <tr>\n          <td>Invoice Number:</td>\n          <td>{{$json['PelnyNumer']}}</td>\n        </tr>\n        <tr>\n          <td>Issue Date:</td>\n          <td>{{$json['DataWystawienia']}}</td>\n        </tr>\n        <tr>\n          <td>Original Due Date:</td>\n          <td>{{$json['TerminPlatnosci']}}</td>\n        </tr>\n        <tr>\n          <td>Days Overdue:</td>\n          <td class=\"overdue-status\">{{$now.diffTo($json['TerminPlatnosci'].toDateTime(), 'days').abs().floor()}} days</td>\n        </tr>\n        <tr style=\"border-top: 2px solid #dddddd;\">\n          <td style=\"padding-top: 15px;\">Outstanding Amount:</td>\n          <td class=\"amount-due\" style=\"padding-top: 15px;\">{{$json.Brutto}} {{ $json.Waluta }}</td>\n        </tr>\n        <tr>\n          <td>Late Payment Interest:</td>\n          <td class=\"overdue-status\">+ Interest accruing daily</td>\n        </tr>\n      </table>\n    </div>\n\n    <!-- Legal Warning -->\n    <div class=\"legal-warning\">\n      <h2>Legal Consequences Notice</h2>\n      <p><strong>We hereby formally notify you of our intent to pursue legal action if this matter is not resolved immedia

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

14310 Send Overdue Invoice Payment Reminders With Ifirma Gmail Postgrid And Slack. Uses httpRequest, stopAndError, slack, gmail. Scheduled trigger; 53 nodes.

Source: https://github.com/japer-technology/github-n8n-intelligence/blob/8da72f77bd009caa7f08cce26b0a8b5b05ff3c5e/workflows/templates/14310_Send_overdue_invoice_payment_reminders_with_iFirma_Gmail_PostGrid_and_Slack.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

Instead of providing a routine check, it focuses on significant movements by: Sending a Slack alert only if a query crosses a defined movement threshold. Emailing a structured report with the Top 25 i

HTTP Request, Slack, Gmail
Email & Gmail

This workflow runs weekly to fetch customers from the Shopify Admin GraphQL API, calculate churn risk based on each customer’s median reorder interval, and then sync at-risk customers to Klaviyo while

Slack, HTTP Request, Gmail +2
Email & Gmail

This workflow automates the complete end-to-end processing of daily revenue transactions for finance and accounting teams. It systematically retrieves, validates, and standardizes transaction data fro

HTTP Request, Gmail, Google Drive +2
Email & Gmail

Security teams often struggle to keep up with the volume of newly published CVEs and manually determine which vulnerabilities are actually relevant to their environment.

Gmail, Slack, HTTP Request
Email & Gmail

E-commerce store owners, product managers, marketplace sellers, and pricing analysts who want to automatically track competitor pricing and get actionable alerts when their products are overpriced or

Google Sheets, HTTP Request, Slack +1