daimon.email
Guides

Magic Upgrade Flow

How AI agents can request tier upgrades from operators without breaking their workflow

The Challenge

AI agents often discover they need additional capabilities mid-task:

  • Trying to send an email but on free tier
  • Hitting daily send limits
  • Needing custom domains
  • Requiring SMTP credentials

Traditional APIs would just return an error. The agent fails, the task fails, and a human has to intervene, debug, and manually upgrade.

The Magic Upgrade Solution

daimon.email provides a seamless upgrade flow that keeps agents running:

  1. Agent hits a limit → API returns a rich error with upgrade context
  2. Agent generates magic link → One-click upgrade URL for the operator
  3. Agent provides script → Tells operator exactly what to say/do
  4. Operator clicks link → No login required, straight to Stripe checkout
  5. Webhook fires → Agent is notified within 5 seconds of upgrade
  6. Agent continues → Automatically retries the original operation

Info

Magic links are cryptographically signed URLs that allow upgrade without authentication. They expire after 7 days and are single-use. The operator never needs to log in or remember credentials.

Complete Implementation

Step 1: Agent Hits a Limit

When an agent tries an operation beyond its tier, it receives a structured error response:

try {
  await client.inboxes.send(inboxId, {
    to: 'customer@example.com',
    subject: 'Your order is ready',
    body: 'Click here to track your shipment...'
  });
} catch (error) {
  if (error.code === 'SEND_REQUIRES_PAID') {
    console.log('Error details:', {
      code: error.code,
      message: error.message,
      upgradeContext: error.upgradeContext
    });
  }
}
try:
    client.inboxes.send(inbox_id, {
        'to': 'customer@example.com',
        'subject': 'Your order is ready',
        'body': 'Click here to track your shipment...'
    })
except DaimonError as e:
    if e.code == 'SEND_REQUIRES_PAID':
        print('Error details:', {
            'code': e.code,
            'message': e.message,
            'upgrade_context': e.upgrade_context
        })

Response

{
  "error": "SEND_REQUIRES_PAID",
  "message": "Sending emails requires a paid tier",
  "upgrade_context": {
    "operator_action_url": "https://daimon.email/upgrade?token=eyJhbGc...",
    "operator_action_label": "Add a payment method to enable sending",
    "agent_script": "Tell your operator: I need sending access to complete the customer notification. Here's a direct upgrade link: https://daimon.email/upgrade?token=eyJhbGc..."
  }
}

Step 2: Agent Communicates with Operator

The agent uses the agent_script to communicate the need clearly:

class EmailAgent {
  async handleUpgradeNeeded(error) {
    // Option 1: Log for operator monitoring
    console.log(`🚨 OPERATOR ACTION NEEDED: ${error.upgradeContext.agent_script}`);

    // Option 2: Send to operator communication channel
    await this.notifyOperator({
      urgency: 'medium',
      message: error.upgradeContext.agent_script,
      action_url: error.upgradeContext.operator_action_url,
      action_label: error.upgradeContext.operator_action_label
    });

    // Option 3: If agent has UI integration
    await this.ui.showAlert({
      type: 'upgrade_required',
      message: error.upgradeContext.agent_script,
      button: {
        label: error.upgradeContext.operator_action_label,
        url: error.upgradeContext.operator_action_url
      }
    });

    // Wait for upgrade to complete
    await this.waitForUpgrade();
  }
}
class EmailAgent:
    async def handle_upgrade_needed(self, error):
        # Option 1: Log for operator monitoring
        print(f"🚨 OPERATOR ACTION NEEDED: {error.upgrade_context['agent_script']}")

        # Option 2: Send to operator communication channel
        await self.notify_operator({
            'urgency': 'medium',
            'message': error.upgrade_context['agent_script'],
            'action_url': error.upgrade_context['operator_action_url'],
            'action_label': error.upgrade_context['operator_action_label']
        })

        # Option 3: If agent has UI integration
        await self.ui.show_alert({
            'type': 'upgrade_required',
            'message': error.upgrade_context['agent_script'],
            'button': {
                'label': error.upgrade_context['operator_action_label'],
                'url': error.upgrade_context['operator_action_url']
            }
        })

        # Wait for upgrade to complete
        await self.wait_for_upgrade()

