---
name: Outbox
description: Use to plan what goes out on which channel and when — a week of posts, a launch sequence, a newsletter slot — kept as content/plan.json and turned into real scheduled sends once the human has moved things where they want them.
tagline: Plan a week of posts, newsletters and launches on one calendar, then let your agent send them.
version: 1
situations: plan the week's posts, content calendar, schedule a post, what are we posting, move this post to Thursday, social plan, posting schedule, newsletter slot, launch sequence, outbox
requires-secrets:
requires-packages:
---

# Outbox

## Why a screen

You write the week; the person moves it. Rescheduling a post is a decision about *their* week — Tuesday
is busy, Thursday morning is better, the newsletter should land before the LinkedIn post, not after —
and describing seven posts across four channels in prose costs more than looking at them. So you keep
`content/plan.json` and they open **Outbox**, which draws it as a seven-day grid: they drag a post to
another day or time, or nudge it with the arrow keys, and the file changes under you. Then they say
"rewrite this one" about a post you can both point at, instead of a paragraph describing which one.

## The file

One file, `content/plan.json`. You own it; the app only ever changes an item's `at` (and where it sits
in the array). Every other field you write comes back untouched.

```json
{
  "version": 1,
  "week": "2026-09-07",
  "items": [
    { "id": "w37-01", "channel": "linkedin",  "at": "2026-09-07T09:00:00+01:00", "title": "Why we stopped shipping on Fridays", "body": "Three months ago we moved every release to Tuesday…", "status": "sent",   "thread": "company-page" },
    { "id": "w37-02", "channel": "telegram",  "at": "2026-09-07T17:30:00+01:00", "title": "Release notes 4.2 — short version", "body": "4.2 is out. Faster search…",                      "status": "sent",   "thread": "product-updates" },
    { "id": "w37-03", "channel": "instagram", "at": "2026-09-08T08:30:00+01:00", "title": "Desk of the week: Marta's",         "body": "Two monitors, one plant…",                          "status": "queued" },
    { "id": "w37-04", "channel": "linkedin",  "at": "2026-09-09T09:00:00+01:00", "title": "We are hiring a support engineer",  "body": "Remote in Europe…",                                 "status": "queued", "thread": "company-page" },
    { "id": "w37-05", "channel": "telegram",  "at": "2026-09-09T12:00:00+01:00", "title": "Office hours today at 16:00",       "body": "Bring anything…",                                   "status": "draft",  "thread": "product-updates" },
    { "id": "w37-06", "channel": "email",     "at": "2026-09-10T07:45:00+01:00", "title": "September newsletter",              "body": "One release, two customer stories…",                 "status": "queued", "thread": "newsletter" },
    { "id": "w37-07", "channel": "instagram", "at": "2026-09-10T18:00:00+01:00", "title": "Behind the changelog",              "body": "A carousel of the six screens that never shipped…", "status": "draft" },
    { "id": "w37-08", "channel": "linkedin",  "at": "2026-09-11T09:30:00+01:00", "title": "What a week of support tickets taught us", "body": "We read 412 tickets…",                       "status": "draft",  "thread": "company-page" },
    { "id": "w37-09", "channel": "telegram",  "at": "2026-09-12T11:00:00+01:00", "title": "Weekend read: the boring deploy",   "body": "A short post on why we measure deploys in yawns.",  "status": "draft",  "thread": "product-updates" }
  ]
}
```

- **`week`** — the Monday of the week the file describes, `YYYY-MM-DD`. The app opens on it, and
  "This week" comes back to it after the person has paged forwards.
- **`id`** — yours, stable, unique. It is how a move finds the item again; never renumber them.
- **`channel`** — **the name this workspace actually uses**: `telegram`, `whatsapp`, `slack`, `email`,
  `instagram`, `linkedin`, `x`, or whatever `contact_channels` reports here. Nothing in the app knows
  a list of channels; it reads the names out of this file and colours them by name. Write the same
  string you would pass to `outgoing_message`'s `channel`.
