Designing the 'Approve / Edit / Reject' Step in Slack and Teams
The first time you watch an LLM-drafted email land in a customer inbox without anyone reading it, you remember the moment forever. Mine was in March 2026: a workflow we'd been quietly testing on internal tickets escalated itself overnight to a real customer queue. Customer: a Series B fintech CFO. Message: "Hello [first_name], based on our records you owe us $0.00 — please remit at your earliest convenience." Twelve people, including our CEO and theirs, were on the cc. The hallucinated dollar figure was the least of it; the bracket placeholder was the screenshot that ended up in the company-wide post-mortem. That night we shipped a Slack approve/edit/reject card in front of every external send. It took us 90 minutes. It should have been there on day one. This lesson is how to ship that card — properly — in Slack Block Kit and Microsoft Teams Adaptive Cards, inside a 30-second human-review budget that does not destroy the agent's leverage.
Why the 30-Second Budget Is the Whole Game
Here is the math that decides whether your human-in-the-loop step is real engineering or theater. An agent that takes 4 seconds to produce a draft and saves 6 minutes of human work has a leverage ratio of 90x. Add a 5-second human review and the ratio is still 72x. Add a 90-second review (a Slack DM that needs context-switching, scrolling, copying back into the workflow, and a thumbs-up) and the ratio collapses to 4x. Add a 4-minute review with three reviewers and you have invented a slower version of the manual process.
The 30-second budget is the threshold beyond which most operators we've worked with stop trusting their own approval step. They start batch-clicking. They stop reading the draft. They write a Zapier filter to auto-approve anything from the workflow because "it's been right the last 200 times." At that point the human-in-the-loop is a placebo with a paper trail.
So the design constraint is: the approval card must let a reviewer make a decision — green-light, tweak, or kill — in under 30 seconds, without leaving Slack or Teams, without copying text anywhere, without opening a second tab. Everything in this lesson is in service of that constraint.
If the reviewer has to leave Slack to do their job, you have not shipped human-in-the-loop. You have shipped a notification.
Anatomy of the Approval Card
An approval card has exactly six elements. Less than this and the reviewer can't decide; more than this and the reviewer won't.
- Context line — one sentence telling the reviewer what this action will do and to whom. "Send refund email to Mark Hennessy at Acme Co. ($1,240 refund)." Not "Workflow output #42948." If the reviewer has to scroll to find out what the action is, the budget is gone.
- Draft preview — the full proposed action. For an email, the subject and body. For a Salesforce update, the field changes. Truncate to 1,200 characters with a "View full" expand for the rest. Beyond 1,200 chars the reviewer reads only the first paragraph anyway.
- Risk signal — a colored badge or emoji indicating any flags the agent itself raised. Confidence below threshold. PII detected. External recipient outside the allowlist. Mention of a currency value above a configured limit. The badge primes the reviewer: read carefully versus skim.
- Three buttons — Approve, Edit, Reject. Always three. Never two. Never four. The Edit button is the one that earns the card; it lets the reviewer salvage 70-80% of imperfect drafts instead of rejecting and forcing the agent to regenerate.
- Reviewer attribution — once a decision is made, the card stays in the channel with the decision and the reviewer's name visible. This is your audit trail surface and we'll cover it in Lesson 2 of this chapter.
- Timeout fallback — what happens if no one acts. Default to "reject after N minutes" for low-risk artifacts and "ping #ops-on-call" for high-risk artifacts. Silent timeout into auto-approve is the failure mode that produces the 2 a.m. customer email disaster.
Slack Block Kit: The Canonical Pattern
Slack Block Kit is the JSON layout system Slack has used since 2019 and is the only interactive UI surface that consistently renders well across desktop, mobile, and Slack Connect channels. For approval cards, the layout is a header section, a context section, a divider, a draft preview as a Markdown block, and an actions block with three buttons.
The Block Kit payload
Here is the minimum viable approval card in Block Kit JSON. Drop it into the Slack Block Kit Builder (block-kit-builder on the Slack API docs) to render it interactively.
{
"blocks": [
{ "type": "header", "text": { "type": "plain_text", "text": "Approval Required: Refund Email" } },
{ "type": "context", "elements": [ { "type": "mrkdwn", "text": ":warning: External send | Recipient: [email protected] | Confidence: 0.71" } ] },
{ "type": "divider" },
{ "type": "section", "text": { "type": "mrkdwn", "text": "*Subject:* Refund processed for order #84112\\n\\n*Body:*\\nHi Mark,\\n\\nWe've issued a refund of $1,240.00 to your card ending in 4471 for order #84112. Funds typically appear in 3-5 business days.\\n\\nIf you need an invoice for accounting, reply to this email and we'll send it within the hour.\\n\\nThanks,\\nAcme Support" } },
{ "type": "actions", "block_id": "approval_actions", "elements": [
{ "type": "button", "style": "primary", "text": { "type": "plain_text", "text": "Approve" }, "value": "approve_84112", "action_id": "approve" },
{ "type": "button", "text": { "type": "plain_text", "text": "Edit" }, "value": "edit_84112", "action_id": "edit" },
{ "type": "button", "style": "danger", "text": { "type": "plain_text", "text": "Reject" }, "value": "reject_84112", "action_id": "reject" }
] }
]
}
That payload, posted via the Slack chat.postMessage API, renders an approval card with three clickable buttons. When a button is clicked, Slack POSTs an interaction payload to a URL you configure in the app manifest. The payload contains the action_id ("approve", "edit", or "reject"), the original message timestamp, the reviewer's Slack user ID, and any selected values.
Where the workflow listens
Three options in 2026, in order of operator complexity:
- n8n native Slack node + Webhook node. The Slack node posts the message; a Webhook node receives the interaction payload. Most operators run this on n8n Cloud or a small self-hosted instance. The Slack app must be configured with the webhook URL as its interactivity request URL.
- Zapier Slack interactive component (beta, March 2026). Zapier added native interactive button handling that hides the webhook configuration behind their UI. Easier to set up, harder to debug when buttons don't trigger.
- Make.com Slack scenario with Webhook trigger. Same pattern as n8n; Make exposes the interaction payload as a structured object in subsequent modules.
The Edit button: where 70% of the value lives
The Approve and Reject buttons are easy. The Edit button is what separates a real approval card from a glorified notification. Two implementation patterns:
Pattern A: Slack modal with pre-filled text input. When the user clicks "Edit," your handler opens a Slack modal (the views.open API) with the draft text in an editable multi-line text input. The reviewer edits inline, clicks "Submit." The modal submission triggers a second webhook with the edited text. This is the right pattern for prose edits (emails, summaries, drafts).
Pattern B: thread-reply edit. The reviewer clicks "Edit," and the bot posts a thread reply saying "Reply in this thread with your changes." The reviewer types the new draft as a thread message. The bot listens to thread messages and uses the latest one as the canonical version. This is the right pattern for structured edits where you also want to capture reviewer rationale ("changed amount from $1240 to $1200 — original order was the discounted version").
The modal pattern is faster (one click + one paste); the thread pattern is more flexible (reviewer can iterate). For external-customer-facing drafts, the modal is almost always the right answer. For internal artifacts where reviewer rationale matters, the thread pattern wins.
Microsoft Teams Adaptive Cards: The Other Half of the Enterprise
If your organization runs on Microsoft 365, Slack is not an option for half your reviewers. Microsoft Teams uses Adaptive Cards — a separate JSON spec, similar in spirit to Block Kit but with different schema and rendering behavior. As of Adaptive Cards v1.6 (Teams support April 2026), the feature parity with Block Kit is finally close enough that operators don't have to write two completely different cards.
The Adaptive Card payload
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [
{ "type": "TextBlock", "size": "Large", "weight": "Bolder", "text": "Approval Required: Refund Email" },
{ "type": "TextBlock", "color": "Warning", "text": "External send | Recipient: [email protected] | Confidence: 0.71", "spacing": "Small", "wrap": true },
{ "type": "Container", "style": "emphasis", "items": [
{ "type": "TextBlock", "weight": "Bolder", "text": "Subject: Refund processed for order #84112", "wrap": true },
{ "type": "TextBlock", "text": "Hi Mark,\\n\\nWe've issued a refund of $1,240.00 to your card ending in 4471 for order #84112. Funds typically appear in 3-5 business days.\\n\\nIf you need an invoice for accounting, reply to this email and we'll send it within the hour.\\n\\nThanks,\\nAcme Support", "wrap": true }
] }
],
"actions": [
{ "type": "Action.Submit", "title": "Approve", "style": "positive", "data": { "decision": "approve", "draft_id": "84112" } },
{ "type": "Action.ShowCard", "title": "Edit", "card": { "type": "AdaptiveCard", "body": [ { "type": "Input.Text", "id": "edited_text", "isMultiline": true, "value": "Hi Mark,..." } ], "actions": [ { "type": "Action.Submit", "title": "Submit Edit", "data": { "decision": "edit", "draft_id": "84112" } } ] } },
{ "type": "Action.Submit", "title": "Reject", "style": "destructive", "data": { "decision": "reject", "draft_id": "84112" } }
]
}
Notice the key difference from Slack: in Adaptive Cards, the Edit flow uses Action.ShowCard, which expands an inline editing card without opening a separate modal. This is faster than the Slack modal — fewer clicks, less context loss — and it is the strongest argument for Teams as a review surface for operators who have a choice.
Where the workflow listens (Teams edition)
Teams interaction payloads route through the Bot Framework or, more commonly in 2026, through Power Automate's "When a card response is received" trigger. The trigger fires on every Submit, and the payload contains the data object you defined on the action plus the responder's Azure AD identity. From there it routes back to your workflow — typically n8n via a webhook, or directly into a Power Automate cloud flow if you've gone full Microsoft stack.
The two Teams gotchas operators hit
First: Adaptive Card versioning. Teams desktop, Teams mobile, and Teams in Outlook each support slightly different Adaptive Card versions. Stick to 1.5 features in May 2026 if you need universal rendering. Newer features (like Action.Execute for in-card refresh) only render correctly on the desktop client at current support levels.
Second: permissions for bot posting. To post an Adaptive Card into a channel, your bot needs the channel scope and the channel-team admin's consent. Many enterprises lock this down. Plan a 1-2 week IT onboarding lead time for any new Teams workflow.
The Design Rules That Keep the Card Under 30 Seconds
Rule one: surface the diff, not the document
For workflows that modify an existing artifact (a Salesforce field update, a Notion doc edit, a JIRA ticket field change), the card should show the diff, not the full new state. "Status: Open → In Progress" plus "Assignee: unassigned → [email protected]" is faster to read than the full ticket. For workflows that create a new artifact (email, document, message), show the full draft because there's no prior state to diff against.
Rule two: pre-compute the risk badge
Don't make the reviewer figure out whether to be careful. Have the agent itself flag risk before the card is posted. Specific signals worth surfacing:
- Confidence below 0.85 on a classification or extraction task — yellow badge with "Confidence: 0.71."
- External recipient detected — red badge for any email going outside the company domain allowlist.
- PII or PCI detected — red badge if the draft contains a credit card pattern, SSN pattern, or anything that matches a configured regex.
- Currency value above threshold — yellow badge for amounts over $1,000; red for over $10,000.
- First time recipient — yellow badge if the agent has not previously sent to this recipient address.
Risk badges shave 5-15 seconds off review time because they tell the reviewer where to look first. They also create a clean audit trail when something does go wrong: "the card showed a yellow badge, the reviewer approved anyway" is a clearer post-mortem than "the reviewer should have noticed."
Rule three: one card per decision, not per item
If your workflow generates 30 draft emails in a batch, do not post 30 cards. Post one summary message with 30 entries and per-entry buttons, or batch them into a single "Approve all 30" card with a "Review individually" expand option. Reviewers facing a wall of 30 cards default to approve-all-rapidly or ignore. Both are worse than no review.
Rule four: the timeout is not optional
Every approval card needs a timeout. After N minutes (typical: 30 for low-risk, 5 for high-risk), the workflow should either auto-reject (default for external sends), escalate (ping a backup reviewer or channel), or queue for the next reviewer rotation. Silent timeout into auto-approve is the design that produces the 2 a.m. disaster. Cards that nobody reviewed should fail closed, not open.
Rule five: the channel matters more than the message
An approval card posted to a quiet, focused #ops-approvals channel gets reviewed in minutes. The same card posted to a 200-member #general channel gets ignored for hours. Set up dedicated approval channels per workflow family. Subscribe the right humans. Restrict channel posting to the bot. The channel is the bottleneck, not the card.
The Five Failure Modes We Have Actually Seen
Failure one: the auto-approver
The most common failure is human, not technical. A reviewer who has approved 200 cards in a row stops reading. They click Approve as fast as the cards arrive. The fix is rotation: never have a single reviewer responsible for an approval queue for more than a week. Rotate two or three people. Adds variety; catches blind spots; protects against the "I've seen this pattern 200 times" complacency.
Failure two: the missing risk badge
A workflow that generates emails to vendors had no risk badge for "first time recipient." A small typo in a vendor name produced a draft addressed to a non-vendor (a journalist with a similar email address). The reviewer approved because the draft body looked fine. The badge would have flagged "first time recipient: never sent to this address before — verify." The fix took 12 lines of n8n. The reputational cost was a Bloomberg paragraph.
Failure three: the silent timeout
A workflow with a 60-minute timeout configured to "auto-approve if no reviewer acts" sent a customer escalation email to the wrong customer at 3 a.m. on a Saturday because the on-call reviewer was offline. The fix: auto-reject on timeout for external sends, auto-escalate to a backup channel for internal sends. Never auto-approve on timeout for anything customer-facing.
Failure four: the modal that breaks on mobile
A Slack modal with a large multi-line text input renders fine on desktop and is unusable on iOS — the keyboard covers the submit button and the modal doesn't scroll. The reviewer can't ship the edit. They give up, switch to desktop, lose the context, leave the queue. Fix: test every approval flow on mobile before shipping it. If your reviewers are field staff (sales, customer success), mobile is the primary surface.
Failure five: the approval card that survived the deletion
A reviewer approved a draft. The workflow sent the email. The reviewer noticed they'd approved the wrong thing 30 seconds later. The card was already gone (button-click removes the card). The fix: cards should not be destructive on click. They should update in place — "Approved by @jen at 11:42 AM. Undo within 60s." The undo window has saved more than a few customer-facing mistakes in workflows we've supported.
Putting It Together: The 90-Minute Build
Here is the build sequence we use when teams ask us to add approval cards to an existing LLM workflow. Total time: about 90 minutes for an operator who has touched Slack apps or Teams adaptive cards before; 3-4 hours for a first-timer.
- Minutes 0-15: Create the Slack app or Teams bot. Slack: api.slack.com → "Create an app" → from scratch. Add the
chat:write,chat:write.public, andcommandsscopes. Enable interactivity and set your webhook URL. Teams: aka.ms/teamsfx for the modern path, or Power Automate's "When a card is posted in a channel" trigger for the no-code path. - Minutes 15-30: Wire up the post-card step. Add a Slack or Teams "Post Message" node in your workflow after the LLM draft. Use the JSON payload from earlier as your starting point. Replace placeholder text with variables from your workflow context.
- Minutes 30-50: Wire up the listener. Create a Webhook node (n8n / Make / Zapier) that receives the interaction payload. Parse the
action_idand thevaluefield. Route by decision: Approve → execute the action; Edit → open a modal or thread reply; Reject → log and exit. - Minutes 50-65: Implement the Edit flow. For Slack: handle the
editaction_id by callingviews.openwith a pre-filled multi-line input. For Teams: the inline ShowCard in your initial payload handles this. Wire the submit to a second webhook that re-runs the downstream action with the edited text. - Minutes 65-80: Add the timeout. Create a scheduled trigger or a delayed node that fires N minutes after the card is posted. If the card has no decision (check a database flag), execute the timeout policy: auto-reject and notify a fallback channel.
- Minutes 80-90: Add the risk badge logic. Three lines of conditional formatting in the post-card step: if confidence below threshold, prepend warning emoji; if external recipient, prepend siren emoji; if PII detected, prepend lock emoji.
At minute 90 you have a working approval card. You do not have audit logging (Lesson 2) or rollback (Lesson 3). You have the surface that lets a human see what the agent is about to do and decide. That is the foundation; the rest of this chapter builds on it.
When to Skip the Approval Card
Not every action needs approval. Three categories where the card is overhead, not safety:
- Read-only outputs to internal channels. An agent that posts a daily summary to a private Slack channel doesn't need a card. The output is the action; readers can correct it socially.
- Reversible internal updates with low blast radius. A Notion page edit by an agent to a draft document, or a JIRA comment, is reversible. The cost of a wrong update is the click to undo. Approval cards add friction without preventing meaningful damage.
- High-volume, low-stakes decisions where the eval set is strong. A classification agent that tags incoming tickets with one of four categories, validated on a 500-example eval set with 98% accuracy, does not need per-decision approval. Sample-based review (manually check 5% of decisions weekly) is the better discipline.
The right question for every workflow: what is the worst outcome of a single bad decision, and how reversible is it? External customer email: high impact, low reversibility → always approve. Internal Slack summary: low impact, high reversibility → no approval. Salesforce field update: medium impact, medium reversibility → approval for high-value accounts only.
Key Takeaways
- The 30-second review budget is the operator constraint that decides whether human-in-the-loop is real engineering or theater. Past 30 seconds, reviewers stop reading and start batch-approving.
- An approval card has six elements: context line, draft preview, risk signal, three buttons (Approve / Edit / Reject), reviewer attribution, and a timeout fallback. Skip any of these and the card weakens.
- The Edit button is where 70% of the value lives — it salvages imperfect drafts that would otherwise be rejected and regenerated. Implement Edit via a Slack modal or a Teams Action.ShowCard.
- Slack Block Kit and Microsoft Teams Adaptive Cards (v1.5+) are both production-grade in May 2026. Teams has a slight UX edge for inline edits via Action.ShowCard; Slack has broader integrations and easier app setup.
- Pre-compute risk badges so the reviewer knows where to look first: confidence below 0.85, external recipient, PII detected, currency above threshold, first time recipient. Each badge shaves 5-15 seconds off review time.
- Never auto-approve on timeout for external-facing actions. Auto-reject and escalate. Silent auto-approve is the design that produces the 2 a.m. disaster.
- Failure modes to defend against: the auto-approver (rotate reviewers), the missing badge (add the first-recipient flag), the broken mobile modal (test on iOS), the irreversible click (add a 60s undo window).
- Not every action needs a card. Read-only internal outputs, reversible low-blast-radius internal updates, and high-volume low-stakes decisions with strong eval coverage are categories where approval cards add friction without preventing damage.
- The 90-minute build sequence: create the app or bot, wire up the post-card step, wire up the listener, implement Edit, add the timeout, add the risk badges. That gets you the surface; audit logging and rollback come next.
Skill.re