{
  "name": "Ratifia: approve a customer reply (HTTP only)",
  "nodes": [
    {
      "parameters": {
        "content": "## Approve an AI-drafted reply, with HTTP Request nodes\n\nNo community node needed. **Create decision** asks for a verdict under a policy, bound to a hash of the exact email, and the workflow polls **Check decision** until it is no longer `PENDING`.\n\nBefore sending, it hashes the email it is **about to send**, then **Verify** and **Consume**. An edited email is refused; a retry is recognised and not sent twice.\n\nCredential: **Header Auth**, name `Authorization`, value `Bearer <your API key>`. No public URL needed.",
        "height": 260,
        "width": 620
      },
      "id": "60fb470c-6306-480c-a16c-8be749fdcfb9",
      "name": "Note 40ad7c",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {},
      "id": "4b92f330-ee8e-4dc5-a03d-b80fb5a22af0",
      "name": "Run it",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "794615da-16ee-48e0-ab52-742d23f3a91d",
              "name": "recipient",
              "value": "dana@acme.com",
              "type": "string"
            },
            {
              "id": "30aabcf6-37cf-456f-ba3f-5c2d89be74f0",
              "name": "subject",
              "value": "Your refund for invoice INV-2291",
              "type": "string"
            },
            {
              "id": "d88fe746-04d8-40e2-9b13-1f72c814d1ad",
              "name": "body",
              "value": "Hi Dana,\n\nWe've refunded the duplicate charge of $620.00 to the card ending 4242. It should appear within 5 business days.\n\nThanks for your patience,\nAcme Support",
              "type": "string"
            },
            {
              "id": "b3d14f0d-2c90-4ff4-b27e-1936493506da",
              "name": "attachments",
              "value": "={{ [\"refund-receipt-INV-2291.pdf\"] }}",
              "type": "array"
            },
            {
              "id": "a9ce8229-90f0-4ec0-ab24-5766915b9e99",
              "name": "amount",
              "value": 620,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "4a7accb8-c3a1-48a8-8057-db0906ce9276",
      "name": "Draft reply",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "type": "SHA256",
        "value": "={{ JSON.stringify({ attachments: $json.attachments, body: $json.body, recipient: $json.recipient, subject: $json.subject }) }}",
        "dataPropertyName": "factsHash"
      },
      "id": "34da92c1-5eab-47c0-8603-3b8f92dc6028",
      "name": "Hash the draft",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.ratifia.com/v1/worker/decisions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  engine: \"api\",\n  externalRunId: \"n8n-\" + $execution.id,\n  stepRef: \"send-customer-reply\",\n  workflowRef: \"customer-replies\",\n  policy: { key: \"customer-replies\" },\n  required: true,\n  factsHash: $json.factsHash,\n  context: { title: \"Reply to \" + $json.recipient, prompt: \"Send this reply to \" + $json.recipient + \"?\", blocks: [{ kind: \"fields\", title: \"Email\", fields: [{ label: \"To\", value: $json.recipient }, { label: \"Subject\", value: $json.subject }, { label: \"Attachments\", value: $json.attachments.join(\", \") }] }, { kind: \"text\", title: \"Body\", text: $json.body }] }\n}) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "69cd358c-ac49-49c2-b15c-4981f1085e58",
      "name": "Create decision",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "amount": 15
      },
      "id": "8846aae0-5e1d-48e5-ac76-4957d8fd4fc7",
      "name": "Wait 15s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        880,
        300
      ],
      "webhookId": "969005fa-51db-45f0-a0ee-c2a10ea4c684"
    },
    {
      "parameters": {
        "url": "=https://api.ratifia.com/v1/worker/decisions/{{ $(\"Create decision\").item.json.decisionId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 30000
        }
      },
      "id": "006633f5-3edb-4c38-8475-a220bec927ce",
      "name": "Check decision",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "bf7b88c1-b5b7-4696-9865-2063827f34ed",
              "leftValue": "={{ $json.status }}",
              "rightValue": "PENDING",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cc046f34-2e88-4231-ad7f-dad7d587697e",
      "name": "Still pending?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1320,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "54780427-27c7-47b3-8432-cdeccdd83520",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "APPROVED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Approved"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "d762f2e7-4411-469a-8e17-207cd09c17f5",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "REJECTED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Rejected"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "fccd4cca-77ef-4325-b9d6-a1f55dd9ad8e",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "EXPIRED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Expired"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Cancelled"
        }
      },
      "id": "c1b631d5-ac3b-48d8-9907-f9d5b9cf9484",
      "name": "Verdict",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1540,
        420
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e7858a54-ec0d-4071-af4f-ed05489ef32b",
              "name": "recipient",
              "value": "={{ $(\"Draft reply\").item.json.recipient }}",
              "type": "string"
            },
            {
              "id": "3dbff6fa-b81b-4b13-95df-65ff43382678",
              "name": "subject",
              "value": "={{ $(\"Draft reply\").item.json.subject }}",
              "type": "string"
            },
            {
              "id": "d27522e7-a354-485c-91ae-3db6f3356ffa",
              "name": "body",
              "value": "={{ $(\"Draft reply\").item.json.body }}",
              "type": "string"
            },
            {
              "id": "9123d99f-1aff-4ca0-83a2-1b67134f66e1",
              "name": "attachments",
              "value": "={{ $(\"Draft reply\").item.json.attachments }}",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "id": "6985509d-34f2-451d-809e-082bb37c0938",
      "name": "The email to send",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1760,
        140
      ]
    },
    {
      "parameters": {
        "type": "SHA256",
        "value": "={{ JSON.stringify({ attachments: $(\"The email to send\").item.json.attachments, body: $(\"The email to send\").item.json.body, recipient: $(\"The email to send\").item.json.recipient, subject: $(\"The email to send\").item.json.subject }) }}",
        "dataPropertyName": "factsHash"
      },
      "id": "f4a2b329-815a-4c41-a0f8-98ee76afc23f",
      "name": "Hash the email to send",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        1980,
        140
      ]
    },
    {
      "parameters": {
        "url": "=https://api.ratifia.com/v1/worker/decisions/{{ $(\"Create decision\").item.json.decisionId }}/verify",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "factsHash",
              "value": "={{ $json.factsHash }}"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "e649c404-62b4-4860-a880-7c5772269ec4",
      "name": "Verify approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        140
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "20152ada-2f46-4e0b-8f88-ef3115f8a425",
              "leftValue": "={{ $json.valid }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "808753fd-e21f-42d5-b356-129adfe53944",
              "leftValue": "={{ $json.reason }}",
              "rightValue": "consumed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "9acc7bb8-37de-48ae-be67-a061a6bfbe8d",
      "name": "Covers this email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2420,
        140
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.ratifia.com/v1/worker/decisions/{{ $(\"Create decision\").item.json.decisionId }}/consume",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ factsHash: $(\"Hash the email to send\").item.json.factsHash, consumer: \"send-email:\" + $(\"Hash the email to send\").item.json.factsHash }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "e3bf1f3b-f406-4a25-b7f8-8c0edb9faca5",
      "name": "Consume approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        40
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "06bbaacb-acec-41ed-ac3a-92c4933a6204",
                    "leftValue": "={{ $json.alreadyConsumed }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Already sent"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "1751847d-5087-4ae9-b6d2-df68064c54ad",
                    "leftValue": "={{ $json.consumed }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Send"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Refused"
        }
      },
      "id": "29a0e378-8b65-4f67-a4c7-6be9ac99eee9",
      "name": "Send it?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2860,
        40
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "54decbe9-b4d1-457c-ae23-53981e813c83",
              "name": "sent",
              "value": "={{ true }}",
              "type": "boolean"
            },
            {
              "id": "346c4d78-aabb-41ab-9fcf-3fd24545246a",
              "name": "to",
              "value": "={{ $(\"The email to send\").item.json.recipient }}",
              "type": "string"
            },
            {
              "id": "45849260-1cf3-4732-bdd3-925687518e08",
              "name": "subject",
              "value": "={{ $(\"The email to send\").item.json.subject }}",
              "type": "string"
            },
            {
              "id": "87aef4b3-6550-4883-aac1-b4a61f9c1546",
              "name": "approvedUnder",
              "value": "={{ $json.decision.policyVersion ? \"policy v\" + $json.decision.policyVersion : \"no policy\" }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "f6c159c1-9b9a-4670-94f0-02c288dd5d19",
      "name": "Send customer email (stand-in)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3100,
        -120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "15600c22-fa1e-40b9-a1a5-2df72499b522",
              "name": "sent",
              "value": "={{ false }}",
              "type": "boolean"
            },
            {
              "id": "185bd975-be85-41bd-9804-9b135d5b928f",
              "name": "why",
              "value": "a retry of an email that was already sent",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "90c108e8-8561-4def-9271-8e2c9b9a2178",
      "name": "Already sent, not sending again",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3100,
        40
      ]
    },
    {
      "parameters": {
        "errorMessage": "=Not sent: {{ $json.reason }}"
      },
      "id": "2c76fa8b-397a-497d-988d-b686d7df9efd",
      "name": "Refuse: approval already spent",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        3100,
        200
      ]
    },
    {
      "parameters": {
        "errorMessage": "=Not sent: {{ $json.reason }}"
      },
      "id": "969b9020-710d-4c57-9342-20ea1667611b",
      "name": "Refuse: approval does not cover this email",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2640,
        380
      ]
    },
    {
      "parameters": {},
      "id": "da2ada66-a3ca-4232-a4d5-bd01a7b5dbd0",
      "name": "Rejected: do not send",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1760,
        480
      ]
    },
    {
      "parameters": {},
      "id": "ec17dc21-b6d6-49cd-a3b6-ddeffe3cf39d",
      "name": "Nobody answered in time",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1760,
        620
      ]
    },
    {
      "parameters": {},
      "id": "5f0c71a4-8c54-4d4f-96fb-6be776621ca6",
      "name": "Request was cancelled",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1760,
        760
      ]
    }
  ],
  "connections": {
    "Run it": {
      "main": [
        [
          {
            "node": "Draft reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft reply": {
      "main": [
        [
          {
            "node": "Hash the draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash the draft": {
      "main": [
        [
          {
            "node": "Create decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Still pending?": {
      "main": [
        [
          {
            "node": "Wait 15s",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Verdict",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "The email to send": {
      "main": [
        [
          {
            "node": "Hash the email to send",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create decision": {
      "main": [
        [
          {
            "node": "Wait 15s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 15s": {
      "main": [
        [
          {
            "node": "Check decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check decision": {
      "main": [
        [
          {
            "node": "Still pending?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verdict": {
      "main": [
        [
          {
            "node": "The email to send",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rejected: do not send",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Nobody answered in time",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Request was cancelled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify approval": {
      "main": [
        [
          {
            "node": "Covers this email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Covers this email?": {
      "main": [
        [
          {
            "node": "Consume approval",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Refuse: approval does not cover this email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Consume approval": {
      "main": [
        [
          {
            "node": "Send it?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send it?": {
      "main": [
        [
          {
            "node": "Already sent, not sending again",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send customer email (stand-in)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Refuse: approval already spent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash the email to send": {
      "main": [
        [
          {
            "node": "Verify approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
