A daily limit on share notices, and a rate limit on the rest

An account may send fifty share notices a day. Adding somebody to a document still works past that — they simply get no email about it. The app's own endpoints are now rate limited the way the public API has always been, so a script in a loop gets a 429 instead of everything it asks for.

Two counters, counting two different things, and it is worth knowing which one you have met. One is about mail leaving our domain; the other is about how fast anybody may ask this application for anything.

Fifty notices a day, and a notice is not access

Adding somebody to a document writes the access. The email telling them is a courtesy on top of it, and the two were always separate here. Past fifty in a day the notice is simply not sent: the person is still added, the document still opens for them, and the response says which addresses were actually written to rather than claiming all of them.

Fifty is far above what sharing a document looks like and far below what a mailing looks like. What is being protected is not the cost of a send — it is the domain. A burst of unwanted mail signed by our SPF and DKIM ends with the sending domain disabled, and the first thing that stops working after that is the confirmation code somebody needs to sign in.

Sixty requests a minute, now on the app too

The public API and the connector have counted calls per caller per minute for as long as they have existed, and answer 429 with a retry-after saying how many seconds until the minute turns. The app's own endpoints were left out of it on the grounds that only our pages call them — true of the pages and false of the endpoints. A session cookie is a credential like any other, and those routes write to the database, send mail and make outbound requests.

They are counted by account where there is one and by address where there is not, so one runaway script cannot spend somebody else's allowance. An AI summary has its own smaller budget, twenty a day, because that call costs money in a way an ordinary one does not.

What it does not do

It does not queue. A request over the limit is refused, with the seconds to wait, and retrying is yours to do. It is also not a precise limiter: a row per caller per minute in Postgres means two calls arriving together can read the same count, which at this size is the right trade against running a second system beside the database.

And a counter that cannot be reached counts as room to spare. A limiter that locks everybody out when its own table is unavailable is worse than the thing it was guarding against.

Related: sharing a document as a link, and converting documents with an API.