Human-in-the-Loop Approvals in n8n
Implement approval workflows where humans review and approve AI-generated content before it goes live
Tools Required
Tags
Human-in-the-Loop Approvals in n8n
Sometimes AI gets it 90% right, but you need human oversight for that final 10%. Here’s how to build approval workflows in n8n.
Use Cases
- Approve AI-generated emails before sending
- Review content before publishing
- Validate data before writing to production database
- Sign off on high-value transactions
Workflow Structure
1. AI generates content
↓
2. Save to Airtable with "pending" status
↓
3. Send Slack message with preview + buttons
↓
4. Wait for button click
↓
5. If approved → proceed
If rejected → skip or modify
Step-by-Step
1. Generate Content
// In Function node
const content = {
id: $input.item.json.id,
subject: "...",
body: "...",
recipient: "...",
status: "pending_approval"
};
return content;
2. Save to Airtable
Create Airtable base with columns:
- ID (unique)
- Content (JSON)
- Status (Single select: pending, approved, rejected)
- Approver
- Timestamp
3. Send Slack Message
Use Slack’s Block Kit for rich formatting:
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*New email pending approval*"
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*To:*\n{{recipient}}"},
{"type": "mrkdwn", "text": "*Subject:*\n{{subject}}"}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "{{body}}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "Approve"},
"style": "primary",
"value": "approve_{{id}}"
},
{
"type": "button",
"text": {"type": "plain_text", "text": "Reject"},
"style": "danger",
"value": "reject_{{id}}"
}
]
}
]
}
4. Wait for Approval
Two approaches:
A) Webhook approach (simpler)
- Create webhook to receive Slack button clicks
- Slack button click → triggers new workflow
- Update Airtable status
- Original workflow polls Airtable every 30s
B) n8n Wait node (better)
- Use Wait node in workflow
- Generate unique resume URL
- Include URL in Slack button action
- Clicking button resumes workflow
5. Process Result
// Check approval status
if ($input.item.json.status === 'approved') {
// Send email, publish content, etc.
return { approved: true };
} else {
// Log rejection, notify creator
return { approved: false };
}
Tips
- Set timeout: Auto-reject after 24 hours if no response
- Track approver: Record who approved for audit trail
- Allow editing: Let approvers modify content before approving
- Bulk approval: Process multiple items at once
Example: Email Approval Workflow
Real workflow I use for AI-generated sales emails:
- AI drafts 50 emails
- Batch them in groups of 10
- Send each batch to Slack for review
- SDR approves/rejects each
- Approved emails → SendGrid queue (rate-limited)
- Rejected emails → saved for analysis
Result: 95% approval rate, SDR only reviews instead of writing from scratch.
Common Pitfalls
❌ Not handling timeouts ❌ Losing context between approval and action ❌ No way to edit before approving ❌ Not tracking who approved what
Start with simple approve/reject, add editing capabilities later.