Recipes
Copy-pasteable patterns for common agent flows. Each assumes you've run am init and have ANIMA_API_KEY in your env. For setup, see /docs.
Inbound email triage → voice callback
Customer emails support. Subject contains 'urgent'. Call them back automatically.
import { Anima } from '@anima/sdk';
const am = new Anima({ apiKey: process.env.ANIMA_API_KEY! });
am.on('email.received', async (msg) => {
if (!msg.subject.toLowerCase().includes('urgent')) return;
if (!msg.from.phone) {
await am.email.send({
threadId: msg.threadId,
to: msg.from.email,
subject: `Re: ${msg.subject}`,
html: '<p>Got it — calling you back. Could you share your phone number?</p>',
});
return;
}
await am.voice.placeCall({
correlationId: msg.correlationId, // threads with the email
to: msg.from.phone,
consentSource: 'customer-initiated',
greeting: `Hi, calling about your "${msg.subject}" email.`,
});
});Why it works: The correlationId from the inbound email threads through to the voice call, so /audit shows them as one chain. consentSource = 'customer-initiated' satisfies the TCPA gate because they emailed us first.
Scheduled outbound (cron + tier check)
Send a weekly digest only if the agent has the email quota.
// Run from your scheduler (Cloud Scheduler, Vercel Cron, etc.)
const am = new Anima({ apiKey: process.env.ANIMA_API_KEY! });
const usage = await am.billing.getUsage();
const remainingEmail = usage.tierIncluded.email - usage.used.email;
if (remainingEmail < 50) {
console.log('Skipping digest — under monthly quota');
return;
}
const subscribers = await loadSubscribers();
for (const sub of subscribers) {
await am.email.send({
to: sub.email,
subject: 'Your weekly digest',
html: render(sub),
// Idempotent retries: same key = same response if network drops
idempotencyKey: `digest-${sub.id}-${weekStart()}`,
});
}Why it works: The Idempotency-Key matches per-subscriber per-week. If the cron retries (network blip, container restart), the digest doesn't double-send. The hard-cap at 90% would email-warn the operator before this script gets close to the cap.
Vault credential rotation
Rotate a stored API key when the upstream service notifies via webhook.
// Webhook handler — verifies signature elsewhere
async function rotateCredential(serviceId: string, newSecret: string) {
// Read the current credential first (audit trail)
const current = await am.vault.read({ serviceId, agentId: AGENT_ID });
// Update with the new secret
await am.vault.update({
credentialId: current.id,
fields: { api_key: newSecret },
});
// The vault stores ciphertext; the agent gets plaintext on next read.
// /audit?correlation_id=... will show one chain:
// webhook.received → vault.credential.read → vault.credential.write
}Why it works: Vault writes are server-side encrypted before storage. The audit chain captures the rotation as one workflow. If the upstream rotation fails, you can replay the webhook with the same Idempotency-Key without double-rotating.
Cross-channel audit query
Customer support asks: 'what did agent X do for tenant Y today?'
// Fetch every event for an agent in the last 24h, grouped by correlation
const events = await am.audit.list({
agentId: 'agent_8x1',
since: new Date(Date.now() - 24 * 60 * 60 * 1000),
});
const chains = new Map<string, typeof events>();
for (const evt of events) {
const id = evt.correlationId;
if (!chains.has(id)) chains.set(id, []);
chains.get(id)!.push(evt);
}
// chains is now a Map<workflow-id, ordered-events>
for (const [id, chain] of chains) {
console.log(`${id}: ${chain.length} events across ${new Set(chain.map(e => e.channel)).size} channels`);
}Why it works: Every cross-channel action emits a row to audit_events keyed by correlationId. Grouping by ID gives you the workflow view; grouping by agentId gives you the per-agent view. The dashboard /audit page does the same query with a UI on top.
Hard-cap alert-only for production agent
Voice meter blocks at 100% by default. For a production support agent, alert-only is safer than mid-call hangup.
// Run once via the dashboard or this admin endpoint
await am.billing.updateHardCap({
meters: {
voice: { mode: 'alert-only', capCents: 100000 }, // $1,000/mo
},
});
// Now: 90% triggers email warning. 100% emits a notification event
// but does NOT block the next call. The operator gets paged but
// the customer call goes through.Why it works: Default mode 'block' is right for most agents — better to refuse a marginal call than overspend. alert-only is the escape hatch for agents where a mid-call hangup is worse than the overage. Per-meter, so email can stay 'block' while voice is 'alert-only'.
Have a pattern you'd like to see here? Tell us.
support@useanima.sh