daimon.email
ExamplesAdvanced

Human Approval Workflow

Agent requests operator approval before sensitive actions

Overview

Not all agent operations should be fully autonomous. For sensitive actions — like bulk email sends, account upgrades, or data deletion — you want human approval before the agent proceeds.

This example shows how to implement a human-in-the-loop approval workflow using the /v1/notify-operator endpoint.

Info

Best for: Agents that perform high-impact operations (bulk sends, payments, data deletion) where you want explicit operator confirmation before proceeding.

Architecture

Agent detects need for approval

POST /v1/notify-operator (creates approval request)

Operator receives magic link (email, Slack, Discord)

Operator clicks link → instant approval

Agent receives webhook: approval.granted

Agent proceeds with approved action

Complete Implementation

import { DaimonClient } from 'daimon-email';

class ApprovalWorkflowAgent {
  private client: DaimonClient;
  private pendingApprovals: Map<string, any> = new Map();

  constructor(apiKey: string) {
    this.client = new DaimonClient({ apiKey });
  }

  async sendBulkEmail(recipients: string[], subject: string, body: string) {
    // Check if this needs approval (e.g., > 100 recipients)
    if (recipients.length > 100) {
      console.log(`⚠️  Bulk send to ${recipients.length} recipients requires approval`);

      const approval = await this.requestApproval({
        action: 'bulk_send',
        context: {
          recipient_count: recipients.length,
          subject: subject,
          preview: body.slice(0, 200)
        },
        reason: `Sending to ${recipients.length} recipients exceeds safety threshold`,
        urgency: 'medium'
      });

      console.log('⏳ Waiting for operator approval...');
      console.log('📧 Approval request sent to operator');

      // Wait for approval (via webhook)
      return approval.requestId;
    }

    // Safe to send without approval
    return this.executeBulkSend(recipients, subject, body);
  }

  async requestApproval(params: {
    action: string;
    context: any;
    reason: string;
    urgency?: 'low' | 'medium' | 'high';
  }) {
    const approval = await this.client.account.notifyOperator({
      type: 'approval_request',
      action: params.action,
      context: params.context,
      reason: params.reason,
      urgency: params.urgency || 'medium',
      callbackUrl: `https://my-agent.com/webhook/approval`,
      expiresIn: 3600 // 1 hour
    });

    // Store pending approval
    this.pendingApprovals.set(approval.requestId, {
      action: params.action,
      context: params.context,
      timestamp: Date.now()
    });

    return approval;
  }

  async handleApprovalWebhook(event: any) {
    const { requestId, status, approvedAt, approvedBy } = event;

    const pending = this.pendingApprovals.get(requestId);
    if (!pending) {
      console.warn('Unknown approval request:', requestId);
      return;
    }

    if (status === 'approved') {
      console.log('✅ Approval granted by:', approvedBy);
      console.log('⏱️  Approved in:', Date.now() - pending.timestamp, 'ms');

      // Execute the approved action
      await this.executeApprovedAction(pending);

      this.pendingApprovals.delete(requestId);
    } else if (status === 'denied') {
      console.log('❌ Approval denied by:', approvedBy);
      console.log('Reason:', event.denialReason);

      this.pendingApprovals.delete(requestId);
    } else if (status === 'expired') {
      console.log('⏰ Approval request expired');
      this.pendingApprovals.delete(requestId);
    }
  }

  private async executeApprovedAction(approval: any) {
    switch (approval.action) {
      case 'bulk_send':
        const { recipient_count, subject, preview } = approval.context;
        console.log(`📧 Executing approved bulk send to ${recipient_count} recipients`);
        // Execute the send
        break;

      case 'account_upgrade':
        console.log('💳 Executing approved account upgrade');
        // Process upgrade
        break;

      case 'data_deletion':
        console.log('🗑️  Executing approved data deletion');
        // Delete data
        break;

      default:
        console.warn('Unknown action:', approval.action);
    }
  }

  private async executeBulkSend(recipients: string[], subject: string, body: string) {
    console.log(`📧 Sending to ${recipients.length} recipients...`);

    for (const recipient of recipients) {
      await this.client.inboxes.send('inb_xxx', {
        to: recipient,
        subject,
        body
      });
    }

    console.log('✅ Bulk send complete');
  }
}

// Usage
const agent = new ApprovalWorkflowAgent('dm_live_xxx');

// This will trigger approval flow
await agent.sendBulkEmail(
  ['user1@example.com', 'user2@example.com', /* ... 500 more */],
  'Product Update',
  'We are excited to announce...'
);

// Webhook handler
app.post('/webhook/approval', async (req, res) => {
  await agent.handleApprovalWebhook(req.body);
  res.status(200).send('OK');
});
import time
from daimon_email import DaimonClient

