How to Implement Human-in-the-Loop for AI Emails A new human-in-the-loop workflow for AI-generated emails, demonstrated with Node.js and TypeScript using the Impri review API and nodemailer, replaces risky 'fire and forget' AI email sending with a controlled pipeline where an AI agent drafts, a human audits and edits, and only the approved version is sent. The implementation polls Impri for a human decision before executing the send command, ensuring production-ready fail-safe LLM agent deployment. How to Implement Human-in-the-Loop for AI Emails approved status.This approach transforms the AI workflow from a risky "fire and forget" system into a controlled pipeline: the agent drafts, a human audits/edits, and only then does the code execute the send command. The Logic Flow Instead of a direct call to an SMTP server, the agent follows this sequence: 1. Draft Generation: The agent produces the content. 2. External Holding: The draft is pushed to a review system like Impri via API. 3. Human Intervention: A reviewer reads the draft, makes necessary edits to the body or subject, and hits "Approve." 4. Execution: The system polls for the decision; if approved, it sends the final edited version , not the original AI draft. Real-world Implementation: Node.js & TypeScript Here is a practical tutorial on how to build this approval gate using nodemailer and a review API. python import nodemailer from "nodemailer"; const IMPRI KEY = process.env.IMPRI API KEY ; const IMPRI BASE = "https://api.impri.dev"; async function sendWithApproval opts: { to: string; subject: string; body: string; expiresIn?: number; // seconds; default 3600 } : Promise<"approved" | "rejected" | "expired" { // 1. Push the draft to review const push = await fetch ${IMPRI BASE}/v1/actions , { method: "POST", headers: { Authorization: Bearer ${IMPRI KEY} , "Content-Type": "application/json", }, body: JSON.stringify { kind: "email.send", title: Email to ${opts.to}: ${opts.subject} , preview: { format: "markdown", body: To: ${opts.to}\n Subject: ${opts.subject}\n\n---\n\n${opts.body} , }, editable: "preview.body" , expires in: opts.expiresIn ?? 3600, } , } ; if push.ok throw new Error Push failed: ${push.status} ; const { id: actionId } = await push.json ; // 2. Poll until human decision let result: { status: string; decision?: { final preview?: { body: string } } }; for ;; { const poll = await fetch ${IMPRI BASE}/v1/actions/${actionId} , { headers: { Authorization: Bearer ${IMPRI KEY} }, } ; result = await poll.json ; if result.status == "pending" break; await new Promise r = setTimeout r, 10 000 ; } if result.status == "approved" { return result.status as "rejected" | "expired"; } // 3. Send the human-approved version const approvedBody = result.decision .final preview .body; const transporter = nodemailer.createTransport { host: process.env.SMTP HOST, port: 587, auth: { user: process.env.SMTP USER, pass: process.env.SMTP PASS }, } ; await transporter.sendMail { from: process.env.SMTP FROM, to: opts.to, subject: opts.subject, text: approvedBody, } ; // 4. Update audit log await fetch ${IMPRI BASE}/v1/actions/${actionId}/result , { method: "POST", headers: { Authorization: Bearer ${IMPRI KEY} , "Content-Type": "application/json", }, body: JSON.stringify { status: "success" } , } ; return "approved"; } By treating human approval as a mandatory API response rather than a prompt suggestion, you create a fail-safe LLM agent deployment that is actually production-ready. Next Mage-Flow: 4B Params vs 32B Giants → /en/threads/2450/