Use cases · Published 2026-09-29 · AgentiSend team
Alerts from a monitoring agent
A monitoring agent pages a person about a check you already run. The same alert text to the same address is held when it repeats.
A monitoring agent emails a person on your team when a check you already run changes state. The recipient asked to get those alerts, and the From address is on your verified domain.
The ceiling
The executed file sets this PATCH /limits/keys/{id} body. A key can only lower a limit, and the period stays the monthly one a new key already has:
{
"budget_per_period": 50,
"rate_ceiling_per_minute": 10
}The default ceiling is 600 a minute. Ten is the cap for this key. Fifty is the recipient count for the period, under 1,000 on Free.
The send
/**
* Give an agent its own key, its own budget, and a guard that stops it.
*
* The three calls below are the whole control plane:
*
* POST /api-keys — a key scoped to sending, and nothing else
* PATCH /limits/keys/:id — a hard spend ceiling for the period
* POST /emails — the agent sends through its own key
*
* The last part of the script is the point. An agent stuck in a retry loop
* sends the same message again and again; after the third near-identical send
* inside the window the API refuses the fourth, holds it for a human to
* approve, and returns `approval_required` with the held action's id and a
* `fix` that says who decides it and where. Nothing was delivered, and the
* agent is told what to do rather than left to guess.
*/
import { AgentiSend, AgentiSendError } from 'agentisend';
function mailFrom(): string {
const from = process.env.MAIL_FROM;
if (!from) throw new Error('Set MAIL_FROM to an address on a domain you have verified.');
return from;
}
export interface Refusal {
code: string;
message: string;
fix: string;
status: number;
/** How many sends went through before the guard refused one. */
sendsBeforeRefusal: number;
}
export interface Report {
apiKeyId: string;
budgetPerPeriod: number;
firstMessageId: string;
refusal: Refusal;
}
export async function run(): Promise<Report> {
// The key you already hold — full access, kept by you, never given to the agent.
const owner = new AgentiSend();
const from = mailFrom();
// 1. A key the agent holds. `sending_access` cannot read your logs, touch
// your domains, or mint further keys.
const key = await owner.apiKeys.create({
name: 'support-triage-agent',
permission: 'sending_access',
});
// 2. A ceiling. The agent cannot spend past it, whatever it decides to do.
// 50 in the key's own window (a rolling 30 days) and 10 a minute. The
// window stays as it is: an API key may only lower a limit, and 50 a
// day would be up to 1,500 in 30 days, more than a new Free key's 1,000.
const limit = await owner.limits.update(key.id, {
budget_per_period: 50,
rate_ceiling_per_minute: 10,
});
// 3. The agent, holding only its own key.
const agent = new AgentiSend(key.token);
const first = await agent.emails.send({
from,
to: 'customer@example.com',
subject: 'Ticket 4182 — we are looking into it',
text: 'Someone from support will reply within the hour.',
});
// 4. Now the failure mode this exists for: the agent loops. Same recipient,
// same body, over and over. The guard refuses before the fourth copy
// reaches anyone.
let sends = 1;
for (let attempt = 0; attempt < 10; attempt += 1) {
try {
await agent.emails.send({
from,
to: 'customer@example.com',
subject: 'Ticket 4182 — we are looking into it',
text: 'Someone from support will reply within the hour.',
});
sends += 1;
} catch (err) {
if (err instanceof AgentiSendError && err.code === 'approval_required') {
return {
apiKeyId: key.id,
budgetPerPeriod: limit.budget_per_period ?? 0,
firstMessageId: first.id,
refusal: {
code: err.code,
message: err.message,
fix: err.fix,
status: err.status,
sendsBeforeRefusal: sends,
},
};
}
throw err;
}
}
throw new Error('The loop guard did not refuse a repeated send. Check the key is the agent key.');
}
export function print(report: Report): void {
console.log(`agent key ${report.apiKeyId}`);
console.log(`budget ${report.budgetPerPeriod} emails in 30 days`);
console.log(`first send ${report.firstMessageId}`);
console.log(`sends allowed ${report.refusal.sendsBeforeRefusal}`);
console.log(`refused with ${report.refusal.code} (HTTP ${report.refusal.status})`);
console.log(`message ${report.refusal.message}`);
console.log(`fix ${report.refusal.fix}`);
}
if (process.argv[1]?.endsWith('agent.ts') || process.argv[1]?.endsWith('agent.js')) {
print(await run());
}The file creates a sending key, applies that ceiling, sends once, then repeats the same text until the guard holds it. Point the subject and the text at the alert. The name in the file is a sample; the calls are the ones this job uses.
The refusal
A repeat of the same alert is held. The code and the fix:
{
"error": {
"code": "approval_required",
"message": "Blocked: this agent has sent 3 near-identical emails to customer@example.com within 60 minutes, which matches a retry or script loop; review the agent before sending more.",
"fix": "Do not send it again: a person approves or rejects it in the console under Agents → Approvals, and approving sends it — the message then appears in GET /emails. action_id in this error names the held send; the key that asked cannot approve itself, and a retry waits on the same approval.",
"docs_url": "https://agentisend.com/what-does-approval-required-mean#approval_required",
"retryable": false
}
}A burst past the per-minute ceiling is a different refusal, rate_ceiling_exceeded, and it carries retry_after_seconds.
Honest limit
The same alert to the same address is held when it would be the 4th near-identical send inside 60 minutes. Change the text when the state changed. Do not retry a held alert; a person decides it.