惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
Vercel News
Vercel News
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
I
InfoQ
小众软件
小众软件
Recent Announcements
Recent Announcements
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
大猫的无限游戏
大猫的无限游戏
美团技术团队
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
V
V2EX
J
Java Code Geeks
有赞技术团队
有赞技术团队
博客园 - 聂微东
B
Blog RSS Feed
博客园 - 司徒正美

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Rate-Limit Your Own Agent Before Someone Else Does
Qasim Muhammad · 2026-06-17 · via DEV Community

0.1%. That's the complaint rate that puts an email-sending account under review on Nylas Agent Accounts — one spam report per thousand sends. At 0.5%, sending is paused outright. For bounces, the review threshold is 5% and the pause kicks in at 10%. These aren't suggestions; they're enforced by the platform, and a pause doesn't clear itself on a timer — you have to contact support with evidence of a fix.

Here's my position: those numbers shouldn't be your rate limit. They should be your last line of defense, behind a stricter limit you set yourself. Rate-limit your own agent before someone else does it for you.

An LLM loop has no natural stopping point

Traditional email code sends when a human or a cron job tells it to. An autonomous agent sends when a model decides to, and models inside feedback loops make weird decisions. A reply triggers a webhook, the webhook triggers a reply, and a benign bug becomes a thousand sends before lunch. Nothing in the model's reasoning says "this is my 400th message this hour, that seems off." That awareness has to live in infrastructure.

Agent Accounts (in beta) bake the infrastructure in through policies. A policy bundles daily send quotas, storage caps, retention windows, and spam settings, and applies to every account in a workspace. Without one, an account runs at your billing plan's maximums — 200 messages per account per day on the free plan — which is exactly what you don't want for an experiment that might loop. Every limit on a policy is optional; omit one and it defaults to the plan maximum, ask for more than the plan allows and the API rejects it.

Quotas are a statement about expected behavior

The useful mental shift: a self-imposed quota isn't throttling, it's an assertion. "This support agent should never need more than 150 sends a day. If it asks for number 151, something upstream is wrong." That's the same logic as a circuit breaker in a service mesh — you're not limiting capacity, you're encoding an expectation so violations become visible instead of expensive.

Policies let you encode different expectations per agent archetype. A prototype gets a tight quota; a production sales agent gets a higher one. The docs explicitly suggest separate workspaces per archetype, because a triage agent and an outreach agent have completely different send profiles.

Outbound rules go a step further than volume — they constrain direction. A rule with trigger: "outbound" evaluates before the message reaches the provider, and a block action rejects the send with a 403:

curl --request POST \
  --url "https://api.us.nylas.com/v3/rules" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Block outbound to example.net",
    "trigger": "outbound",
    "match": {
      "conditions": [
        { "field": "recipient.domain", "operator": "is", "value": "example.net" }
      ]
    },
    "actions": [{ "type": "block" }]
  }'

The recipient.* fields match any recipient, including BCC and SMTP envelope recipients — so an agent can't smuggle a send past the rule by hiding the address. You can also match outbound.type (compose vs reply) to, say, let an agent reply freely but block it from starting brand-new threads.

Watch the same telemetry the platform watches

The bounce and complaint rates that trigger pauses are computed from events you can subscribe to: message.transactional.delivered, message.transactional.bounced, message.transactional.complaint, and message.transactional.rejected — four webhook triggers that are your only real-time window into those rates. The docs' advice is blunt: wire them up and pause your own outbound logic when bounces or complaints climb. You'll see the problem in your own telemetry before the platform tells you about it, and "we paused ourselves" is a much better incident report than "we got paused."

It also helps to know what's actually being counted. Bounce rate only counts hard bounces — addresses that don't exist — divided by a recent representative send volume; soft bounces from full mailboxes or greylisting don't touch it, and healthy is under 2%. Complaint rate counts recipients clicking Mark this email as spam or dragging your mail to junk, measured only across domains that send complaint feedback. That's why 0.1% is so easy to hit at low volume: a handful of annoyed recipients in a 2,000-send week puts the account under review.

The error responses are worth knowing too. A reputation pause surfaces as a 400 on send; a per-account or per-domain rate limit returns 429 (back off and retry); an abuse restriction returns 403 with send blocked by abuse restriction. That last one can be scoped to a single sender address, a domain and its subdomains, a grant, or the entire application — and an application-level restriction stops every Agent Account under the app, not just the one that misbehaved. If your agent treats all send failures as retryable, it will hammer a paused account and learn nothing.

Circuit breakers need receipts

Two details make the rule layer trustworthy enough to bet on. First, evaluation fails closed: if a block rule can't be evaluated because of a transient infrastructure error — say, a list lookup failure during in_list matching — Nylas blocks the message rather than letting it through. The failure is surfaced as retryable: an API send returns 503 instead of 403, and inbound SMTP answers with a 451 tempfail so the sending server retries instead of bouncing. A safety mechanism that silently disables itself under load isn't a safety mechanism.

Second, every evaluation writes an audit record. GET /v3/grants/{grant_id}/rule-evaluations lists, most recent first, which rules matched, what actions were applied, and the normalized sender and recipient data that was considered. When a block happened because evaluation errored rather than matched, the record carries blocked_by_evaluation_error: true. So when your agent's send comes back 403 at 2 a.m., "why was this blocked?" is one API call, not an archaeology project. A circuit breaker without observability is just a mystery outage.

The counterargument: limits break legitimate bursts

The honest objection is that real workloads spike. A support agent during an outage might legitimately need 5x its normal volume, and a hard quota turns your safety net into an availability incident. That's true — if the quota is a dead end.

So don't make it a dead end. Make hitting the quota an escalation path: alert a human, queue the overflow, require an approval to raise the cap. The failure mode of a too-tight quota is a Slack ping and an hour of delayed email. The failure mode of no quota is a 10% bounce rate, a platform-level pause that requires a support ticket to lift, and a sender reputation you rebuild over weeks. Those aren't symmetric risks.

There's also a softer dial worth knowing: policies expose spam_sensitivity from 0.1 to 5.0 for inbound filtering. Inbound hygiene matters for outbound health, because agents that reply to junk generate complaints.

Concrete next step: before your agent's next deploy, create one policy with a daily quota at roughly 2x the agent's observed peak, attach it to the workspace, and subscribe to the four message.transactional.* triggers. Then deliberately make your agent hit the quota in staging and check that your alerting fires. If it doesn't, you've found the gap while it's still cheap.