Magic links in error responses are pre-generated. For custom flows, agents can generate fresh links:

// Generate a new magic upgrade link
const link = await client.account.createUpgradeLink({
  target_tier: 'developer',  // or 'growth'
  metadata: {
    agent_id: 'agent-7392',
    task: 'customer-notification',
    urgency: 'high'
  }
});

console.log('Generated upgrade URL:', link.url);
console.log('Expires at:', link.expires_at);

// Share with operator
await sendToSlack(`
  I need to upgrade to continue sending customer notifications.

  Current task blocked: Order confirmation emails
  Required tier: Developer ($9/month)

  → ${link.url}

  This link expires in 7 days.
`);
# Generate a new magic upgrade link
link = client.account.create_upgrade_link({
    'target_tier': 'developer',  # or 'growth'
    'metadata': {
        'agent_id': 'agent-7392',
        'task': 'customer-notification',
        'urgency': 'high'
    }
})

print(f"Generated upgrade URL: {link['url']}")
print(f"Expires at: {link['expires_at']}")

# Share with operator
send_to_slack(f"""
    I need to upgrade to continue sending customer notifications.

    Current task blocked: Order confirmation emails
    Required tier: Developer ($9/month)

{link['url']}

    This link expires in 7 days.
""")

When the operator clicks the link, they see a simple upgrade page:

  1. No login required — The JWT in the URL identifies the account
  2. Clear context — Shows why the agent needs the upgrade
  3. One-click checkout — Stripe Checkout handles payment
  4. Instant activation — Account upgrades immediately upon payment

The upgrade page shows:

  • Agent that requested the upgrade
  • Current tier vs requested tier
  • Features that will be unlocked
  • Monthly price
  • "Upgrade Now" button → Stripe Checkout

Step 5: Agent Receives Webhook Notification

Within 5 seconds of payment, a webhook fires to notify the agent:

// Webhook handler
app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-daimon-signature'];

  if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;

  if (event === 'account.upgraded') {
    console.log('🎉 Account upgraded!', {
      old_tier: data.old_tier,
      new_tier: data.new_tier,
      upgraded_at: data.upgraded_at,
      expires_at: data.expires_at
    });

    // Resume blocked operations
    await this.resumeBlockedTasks();
  }

  res.status(200).send('OK');
});

// In the agent's task queue
async resumeBlockedTasks() {
  const blocked = await this.getBlockedTasks();

  for (const task of blocked) {
    if (task.blocked_reason === 'SEND_REQUIRES_PAID') {
      // Retry the send operation
      await this.retrySend(task);
    }
  }
}
# Webhook handler
@app.post('/webhook')
async def handle_webhook(request):
    signature = request.headers.get('X-Daimon-Signature')

    if not verify_webhook_signature(request.body, signature, WEBHOOK_SECRET):
        return Response(status=401, text='Invalid signature')

    data = request.json()

    if data['event'] == 'account.upgraded':
        print('🎉 Account upgraded!', {
            'old_tier': data['data']['old_tier'],
            'new_tier': data['data']['new_tier'],
            'upgraded_at': data['data']['upgraded_at'],
            'expires_at': data['data']['expires_at']
        })

        # Resume blocked operations
        await resume_blocked_tasks()

    return Response(status=200, text='OK')

# In the agent's task queue
async def resume_blocked_tasks():
    blocked = await get_blocked_tasks()

    for task in blocked:
        if task['blocked_reason'] == 'SEND_REQUIRES_PAID':
            # Retry the send operation
            await retry_send(task)

All Upgrade Trigger Codes

Your agent should handle these upgrade scenarios:

Error CodeDescriptionRequired TierMonthly Cost
SEND_REQUIRES_PAIDSending any emailDeveloper$9
SEND_LIMIT_EXCEEDEDDaily send limit hitGrowth$49
DOMAIN_REQUIRES_PAIDCustom domain setupDeveloper$9
INBOX_LIMIT_EXCEEDEDToo many inboxesGrowth$49
SMTP_REQUIRES_PAIDSMTP credentialsDeveloper$9
WEBHOOK_ENDPOINT_UNHEALTHYWebhook failingAny-
ACCOUNT_UNDER_REVIEWSuspicious activityContact support-

Advanced Patterns

Pattern: Graceful Degradation

Operate within current tier limits while waiting for upgrade:

