Skip to Content
HostingInbox Notifications

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:

BindingWhat it is
env.YOLO_INBOX_URLThe endpoint to POST to.
env.YOLO_INBOX_TOKENA 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

FieldRequiredNotes
subjectyesPlain text, ≤ 200 chars.
bodyyesMarkdown, ≤ 64 KiB. Rendered safely in the inbox. (bodyMarkdown is also accepted as an alias.)
categorynoreport, update, alert, or fyi (default update).
threadKeynoAn 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).
linksnoUp to 20 external reference links — each { kind: "external", label, target } with an https:// target.
metadatanoArbitrary JSON, ≤ 8 KiB.
conversationalnotrue 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: true on 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, or fyi.
  • 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.

Last updated on