---
title: "Sealed notes: how-to"
description: "Six tasks with sealed notes. Publish your lock, put \"write to me\" on your site, read notes offline with age, give an agent an inbox, seal to several people, check a signature."
type: how-to
audience: both
verified: 2026-11-08
canonical: https://one.ie/docs/sealed-notes-how-to
---
Recipes for people who know what a lock and a sealed note are. For the model, the
HTTP doors and the formats, read [Sealed notes](/docs/sealed-notes).

## Publish your lock

Publishing files your lock under your name, signed by your Sui key, so a writer can
check it is yours instead of trusting the link.

**In the browser**

1. Open [/keys](/keys) and open your key.
2. Under **Your lock**, press **Publish my lock**.
3. The line turns to "Published: anyone can check this lock belongs to *you*."

**From the CLI**

```bash
one login                                   # a person's login key
one key publish-lock --key me.json          # signs for your login's name
one key lock-of <your-name>                 # anyone can now check it
```

Add `--slug <name>` if the CLI cannot tell your name from the environment, and
`--derivation v1` if your account registered the older Sui address. A
`wrong_signer` refusal prints both addresses so you can see which one.

**Verify:** `curl -s https://one.ie/api/lock/<your-name>` answers
`"published":true` with your `age1…`.

## Put "write to me" on your site

1. On [/keys](/keys), press **Share link**. It copies
   `https://one.ie/write#to=<your age1…>&from=<your name>&ws=<your workspace>`.
2. Put it on your site as a plain link:

   ```html
   <a href="https://one.ie/write#to=age1…&from=Tony&ws=tony">Write to me privately</a>
   ```

3. For print or a shop counter, use the QR code for the same link on [/keys](/keys).

The writer seals the note in their own browser on one.ie and it lands in your
**Sealed notes**. Your site sends nothing and holds nothing.

