Use cases · Published 2026-09-29 · AgentiSend team
Onboarding sequence run by an agent
An agent sends the first welcome, then later notes on a schedule, to someone who just created an account. There is no delay step.
An agent sends a welcome to someone who just created an account with you, then later notes in that same sequence. Each note has its own send time, and each recipient already took the step that starts the sequence.
The ceiling
PATCH /limits/keys/{id} for the onboarding key:
{
"budget_per_period": 200,
"period": "monthly",
"rate_ceiling_per_minute": 10
}Two hundred is under the Free inclusion of 1,000 a monthly period. Ten a minute is under 600. A key can only lower these. The plan inclusion is still the account cap.
The send
"""FastAPI: one POST route that sends a welcome email.
POST /send {"email": "ada@example.com", "name": "Ada"}
200 {"id": "..."} accepted
400 {"error": "..."} not one address; the API was not called
4xx/5xx {"code": "...", "fix": "..."} the API refused, and says what to do
Environment: AGENTISEND_API_KEY (a key with sending_access), MAIL_FROM (an
address on a domain you have verified), and optionally AGENTISEND_BASE_URL.
Run: uvicorn app:app
"""
from __future__ import annotations
import os
import re
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from agentisend import AgentiSend, AgentiSendError, idempotency_key
agentisend = AgentiSend() # reads AGENTISEND_API_KEY and AGENTISEND_BASE_URL
MAIL_FROM = os.environ["MAIL_FROM"]
# One address, no spaces, a dot in the domain. The API checks properly; this
# only stops a form typo from costing a request.
ADDRESS = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
app = FastAPI()
class Signup(BaseModel):
email: str
name: str = "there"
@app.post("/send")
def send(signup: Signup) -> JSONResponse:
email = signup.email.strip()
if not ADDRESS.match(email):
return JSONResponse({"error": "email must be one address, like you@example.com"}, status_code=400)
try:
sent = agentisend.send_email(
{
"from": MAIL_FROM,
"to": email,
"subject": "Welcome",
"text": f"Hi {signup.name}, your account is ready.",
},
# Derived from the thing being done, never from the moment: a retry
# after a timeout replays the first send instead of mailing twice.
idempotency=idempotency_key("welcome", email),
)
except AgentiSendError as err:
return JSONResponse({"code": err.code, "fix": err.fix}, status_code=err.status)
return JSONResponse({"id": sent["id"]})The welcome starter is Welcome. A later note is a scheduled send: set scheduled_at on that message. The furthest it can sit is 30 days out.
The refusal
The same welcome text to the same address is held when it repeats inside the loop window. 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
}
}Honest limit
An automation step is an email, a webhook, or a split. There is no delay step, so the agent schedules each later message itself. A scheduled welcome is counted against the budget when it is accepted, and cancelling it before it sends gives that count back.