AI screen, human approve, then send
Classify an inbound request, ask a reviewer to approve or reject it, and email the requester the outcome.
Drag to pan, pinch to zoom. This is the real graph the template installs.
Purpose
Classify an inbound request, ask a reviewer to approve or reject it, and email the requester the outcome.
How it works
What it does
- Webhook
approval-request→ Risk screen (low_risk | needs_review | high_risk) gives the reviewer context. - Reviewer decision pauses the run until someone picks Approve / Reject (48 h timeout).
- Approved? routes to the approval or rejection email.
Nodes 6
- 01Request intrigger.webhookTrigger
- 02Risk screenai.classifyAI
- 03Reviewer decisionhuman.promptHuman
- 04Approved?control.branchLogic
- 05Send approvalmail.sendDestination
- 06Send rejectionmail.sendDestination
View template JSON
{
"format": "mantle-workflow/v1",
"id": "hitl-approval-gate",
"name": "AI screen, human approve, then send",
"graph": {
"nodes": [
{
"id": "webhook",
"type": "trigger.webhook",
"label": "Request in",
"config": {
"slug": "approval-request",
"input_schema": {
"required": [
"request",
"requester_email"
],
"properties": {
"request": {
"type": "string"
},
"requester_email": {
"type": "string"
}
}
}
},
"worker_type": "generic-worker",
"position": {
"x": 60,
"y": 220
}
},
{
"id": "ai_classify",
"type": "ai.classify",
"label": "Risk screen",
"config": {
"model": "claude-haiku-4-5",
"labels": [
"low_risk",
"needs_review",
"high_risk"
],
"text": "{{ $json.request }}",
"temperature": 0
},
"worker_type": "ai-worker",
"position": {
"x": 380,
"y": 220
}
},
{
"id": "reviewer",
"type": "human.prompt",
"label": "Reviewer decision",
"config": {
"prompt_text": "Request from {{ steps.webhook.requester_email }} (AI: {{ steps.ai_classify.response.label }}, {{ steps.ai_classify.response.confidence }}):\n\n{{ steps.webhook.request }}",
"options": [
{
"id": "approve",
"label": "Approve"
},
{
"id": "reject",
"label": "Reject"
}
],
"timeout_seconds": 172800
},
"worker_type": "generic-worker",
"position": {
"x": 700,
"y": 220
}
},
{
"id": "branch_approved",
"type": "control.branch",
"label": "Approved?",
"config": {
"mode": "first_match"
},
"worker_type": "generic-worker",
"position": {
"x": 1020,
"y": 220
}
},
{
"id": "send_approved",
"type": "mail.send",
"label": "Send approval",
"config": {
"to": "{{ steps.webhook.requester_email }}",
"subject": "Your request was approved",
"body_text": "Good news — your request has been approved.\n\n{{ steps.webhook.request }}"
},
"worker_type": "generic-worker",
"position": {
"x": 1340,
"y": 30
}
},
{
"id": "send_rejected",
"type": "mail.send",
"label": "Send rejection",
"config": {
"to": "{{ steps.webhook.requester_email }}",
"subject": "Your request was not approved",
"body_text": "Unfortunately your request was not approved. Reply to this email if you have questions.\n\n{{ steps.webhook.request }}"
},
"worker_type": "generic-worker",
"position": {
"x": 1340,
"y": 410
}
}
],
"edges": [
{
"from": "webhook",
"to": "ai_classify"
},
{
"from": "ai_classify",
"to": "reviewer"
},
{
"from": "reviewer",
"to": "branch_approved"
},
{
"from": "branch_approved",
"to": "send_approved",
"branch": "true",
"condition": {
"match": "all",
"rules": [
{
"left": "response",
"op": "equals",
"right": "approve"
}
]
},
"label": "approve"
},
{
"from": "branch_approved",
"to": "send_rejected",
"branch": "false",
"label": "reject",
"priority": 1
}
]
}
}Requirements
The connections and permissions each step needs, from the node contracts in the catalog.
- AnthropicAPI key
Used by Risk screen.
Credential: {"api_key"}
- The provider adapter is chosen from the connection; `model` must belong to it.
- SendGridAPI key
Used by Send approval, Send rejection.
Credential: {"api_key"} or {"username", "password"}
Setup
- AI connection on Risk screen; SendGrid/SMTP on both send nodes.
- Optional: add a second branch before the reviewer to auto-approve
low_riskrequests.
Sample input and output
Synthetic examples: node-contract samples or placeholders, never customer data.
Input
The fields the template declares or reads from the request body. Values are placeholders.
{
"request": "<request>",
"requester_email": "<requester_email>"
}Output
Send approval mail.send
{
"body": null,
"message_id": "",
"provider": "",
"sent": true,
"status_code": 0,
"thread_id": ""
}Send rejection mail.send
{
"body": null,
"message_id": "",
"provider": "",
"sent": true,
"status_code": 0,
"thread_id": ""
}Failure handling
What the graph does when a step fails, read from its retry settings, error branches and error policies.
- Reviewer decision: the step fails if nobody answers within 2 days.
- A step that fails, after any retries, fails the run. The failed step is kept with its input and error.
See it run, then make it yours.
Templates install as drafts. Edit any step, and Workflow Health re-checks the graph on every save.