Do not post to `/api/write` from your own page's JavaScript. The door sends no CORS
headers, so a visitor's browser cannot call it from another origin. A server of
yours can, with the body in the [HTTP reference](/docs/sealed-notes#post-apiwrite).

**Verify:** open the link in a private window, seal a short note and send it. It
appears in [/messages](/messages) under **Sealed notes**, marked "Someone with your
lock".

## Read notes offline with age

1. Export your key once. On [/keys](/keys) use **Take my key**, or:

   ```bash
   one key identity --key me.json > me.key   # one line, AGE-SECRET-KEY-1…
   chmod 600 me.key
   ```

2. Save the sealed block as a file, `note.age`, from the
   `-----BEGIN AGE ENCRYPTED FILE-----` line to the `-----END …` line.
3. Open it:

   ```bash
   age -d -i me.key note.age
   ```

Getting the block out of ONE: the inbox has no "copy the sealed block" button yet.
The block you sent is in your hands already (`one key seal` prints it, and `/write`
offers **Copy the sealed note**). For notes you received, the thread export
`GET /api/threads/<threadId>/export?format=json`, signed in as the workspace owner,
returns the stored messages (read from code, not yet run against a sealed thread).

`me.key` opens every note ever sealed to your lock. Keep it off shared disks, out of
chat and out of shell history.

## Give an agent an inbox

The agent's key is its own key file, made on its own machine.

1. **Make the key** (on the agent's machine):

   ```bash
   one key new --out agent.json --yes-i-accept-plaintext-file
   one key lock --key agent.json             # the agent's age1…
   ```

2. **Prove its Sui address** so the lock can be published under its name. Run as
   the agent, with its own API key:

   ```bash
   one wallet register --kind agent --from agent.json --workspace <ws> --actor <agent-id> --yes
   ```

   The keys in `agent.json` sign a server challenge locally; only addresses and
   signatures are sent.
3. **Publish, as the agent itself** (its own API key, never yours):

   ```bash
   one key publish-lock --key agent.json --agent <agent-id> --workspace <ws>
   ```

   A person, the owner or another agent is refused `not_the_agent`. A wallet row
   with no proven address answers `no_sui_address`.
4. **Senders write by name:**

   ```bash
   echo "the staging token is …" | one write <ws>/<agent-id>
   ```

   The CLI looks up `<ws>/<agent-id>`'s lock, checks it, seals and posts. The note
   lands in the **workspace's** Sealed notes. That works only when `<ws>` is a real
   workspace with an owner. `--workspace` defaults to the agent's own id (a
   self-registered agent), and for that shape `/api/write` finds no owner and
   answers `unknown_workspace` (read from code, not probed).
5. **The agent opens it** wherever the block reaches it (a file, a task note, stdin):

   ```bash
   one key open --key agent.json note.age --json
   # {"ok":true,"text":"the staging token is …"}
   ```

No CLI command reads the Sealed notes thread yet, so the block has to be handed to
the agent by some channel that carries text. Without a published name, skip steps 2
and 3: give senders the `age1…` directly and they use `one write <ws> --to age1…`.

**Verify:** `one key lock-of <ws>/<agent-id>` prints the agent's lock and
"checked".

## Seal to several people

Every reader gets their own copy of the file key inside one block; anyone listed
opens it.

```bash
one key seal note.txt \
  --to age1…alice \
  --to age1…bob \
  --self --key me.json > note.age          # --self: you can re-read it too
```

Or post it straight to one inbox: `one write <ws> note.txt --to age1…alice --to age1…bob`.

In the `/messages` composer, paste several locks; it adds your own automatically
and says "Sealed for 2 locks and you".

The block caps at 8,000 bytes, so more readers leave less room for the note: about
4,750 bytes at 10 readers, 1,750 at 40 (unsigned). See
[Size caps](/docs/sealed-notes#size-caps).

**Verify:** each reader runs `one key open --key their.json note.age`. A key not on
the list gets `no_identity`.

## Check a note is really signed

**In the browser:** open the note in [/messages](/messages). The line under it is
the check. Only **Signed by *name* · checked** means the signature holds and the
signer is the address *name* has published. Every other line ("not by the account
it names", "has not published a lock", "written to a different lock than yours",
"the signature does not hold") means do not trust the name.

**Offline:** no CLI command checks a signature yet; `one key open` prints the
envelope as text. This script does the same check the inbox does, with the SDK
only. It was run on 2026-11-08 against an envelope the web verifier also accepted,
and refused the same envelope with one word changed.

```ts
// check-signed.ts   usage: one key open --key me.json note.age | bun check-signed.ts
import { createHash } from 'node:crypto'
import { verifyLockRecord, verifySuiPersonalMessage } from '@oneie/sdk/message-lock'

const plain = await new Response(Bun.stdin.stream()).text()
const gap = plain.indexOf('\n\n')
const head = plain.slice(0, gap).split('\n')
const note = plain.slice(gap + 2)
if (head[0] !== 'ONE signed note v1') throw new Error('not a signed note')
const field = (k: string) => head.find((l) => l.startsWith(`${k}: `))?.slice(k.length + 2) ?? ''
const slug = field('slug'), address = field('address'), signature = field('signature')
const readers = head.filter((l) => l.startsWith('to: ')).map((l) => l.slice(4))

// Rebuild the signed text; never trust the envelope's own copy of it.
const hash = createHash('sha256').update(note, 'utf8').digest('hex')
const message = ['ONE signed note v1', slug, ...[...new Set(readers)].sort(), hash].join('\n')
const signer = verifySuiPersonalMessage(message, signature)
if (!signer || signer.toLowerCase() !== address.toLowerCase()) throw new Error('the signature does not hold')

// The signer must be the address the name has published.
const rec = await (await fetch(`https://one.ie/api/lock/${slug}`)).json()
if (!rec.published) { console.log(`signed by ${signer}, but ${slug} has not published a lock`); process.exit(2) }
const lock = verifyLockRecord(slug, rec)
if (!lock.ok) { console.log(`${slug}'s published lock does not check out: ${lock.detail}`); process.exit(2) }
console.log(lock.suiAddress.toLowerCase() === signer.toLowerCase() ? `signed by ${slug} · checked` : `signed, but not by the account ${slug} names`)
```

Also confirm your own lock is one of the `to:` lines. If it is not, the note was
written to someone else and passed on.

The limit, in both places: the signature is checked on your machine, and that the
Sui address belongs to the name is ONE's word, from the account's registered wallet.