class AdaptiveEmailAgent {
  async sendEmail(to, subject, body) {
    const caps = await this.getCapabilities();

    if (caps.can_send) {
      // Send directly
      return await this.client.send(to, subject, body);
    } else if (caps.can_create_draft) {
      // Create draft for human to send later
      const draft = await this.client.drafts.create({
        to, subject, body,
        note: 'Auto-draft: Pending tier upgrade for sending'
      });

      await this.notifyOperator({
        message: `Created draft "${subject}" - upgrade required to send`,
        upgrade_url: caps.upgrade_url
      });

      return { status: 'drafted', draft_id: draft.id };
    } else {
      // Store locally until upgraded
      await this.queueForLater({ to, subject, body });
      await this.requestUpgrade();
      return { status: 'queued' };
    }
  }
}

Pattern: Proactive Upgrade Requests

Monitor usage and request upgrades before hitting limits:

class ProactiveAgent {
  async checkUpgradeNeeded() {
    const metrics = await this.client.getMetrics();

    // At 80% of daily limit
    if (metrics.sends_today / metrics.daily_limit > 0.8) {
      const link = await this.client.account.createUpgradeLink({
        target_tier: 'growth',
        metadata: {
          reason: 'Approaching daily send limit',
          current_usage: metrics.sends_today,
          limit: metrics.daily_limit
        }
      });

      await this.notifyOperator({
        urgency: 'low',
        message: `📊 I'm at 80% of my daily send limit. Consider upgrading to Growth tier for 10x more sends: ${link.url}`
      });
    }
  }
}

Pattern: Upgrade Analytics

Track upgrade requests and success rates:

class AnalyticsAgent {
  async trackUpgradeRequest(reason, tier) {
    await this.analytics.track('upgrade_requested', {
      reason,
      requested_tier: tier,
      current_tier: this.currentTier,
      timestamp: new Date().toISOString()
    });
  }

  async trackUpgradeComplete(oldTier, newTier) {
    await this.analytics.track('upgrade_completed', {
      old_tier: oldTier,
      new_tier: newTier,
      time_to_upgrade: Date.now() - this.upgradeRequestTime,
      blocked_tasks_resumed: this.blockedTasks.length
    });
  }
}

Implementation Checklist

When implementing magic upgrade flows in your agent:

  • Handle all upgrade error codes appropriately
  • Store blocked tasks for retry after upgrade
  • Register webhook endpoint for account.upgraded events
  • Implement operator notification channel (Slack, Discord, logs)
  • Add upgrade request tracking/analytics
  • Test graceful degradation within tier limits
  • Implement proactive upgrade monitoring
  • Handle magic link expiration (7 days)
  • Verify webhook signatures for security

Operator Experience

From the operator's perspective, the flow is seamless:

  1. Notification appears in Slack/Discord/logs
  2. Context is clear — why the agent needs upgrade
  3. One click on the magic link
  4. No login — goes straight to checkout
  5. Payment via Stripe — saved cards, Apple Pay, etc.
  6. Instant confirmation — agent resumes immediately

Example operator notification:

🤖 Sales Agent needs upgrade

I've queued 47 outreach emails but need sending access to deliver them.

Current tier: Free (receive only)
Required tier: Developer ($9/month)
Blocked tasks: 47 emails

→ Upgrade now: https://daimon.email/upgrade?token=eyJ...
   (Link expires in 7 days)

Once upgraded, I'll automatically send all queued emails.

Security Considerations

  • Signed JWTs: Links contain cryptographically signed tokens
  • Time-limited: Expire after 7 days
  • Single-use: Can only be used once
  • Account-bound: Only upgrade the specific account
  • No PII: Token doesn't contain sensitive information

Webhook Verification

Always verify webhook signatures to ensure upgrades are legitimate:

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  const calculated = `sha256=${hmac.digest('hex')}`;

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(calculated)
  );
}
AspectMagic Link (daimon.email)Traditional
AuthenticationNone requiredEmail + password
Time to upgrade<30 seconds5-10 minutes
ContextEmbedded in linkLost, operator must investigate
Operator effortSingle clickMultiple steps
Agent awarenessInstant webhookMust poll or wait
Mobile friendlyYesOften difficult
Expiration7 daysSession timeout

Next Steps