> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Notes

> The free capture of the workspace over JMAP: notebooks as the unit of sharing, pages with a lossless rich body and a server-derived plain text, tags and links, the trash, and dictation with a transcription whose provenance the page carries.

Notes are an OxiMail JMAP extension (`urn:oximail:params:jmap:notes`, ADR-144): the place for what is not a task. The rule that separates the two is one sentence: **if it has a "done" state, it is a task; otherwise it is a note.** A note *generates* tasks and keeps the link; a task *points* to the page it came from. The call summary, the idea, the points seen in a meeting used to be filed in a task's description for want of anything else.

## The capability object

```json theme={null}
"urn:oximail:params:jmap:notes": {
  "transcriptionAvailable": false,
  "maxBodyOctets": 1048576
}
```

`transcriptionAvailable` is a **runtime** fact written into the Session per instance: the community binary carries no transcription engine, and an extension may plug one in through the server's seam. `maxBodyOctets` (1 MiB) bounds the rich body of a page.

## Objects and methods

| Object | Methods | Notes |
| - | - | - |
| `Notebook` | `get`, `set`, `changes` | The unit of sharing. `name`, `color`, `sortOrder`, `isDefault` (the notebook minted at the account's first page, "Quick notes", server-managed, with a `localizedName` from the account's locale), `shareWith` and a per-viewer `myRights`. |
| `Note` | `get`, `set`, `query`, `queryChanges`, `changes`, `transcribe` | A page of a notebook. |

A **`Note`** carries `id`, `notebookId` (exactly one, movable), `title` (empty allowed, the client shows the first line), `body`, `bodyText`, `preview` (the first characters of the text, always served), `tags` (free, normalised to lowercase), `links`, `attachments`, `source` (`manual`, `dictation`, `voicemail`, `meet`, a closed vocabulary that says where the page came from and never switches behaviour except at creation), `transcription` and its four provenance fields, `isPinned`, `isTrashed` and `trashedAt`, `createdBy` (meaningful in a shared notebook), `createdAt`, `updatedAt`.

**The body is served only when named.** `body` is the canonical rich document the editor produces (ProseMirror), stored without loss; `bodyText` is the plain text the **server** derives from it, what the index and an assistant read, never written by a client. Both are in the projectable vocabulary and out of the default projection, the `bodyValues` idiom of `Email/get`: a list of two hundred pages must not carry two hundred documents. Ask for them in `properties`.

**Links** point to other workspace objects, `{type, id}` with `type` in `task`, `email`, `contact`, `event`, `file`, `note`; the form is verified at write and the target is not resolved, so a dead link is displayed and never breaks a page. **Attachments** are blobs (`blobId`, `name`, `type`), the dictated audio among them.

`Note/query` filters on `inNotebook`, `hasTag`, `linkedTo`, `text` (answered from the full-text index, which holds the title, the derived text and every attachment transcript, never from a `LIKE` scan), `source`, `hasAttachment` and `isTrashed`; it sorts on `updatedAt`, `createdAt`, `title` and `isPinned`, pinned pages first and most recently updated next by default. A property a client cannot write (`bodyText`, `preview`, the transcription fields, `trashedAt`, `createdBy`, the timestamps) is refused by name on a create or update, never silently dropped; `Note` declares its [write vocabulary](./jmap-v2#the-write-vocabulary) to the dispatcher.

## Sharing

A notebook is shared like a task list or a calendar, with the RFC 9670 `shareWith` map ([sharing](./jmap-sharing)) and a vocabulary of OxiMail's own, since a note has no assignee, no invitation and no privacy axis to map the calendar tiers onto. Three bits, with the two implications a human would assume imposed: `mayReadItems` (read the pages), `mayWriteItems` (create, edit, move, trash, restore and hard-delete the pages, a writer owns the page lifecycle, and requires `mayReadItems`), `mayAdmin` (rename, recolour, modify `shareWith`, destroy the notebook, and requires `mayWriteItems`). A grantee writes through the `shared:<owner>` account and `createdBy` says who wrote each page.

The first share of a notebook opens an account in the grantee's Session, so it moves `sessionState` (RFC 8620 §2), as a share of a mailbox, a calendar, a task list or a Drive node does: the grantee's client reloads the Session and the shared account appears. A notebook share used to change the account list without moving the state, and a client never saw it.

## The trash

Destruction is two-stage (ADR-035). `Note/set destroy` on a live page moves it to the trash (the page answers `Updated` with `isTrashed: true`); `destroy` on a trashed page deletes it for good (`Destroyed`); `Note/set update {isTrashed: true}` trashes as well, and `{isTrashed: false}` restores. The trash sweep destroys what has been in the trash longer than the tenant's horizon, its `notes.trash_retention_days` override or else `[notes] trash_retention_days` (30 by default, floor 1; an override of `0` purges nothing, see [per-tenant horizons](../operator/groupware#per-tenant-horizons)), through the same exit as the Drive's trash, respecting legal holds and announcing what it removed to synchronised clients ([storage](../operator/mailboxes-storage)).

## Dictation and transcription

Dictation is not a method: it is `Note/set create` with `source: "dictation"` and an audio attachment (`type` `audio/*`, refused otherwise). The page is created immediately, `transcription: "pending"`, and after commit the audio is handed to the transcription engine; when the job reports, the body, the plain text and the per-attachment `transcript` (timed segments `{start, end, text}`, so a client jumps the audio to a paragraph and highlights the segment being played) are filled and the page answers `done`. **Without an engine the page is accepted anyway** and answers `transcription: "failed"` with `transcriptionError` naming the reason: honest, never silent, never a refusal of the page.

`Note/transcribe {accountId, id, blobId}` is the other gesture, added later: transcribe a recording **added to a page that already has content**, and **retry** a failed one. An ordinary `Note/set update` (reordering pieces, renaming an attachment) never starts a job by side effect; the client asks. The call verifies the piece is an audio attachment of this page, puts the page to `pending` with that piece's transcript and provenance erased (the other pieces keep theirs), and sends one request for that piece alone in *append* mode: the text is added to the body as it is when the job reports, merged by the store in its own transaction, so an edit committed meanwhile is never lost. The method exists on every instance, never `unknownMethod`; without an engine it fails the same honest way.

**A page says where its transcription happened.** Local engine or third-party provider are both legitimate operator choices, but they do not say the same thing about the audio. Each audio attachment carries the provenance of its own transcription, written by the job on the piece it worked on, on success **and** on failure (the audio left all the same): `transcriptionProvider` (an open vocabulary naming an implementation, `local_whisper`, `openai_compat`, …, displayed and never decided on), `transcriptionEndpointHost` (a host, never a URL that could carry a key; `null` for a local engine), and `transcriptionResidencyTier`, the field a client **branches on**: `local` (the audio never left the instance), `ch`, `eu`, `non_eu`. `null` means unknown, never `local`. The page's own three fields are **derived from its pieces**: two recordings may have been processed at different times by different engines, and the page can claim no more than the least sovereign of them. A sentence that over-claims sovereignty is the defect this design forbids.

The transcription engine is an extension seam; the community binary ships none, which is exactly what `transcriptionAvailable: false` says. Strictness rules (unknown ids in `notFound`, `invalidProperties` over coercion, `stateMismatch` on `ifInState`) are identical to the [mail surface](./jmap-mail).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.