Docs
Approvals & Email Flows

Approvals & Email Flows

Plan approval, step-level approvals, and the bot-prefetch guard.

Plan Approval

After a workflow is created, the team owner receives a plan approval email with a step-by-step breakdown and an "Approve & Activate" button.

The button links to:

GET /api/workflows/[id]/approve?token=<24h-JWT>

When the user clicks it:

  1. Token is verified via verifyToken() from lib/ai/workflows/tokens.ts
  2. Workflow status set to "active"
  3. A new conversation is created (with niche: "workflow")
  4. A new workflowRun is created with status: "running"
  5. Step 0 is enqueued to QStash with a 2-second delay (allows DB commit to propagate)
  6. User is redirected to /dashboard/workflows/[id]

Step-Level Approval

When a workflow step's tool has requiresApproval: true (e.g., format_post):

  1. The step agent runs, produces the tool output
  2. saveAgentResponse() returns toolNeedsApproval: true
  3. The step executor sets workflowRun.status = "awaiting_approval"
  4. sendStepApprovalEmail() is called with the tool's output as a preview
  5. The user receives an email with Approve / Request Changes buttons

On approve: GET /api/workflows/resume?token=<2h-JWT>&action=approve

  1. Token verified
  2. Run status set to "approved"
  3. QStash re-enqueues the same step index with a userResponse: "Approved. Please continue." message prepended to the conversation

On reject: GET /api/workflows/resume?token=<2h-JWT>&action=reject&note=<feedback>

  1. Token verified
  2. Run status set to "approved" (re-runs the step)
  3. QStash re-enqueues with userResponse: "Rejected. Feedback: <note>. Please revise." prepended

JWT Token Types

Both token types are signed with WORKFLOW_TOKEN_SECRET (HS256 via jose):

// lib/ai/workflows/tokens.ts
type TokenPayload =
  | { type: "plan_approval"; workflowId: string }
  | { type: "step_approval"; workflowId: string; runId: number; conversationId: string; stepIndex: number }
TokenExpiryUsed By
plan_approval24 hoursPlan approval email link
step_approval2 hoursStep approve/reject email links

Bot-Prefetch Guard

Email clients (Outlook, Gmail, Apple Mail) automatically send a GET request to every link in an email to generate link previews. Without protection, this would trigger approvals before the user opens the email.

GET /api/workflows/resume checks the incoming User-Agent header against a list of known bot patterns. If matched, it returns 200 OK with no side effects. Human browser UAs proceed with the full approval logic.

Never disable the bot-prefetch guard. A single prefetch from an email client can approve or reject a workflow step instantly, before the user even opens the email.