class ApprovalWorkflowAgent:
    def __init__(self, api_key: str):
        self.client = DaimonClient(api_key=api_key)
        self.pending_approvals = {}

    def send_bulk_email(self, recipients: list, subject: str, body: str):
        # Check if this needs approval (e.g., > 100 recipients)
        if len(recipients) > 100:
            print(f'⚠️  Bulk send to {len(recipients)} recipients requires approval')

            approval = self.request_approval({
                'action': 'bulk_send',
                'context': {
                    'recipient_count': len(recipients),
                    'subject': subject,
                    'preview': body[:200]
                },
                'reason': f'Sending to {len(recipients)} recipients exceeds safety threshold',
                'urgency': 'medium'
            })

            print('⏳ Waiting for operator approval...')
            print('📧 Approval request sent to operator')

            # Wait for approval (via webhook)
            return approval['request_id']

        # Safe to send without approval
        return self.execute_bulk_send(recipients, subject, body)

    def request_approval(self, params: dict):
        approval = self.client.account.notify_operator({
            'type': 'approval_request',
            'action': params['action'],
            'context': params['context'],
            'reason': params['reason'],
            'urgency': params.get('urgency', 'medium'),
            'callback_url': 'https://my-agent.com/webhook/approval',
            'expires_in': 3600  # 1 hour
        })

        # Store pending approval
        self.pending_approvals[approval['request_id']] = {
            'action': params['action'],
            'context': params['context'],
            'timestamp': time.time()
        }

        return approval

    def handle_approval_webhook(self, event: dict):
        request_id = event['request_id']
        status = event['status']

        pending = self.pending_approvals.get(request_id)
        if not pending:
            print(f'Unknown approval request: {request_id}')
            return

        if status == 'approved':
            print(f'✅ Approval granted by: {event["approved_by"]}')
            print(f'⏱️  Approved in: {time.time() - pending["timestamp"]} seconds')

            # Execute the approved action
            self.execute_approved_action(pending)

            del self.pending_approvals[request_id]

        elif status == 'denied':
            print(f'❌ Approval denied by: {event["approved_by"]}')
            print(f'Reason: {event.get("denial_reason")}')

            del self.pending_approvals[request_id]

        elif status == 'expired':
            print('⏰ Approval request expired')
            del self.pending_approvals[request_id]

    def execute_approved_action(self, approval: dict):
        if approval['action'] == 'bulk_send':
            context = approval['context']
            print(f'📧 Executing approved bulk send to {context["recipient_count"]} recipients')
            # Execute the send

        elif approval['action'] == 'account_upgrade':
            print('💳 Executing approved account upgrade')
            # Process upgrade

        elif approval['action'] == 'data_deletion':
            print('🗑️  Executing approved data deletion')
            # Delete data

        else:
            print(f'Unknown action: {approval["action"]}')

    def execute_bulk_send(self, recipients: list, subject: str, body: str):
        print(f'📧 Sending to {len(recipients)} recipients...')

        for recipient in recipients:
            self.client.inboxes.send('inb_xxx', {
                'to': recipient,
                'subject': subject,
                'body': body
            })

        print('✅ Bulk send complete')

# Usage
agent = ApprovalWorkflowAgent('dm_live_xxx')

# This will trigger approval flow
agent.send_bulk_email(
    ['user1@example.com', 'user2@example.com'] + ['user{}@example.com'.format(i) for i in range(500)],
    'Product Update',
    'We are excited to announce...'
)

Approval Request Formats

Standard Approval Request

{
  "type": "approval_request",
  "action": "bulk_send",
  "context": {
    "recipient_count": 500,
    "subject": "Product Update",
    "preview": "We are excited to announce..."
  },
  "reason": "Sending to 500 recipients exceeds safety threshold",
  "urgency": "medium",
  "callbackUrl": "https://my-agent.com/webhook/approval",
  "expiresIn": 3600
}

Upgrade Approval Request

{
  "type": "approval_request",
  "action": "account_upgrade",
  "context": {
    "currentTier": "free",
    "requestedTier": "developer",
    "monthlyCost": 9,
    "reason": "Need sending capabilities"
  },
  "reason": "Agent requires sending access for email automation",
  "urgency": "high"
}

Operator Experience

When an approval is requested, the operator receives a notification with a magic link:

Subject: Agent Approval Required: Bulk Send

Your agent needs approval for the following action:

Action: Send bulk email
Recipients: 500
Subject: Product Update
Urgency: Medium

[Approve Action] [Deny Action]

This request expires in 1 hour.

Clicking "Approve" instantly triggers the webhook to your agent.

Advanced Patterns

Multi-Step Approval

// Step 1: Request approval for upgrade
const upgradeApproval = await agent.requestApproval({
  action: 'account_upgrade',
  context: { tier: 'growth', cost: 29 },
  reason: 'Need higher send limits'
});

// Step 2: After upgrade approved, request approval for bulk send
const sendApproval = await agent.requestApproval({
  action: 'bulk_send',
  context: { recipientCount: 1000 },
  reason: 'Large campaign launch'
});

Conditional Approval

// Only request approval for high-value operations
if (operation.estimatedCost > 100) {
  await agent.requestApproval({
    action: 'execute_campaign',
    context: { estimatedCost: operation.estimatedCost },
    reason: 'High-cost operation requires approval'
  });
}

Approval with Timeout Fallback

const approval = await agent.requestApproval({
  action: 'data_export',
  context: { recordCount: 10000 },
  reason: 'Large data export',
  expiresIn: 300 // 5 minutes
});

// If no response in 5 minutes, execute safe fallback
setTimeout(() => {
  if (agent.pendingApprovals.has(approval.requestId)) {
    console.log('Approval timeout - executing safe fallback');
    agent.executeSafeDataExport();
  }
}, 300000);

Next Steps