Automatically Pay Cloudflare Invoices
Scenario: Your AI DevOps agent monitors a billing email inbox. When it detects a Cloudflare invoice, it uses an LLM to extract the invoice amount, validates it against a PQSafe spend envelope, dispatches via the Wise or Stripe rail, and posts a Slack notification.
Prerequisites
@pqsafe/agent-payinstalled- Wise account with API access or Stripe account with a stored card
- Email inbox access (e.g., Gmail API, IMAP)
- Slack webhook URL for notifications
- Cloudflare billing email address:
billing@cloudflare.com
Install
npm install @pqsafe/agent-pay @langchain/anthropic langchain-
Extract invoice amount from email (LLM step)
The agent reads the email body and uses Claude to extract the invoice total.
import { ChatAnthropic } from '@langchain/anthropic'import { HumanMessage } from '@langchain/core/messages'const llm = new ChatAnthropic({ model: 'claude-haiku-4-5' })async function extractInvoiceAmount(emailBody: string): Promise<number> {const response = await llm.invoke([new HumanMessage(`Extract the total invoice amount in USD from this Cloudflare billing email.Return ONLY a number (e.g. 47.20). No currency symbol, no text.Email:${emailBody}`),])const amount = parseFloat(response.content as string)if (isNaN(amount) || amount <= 0) throw new Error(`Could not parse amount: ${response.content}`)return amount} -
Validate against envelope’s allowed recipients
The envelope allowlists Cloudflare’s payment identifiers — the agent cannot accidentally pay the wrong recipient.
import {generateKeyPair,createSpendEnvelope,createSignedEnvelope,} from '@pqsafe/agent-pay'const { secretKey } = await generateKeyPair()const envelope = createSpendEnvelope({agentId: 'devops-billing-agent',maxAmount: 500, // Cap: won't pay Cloudflare invoices over $500 autonomouslycurrency: 'USD',allowedRails: ['wise', 'stripe'],allowedRecipients: ['cloudflare.com', // Wise international wire'cloudflare.com/billing', // Stripe card-on-file],validUntil: new Date(Date.now() + 7 * 86_400_000), // 7 daysrequireApproval: true,approvalThreshold: 200, // Auto-approve under $200; Telegram approval over $200memo: 'Cloudflare infrastructure billing',})const signedEnvelope = createSignedEnvelope(envelope, secretKey) -
Dispatch via Wise (international wire) or Stripe (card-on-file)
The agent picks the best available rail based on invoice currency.
import { executeAgentPayment, buildLedgerRecord, submitToLedger } from '@pqsafe/agent-pay'async function payCloudflareInvoice(amount: number,invoiceRef: string,emailCurrency: 'USD' | 'EUR' | 'GBP' = 'USD') {// Use Wise for non-USD (international), Stripe for USD card-on-fileconst rail = emailCurrency !== 'USD' ? 'wise' : 'stripe'const result = await executeAgentPayment(signedEnvelope, {recipient: rail === 'stripe' ? 'cloudflare.com/billing' : 'cloudflare.com',amount,rail,memo: `Cloudflare invoice ${invoiceRef} — ${emailCurrency} ${amount}`,railOptions: rail === 'wise'? { targetCurrency: emailCurrency, reference: invoiceRef }: { paymentMethodId: process.env.CLOUDFLARE_STRIPE_PM_ID },})await submitToLedger(buildLedgerRecord(signedEnvelope, result))return result} -
Log to ledger and notify Slack
async function notifySlack(amount: number, status: string, txId: string) {await fetch(process.env.SLACK_WEBHOOK_URL!, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({text: [`*Cloudflare Invoice Paid* :white_check_mark:`,`Amount: $${amount}`,`Status: ${status}`,`TX ID: \`${txId}\``,`Authorized by: PQSafe ML-DSA-65 envelope`,].join('\n'),}),})} -
Full MCP-compatible implementation
This function can be registered as an MCP tool so any Claude agent can trigger it.
import { Server } from '@modelcontextprotocol/sdk/server/index.js'import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'const server = new Server({ name: 'pqsafe-billing-agent', version: '1.0.0' },{ capabilities: { tools: {} } })server.setRequestHandler('tools/list', async () => ({tools: [{name: 'pay_cloudflare_invoice',description: 'Pay a Cloudflare invoice from email. Extracts amount, validates envelope, dispatches payment.',inputSchema: {type: 'object',properties: {emailBody: { type: 'string', description: 'Raw Cloudflare invoice email body' },invoiceRef: { type: 'string', description: 'Invoice reference number' },},required: ['emailBody', 'invoiceRef'],},}],}))server.setRequestHandler('tools/call', async (req) => {const { emailBody, invoiceRef } = req.params.arguments as anyconst amount = await extractInvoiceAmount(emailBody)const result = await payCloudflareInvoice(amount, invoiceRef)await notifySlack(amount, result.status, result.txId)return {content: [{type: 'text',text: `Invoice paid. Amount: $${amount}. Status: ${result.status}. TX: ${result.txId}`,}],}})const transport = new StdioServerTransport()await server.connect(transport)
Expected output
Email detected: "Your Cloudflare invoice is ready — $47.20"LLM extraction: $47.20Rail selected: stripe (USD)Envelope valid: truePayment dispatched...Payment status: settledTX ID: ch_3abc123defSlack notified: ✅ Cloudflare Invoice Paid — $47.20Ledger hash: 0x7f3a...Troubleshooting
| Problem | Solution |
|---|---|
| LLM extracts wrong amount | Add invoice examples to the extraction prompt; validate 0 < amount < 500 |
APPROVAL_REQUIRED | Invoice exceeds approvalThreshold — approve via Telegram or increase threshold |
RECIPIENT_NOT_ALLOWED | Check allowedRecipients matches the recipient passed to executeAgentPayment |
| Stripe card declined | Check CLOUDFLARE_STRIPE_PM_ID is a valid stored payment method |
| Wise transfer failed | Verify WISE_PROFILE_ID has USD source balance |
Next steps
- Telegram Approval Gate — auto-approve under $200, human gate over $200
- Pay USDC Vendor — for crypto-native vendor payments
- Ledger concept — audit trail for all payments