Skip to content

Microsoft response cache ​

The Microsoft plugin holds recent answers from SchulyMicrosoft in memory, so the web app's mail and chats open quickly and use fewer of Microsoft's rate limits. This follows OutNote's frontend and API guide. The plugin is a C# port of the guide's cache, because the plugin owns the sign-in and refresh tokens, and SchulyMicrosoft stays stateless.

Browser (IndexedDB) -> Plugin (memory) -> SchulyMicrosoft -> Microsoft

It's on for everyone and needs no setting. Nothing goes to disk or into a database, and a restart clears it.

Routes ​

The web app reads through these. They answer with the same JSON and sealed ids as the live routes.

Route under /api/plugins/microsoftAnswers
GET accounts/{id}/cached/bootstrap{mail, folders, chats}: the first screen in one request
GET accounts/{id}/cached/mailThe list, with folder, top, skip, unread
GET accounts/{id}/cached/mail/foldersThe folders
GET accounts/{id}/cached/mail/{messageId}One message
GET accounts/{id}/cached/chatsThe chat list, with top
GET accounts/{id}/cached/chats/{chatId}/messagesA chat's messages, with top
DELETE cachedForgets the signed-in user's entries. The web app calls it before signing out, because plugins get no sign-out event.

Each answer has an ETag, Cache-Control: private, no-cache, and Age. A matching If-None-Match gets a 304.

Every other route stays live. That includes the live routes these mirror, which is what the mobile app, Helply, MCP, and connected AI apps call. These routes answer 404 to an MCP token.

How old an answer can be ​

AgeWhat happens
Under FreshAnswers from memory
Under StaleAnswers from memory, and refreshes in the background
OlderWaits for Microsoft. If Microsoft is throttling or failing, answers the old result
Past Stale plus KeepOldSecondsGone, including as a fallback

A message that was deleted, or a sign-in that expired, shows as such instead of as old data.

Writes always go to Microsoft first. Then the plugin forgets what they could have changed.

Settings ​

Set these under MicrosoftApi:Cache in the plugin's configuration. Times are in seconds.

Times (Times:<name>:Fresh, Times:<name>:Stale)FreshStale
Bootstrap5600
Lists: mail and chat lists30600
ChatMessages: an open chat10600
Message: one mail3003600
Folders30086400
SettingDefaultWhat it does
KeepOldSeconds3600How long past Stale an answer stays as a fallback
MaxEntries5000Most entries, for all users. The least recently used goes first
MaxMegabytes256Most memory for all answers
CallsPerMinute120Calls to SchulyMicrosoft per user through these routes. Past it, the user gets what's held, or a 429 with Retry-After

What's held, and for how long ​

Entries hold real mail and chats, unencrypted in the server's memory, keyed by Schuly user and linked account.

DataLongest time in memory, with the default settings
Mail and chat lists, a chat's messages70 minutes
One mail, with its body2 hours
Folder names and counts25 hours

An entry goes sooner when the user signs out, unlinks the account, or deletes their Schuly account. An account unlinked through DELETE /api/me/linked-accounts/{id}, which doesn't reach the plugin, is answered 404 at once and leaves memory within 5 minutes. Someone whose session just expires can't call the sign-out route, so their entries stay until these times run out.

The cache lives in one process, so it assumes one server, like the rest of the host's plugin runtime. With several servers, each keeps its own copy and a write forgets only that server's copy.