Inbox Notifications
Your deployed site can send messages straight to your YOLO Inbox — the global mailbox you read at yolo.studio/inbox . Use it for things your running app wants to tell you about: a contact-form submission, a payment, a job finishing, or a runtime error.
It works with zero setup. Every deploy automatically gives your Worker two bindings:
| Binding | What it is |
|---|---|
env.YOLO_INBOX_URL | The endpoint to POST to. |
env.YOLO_INBOX_TOKEN | A per-project credential (a write-only secret). It only ever delivers to your inbox — it can’t do anything else. |
You don’t create or manage these — they’re injected at ship time and rotated automatically.
A drop-in helper
Copy this small helper into your Worker once — it wraps the two bindings and the right field names so the rest of your code just calls sendInbox(env, { … }):
// inbox.js — send a message to your YOLO Inbox
export async function sendInbox(env, message) {
const res = await fetch(env.YOLO_INBOX_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.YOLO_INBOX_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(message), // { subject, body, category?, links?, metadata?, threadKey? }
});
if (!res.ok) throw new Error(`inbox send failed: ${res.status}`);
return res.json(); // { messageId }
}Send a message
Call the helper from anywhere in your Worker. Note the message field is body (markdown) — not bodyMarkdown:
import { sendInbox } from './inbox.js';
export default {
async fetch(request, env, ctx) {
// … your app …
// Don't block the response on delivery — let it finish in the background.
ctx.waitUntil(sendInbox(env, {
subject: 'New contact-form submission',
body: '**From:** jane@example.com\n\nLoved the site — can we talk?',
category: 'alert',
}));
return new Response('ok');
},
};The message shows up in your inbox immediately, with your site named as the sender and an Open site link back to it.
Fields
| Field | Required | Notes |
|---|---|---|
subject | yes | Plain text, ≤ 200 chars. |
body | yes | Markdown, ≤ 64 KiB. Rendered safely in the inbox. (bodyMarkdown is also accepted as an alias.) |
category | no | report, update, alert, or fyi (default update). |
threadKey | no | An arbitrary string that groups related messages into one thread. Messages with the same threadKey stack chronologically in the inbox as a single row — useful for start/progress/done sequences. Omit to send a standalone message. Grouping only: it does not make a thread answerable (see Conversations). |
links | no | Up to 20 external reference links — each { kind: "external", label, target } with an https:// target. |
metadata | no | Arbitrary JSON, ≤ 8 KiB. |
conversational | no | true marks a message that expects an answer — see Conversations. It names nobody: who the answer would come from is derived from the project. Default false. |
Threading messages
Pass the same threadKey across related messages to group them into a conversation:
// message 1 — job started
await sendInbox(env, { subject: 'Export started', body: 'Running...', category: 'update', threadKey: 'export-job-42' });
// message 2 — job done (same threadKey → same thread)
await sendInbox(env, { subject: 'Export complete', body: '**2,431 rows** exported to `data.csv`.', category: 'report', threadKey: 'export-job-42' });In the inbox, these appear as a single row with a message-count chip. Opening the thread shows the messages in chronological order. Archiving applies to the whole thread at once.
Conversations: when a message can be answered
threadKey groups your notifications; it does not make them answerable. A
message your site sends is a one-way notification, and there is no reply path
back into your Worker. Nothing you can put in the request body changes that: a
site names only itself, never a recipient and never a conversation.
There is one case where a site’s message does join a two-way conversation, and it is routed entirely server-side:
- If the project has an active managed customer (someone you gave a project portal to), an enquiry your site sends is also delivered into that customer’s portal message thread, as a “Website enquiry”. Your inbox notification is unchanged; this is an extra delivery, routed server-side.
- That thread IS a two-way conversation: the customer can write into it from their portal, and you answer them — from Customers on the project, or from the inbox conversation once one is there.
- Ask for the thread when it’s warranted. Most sends are one-off, so an
enquiry is an ordinary notification by default. Pass
conversational: trueon the ones that expect an answer and the message opens as a conversation in your inbox straight away, beside anything that customer has written. (With no managed customer on the project — or more than one, where a reply would be ambiguous — it stays an ordinary notification.) - With no managed customer on the project, nothing changes: the message is an ordinary inbox notification and there is nowhere to answer it.
So the way to make a site’s enquiries answerable is to give the person who
should answer them a managed-customer portal on that project. You do not send
anything different — the same subject/body post is enough, and the routing
happens for you.
What a site still cannot do: address another user, name a conversation, or open a two-way channel with its own visitors. A visitor writing to your site is not a conversation the inbox can carry — their message reaches you as a notification, and you answer them however your site already does (email, your own UI).
Good to know
- Owner-only. The token is bound to your account, so a site can only message its own owner — never another user.
- Markdown is sanitized. No raw HTML, no remote images, links open in a new tab — so a message body is safe to render even if it’s built from untrusted input (e.g. form fields). Escape or trust your inputs accordingly.
- Rate-limited per project and per recipient, so a runaway loop can’t flood your inbox.
- Revocable. Suspend or delete the project and delivery stops at once.
- The token is long-lived and rotated for you; you never have to refresh it.
Reading your inbox
The inbox lives at yolo.studio/inbox — open it from Inbox in the sidebar.
Filtering. The toolbar above the thread list lets you narrow by:
- Text search — searches subject and body (literal substring).
- Category — filter to
report,update,alert, orfyi. - Workspace — messages from a specific workspace.
- Unread only — hide everything you’ve already opened.
Archived view. A toggle switches between your active inbox and your archive. Archive a thread (whole conversation) to tuck it away without deleting it — it stays searchable.
Copy as Markdown. Each message and each thread has a “Copy as Markdown” action that puts the subject + body on your clipboard, formatted for pasting into a document or passing to an agent.
Inline artifact previews. If a message was sent from an agent’s workspace and carries a workspace artifact key in its metadata, the inbox expands a preview of that artifact inline in the reading pane — no need to navigate to the workspace. The preview loads lazily the first time you expand it.