{
  "name": "Ratifia: send customer email (protected)",
  "nodes": [
    {
      "parameters": {
        "content": "## The protected action\n\nThe only workflow that sends customer email, so the one that enforces approval. It trusts nothing it is handed: it hashes the email from its **own** arguments, asks Ratifia whether an approval covers exactly that email, then spends it.\n\nIt refuses when the decision is missing, not approved, for a different email, or already spent on another. Keep the mail credential **only** here.",
        "height": 260,
        "width": 580
      },
      "id": "473aca62-0ae8-461b-b08f-857c6d3db73c",
      "name": "Note 3b53da",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -80
      ]
    },
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "9775df94-24f3-4213-b478-fd565ed69651",
      "name": "Called by another workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "e2863693-ba5e-4c74-b87b-85f560aba794",
              "leftValue": "={{ $json.decisionId }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3844f292-7843-43b7-b759-f7fd2807481d",
      "name": "Has a decision?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "errorMessage": "Not sent: no decision was passed in"
      },
      "id": "f39a0356-5723-4f3f-8816-d2671c62a2cd",
      "name": "Refuse: no approval",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        440,
        460
      ]
    },
    {
      "parameters": {
        "type": "SHA256",
        "value": "={{ JSON.stringify({ attachments: $(\"Called by another workflow\").item.json.attachments, body: $(\"Called by another workflow\").item.json.body, recipient: $(\"Called by another workflow\").item.json.recipient, subject: $(\"Called by another workflow\").item.json.subject }) }}",
        "dataPropertyName": "factsHash"
      },
      "id": "3743ee46-e5d6-4a65-9685-5e457ebe3eed",
      "name": "Hash the email to send",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        440,
        200
      ]
    },
    {
      "parameters": {
        "url": "=https://api.ratifia.com/v1/worker/decisions/{{ $(\"Called by another workflow\").item.json.decisionId }}/verify",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "factsHash",
              "value": "={{ $json.factsHash }}"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "425455a4-a4e3-4ced-a0a6-1fb03c13eaf2",
      "name": "Verify approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "1e668d7d-d70e-4bc1-a948-7481dbeeb1c0",
              "leftValue": "={{ $json.valid }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "9eb88f15-c777-4536-bdaa-71abc48ec126",
              "leftValue": "={{ $json.reason }}",
              "rightValue": "consumed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "bb25d990-4f20-47a7-adac-f3e875218297",
      "name": "Covers this email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        880,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.ratifia.com/v1/worker/decisions/{{ $(\"Called by another workflow\").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": "ae4cec2a-9640-4184-836c-370e557539e4",
      "name": "Consume approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        100
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "776f125e-5953-4835-bb4f-4e5ae5952e22",
                    "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": "95cc94f7-51a8-46fe-b30d-2ed80eea7fdd",
                    "leftValue": "={{ $json.consumed }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Send"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Refused"
        }
      },
      "id": "7b3ad586-1272-4f8d-b26b-eb8964858d19",
      "name": "Send it?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1320,
        100
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b173af78-7f39-475c-961a-0200d08b1480",
              "name": "sent",
              "value": "={{ true }}",
              "type": "boolean"
            },
            {
              "id": "d4fa55f8-3d2e-4db3-9283-39eb5d5f97c2",
              "name": "to",
              "value": "={{ $(\"Called by another workflow\").item.json.recipient }}",
              "type": "string"
            },
            {
              "id": "6cb8ddbe-aee8-48a5-954a-07e664d47711",
              "name": "subject",
              "value": "={{ $(\"Called by another workflow\").item.json.subject }}",
              "type": "string"
            },
            {
              "id": "cdf138dd-796b-4b1e-97ef-001259bb7184",
              "name": "approvedUnder",
              "value": "={{ $json.decision.policyVersion ? \"policy v\" + $json.decision.policyVersion : \"no policy\" }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c91aa709-f78c-495f-bb34-cc32181666b7",
      "name": "Send customer email (stand-in)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1560,
        -60
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "11ef178c-0358-4936-aa03-fd09b88e9467",
              "name": "sent",
              "value": "={{ false }}",
              "type": "boolean"
            },
            {
              "id": "8dd8481d-ce77-4c53-b3da-999ac6e6819a",
              "name": "why",
              "value": "a retry of an email that was already sent",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ef77a2ce-5957-40ec-9218-c8acb74e3eff",
      "name": "Already sent, not sending again",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1560,
        100
      ]
    },
    {
      "parameters": {
        "errorMessage": "=Not sent: {{ $json.reason }}"
      },
      "id": "1dc9b1af-e2f0-49c4-8dda-04685b7a08f7",
      "name": "Refuse: approval already spent",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        1560,
        260
      ]
    },
    {
      "parameters": {
        "errorMessage": "=Not sent: {{ $json.reason }}"
      },
      "id": "9e1c0f34-b96e-43e5-adcf-6feb11061d9f",
      "name": "Refuse: approval does not cover this email",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        1100,
        440
      ]
    }
  ],
  "connections": {
    "Called by another workflow": {
      "main": [
        [
          {
            "node": "Has a decision?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has a decision?": {
      "main": [
        [
          {
            "node": "Hash the email to send",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Refuse: no approval",
            "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"
  }
}