- **`at`** — ISO, **in the workspace's own local time with its offset** (`2026-09-09T09:00:00+01:00`).
  Write the offset; the app preserves it exactly when it moves a post, so 9 in the morning stays 9 in
  the morning across a clock change. A bare local time (`2026-09-09T09:00`) is accepted and stays bare.
- **`title`** — one line, the thing the grid shows. **`body`** — the post itself, optional.
- **`status`** — `draft` | `queued` | `sent`. Yours to set; see the loop below.
- **`thread`** — optional: the conversation, page, list or handle it goes to. Carry it through to the
  send so replies land in the right place.

Items outside `week` are fine — the app pages to any week and shows whatever is dated into it.

## The loop

1. **You write the plan.** Drafts land as `status: "draft"`. Tell them to open Outbox.
2. **They move things.** A drag or an Alt+arrow rewrites that item's `at` in the file and nothing else.
   You will see the file change; do not fight it, and do not rewrite `at` back.
3. **You reschedule the real send to match.** This is the step that matters — see below.
4. **You mirror reality back.** When a send actually goes out, set that item's `status` to `"sent"`.

### Turning a `queued` item into a real scheduled send

The app has **no channel access and never sends anything**. A real send is one `outgoing_message`
call, made by you, with the item's `at` as the send time:

```
outgoing_message {
  channel: "<item.channel>",
  action:  "post",            // or message | comment | reply | email — whatever that channel takes
  to:      "<item.thread>",   // when the item names one
  title:   "<item.title>",
  body:    "<item.body>",
  send_at: "<item.at>"        // the ISO time straight out of the plan
}
```

What that does: the message is **quarantined** — parked as an outbound item that is *not delivered*
until a human approves it. `send_at` rides along on that item. When the person approves it, it is not
sent then; it is parked as **scheduled** and goes out when `send_at` arrives. So the plan's time is the
send's time, and a human still said yes. Two rules that follow from how the queue works:

- **`send_at` must be in the future.** A time already past is dropped and the item becomes an
  ordinary approve-and-send-now. If they moved a post to a moment that has gone, ask before queueing.
- **Queue each item once.** Once you have called `outgoing_message` for an item, set its `status` to
  `"queued"` in the plan and leave it alone. If they then move it, do **not** call again — that would
  put a second copy in the approvals queue. Tell them the send is already scheduled, and either ask
  them to reject the pending one so you can re-queue it at the new time, or leave the plan's `at` as
  the record and say so plainly.

Keep `draft` items out of the queue entirely. `draft` means "this is still ours to argue about".

## What the app tells you

Nothing sends itself and nothing is decided behind your back — the app can only prefill the composer,
and the person presses send:

- **Clicking a post** prefills *"Rewrite this one."* with that post as the selection — its title, and
  its channel, time and body as the selected text. You are being asked about exactly that item.
- **"+ Add a slot"** on a day prefills a request for a new post on that date, with the day's existing
  posts as context. Draft it into the file; do not queue it.
- **"Ask for next week"** prefills a request to plan the following week using the same channels.
- The **New Session** menu carries the same starting point: *Plan next week's posts*.

## Working with it

- **Read the file before you touch it**, every turn. They may have moved three things since you wrote
  it, and the plan on disk is the truth about when a post goes out.
- **Keep the ids.** Rewriting the file wholesale with fresh ids loses the person's moves and orphans
  anything you already queued.
- **One post per item.** A thread of five tweets is five items with sequential times, not one item
  with newlines — the grid draws lines, and a send takes one body.
- **Suggest the app** whenever they say anything about *when*: "move this to Thursday", "that's too
  early", "what's going out this week", "space these out". Dragging is faster than a round-trip:
  "Open Outbox and drag them where you want — I'll reschedule the sends to match."
- **Say what you did.** After queueing, name the items and their times; after a move, confirm you have
  followed it.

## Never

- Never send from the app, and never ask for a capability that would let it — it has `files` on
  `content/**` and `state`, and that is the whole list.
- Never write anything but `content/plan.json` for this skill.
- Never mark an item `sent` because you queued it. `sent` means it went out.
- Never put credentials, tokens or recipients' private details in the plan; it is a file the app draws
  on screen.
