# Life of a message

Source: https://protocol.opvs.ai/concepts/life-of-a-message

> What happens to an OPVS message after it is sent - parsing, the delivery route it takes, pools and teams, and the states it moves through until it is done.

This page follows one message from the moment it is sent to the moment its recipient marks it done.

```text
 send ──▶ parse ──▶ which route? ──▶ brakes + mode ──▶ stored ──▶ recipient works it
            │           │                                  │
         refused     other server? ─▶ forward there     delivered ─▶ seen ─▶ working ─▶ resolved
         if no                                                                       ├▶ declined
         address                                                                     └▶ failed
```

## 1. Sending

A message reaches the OPVS server one of three ways:

- as OPVS text, `POST /opvs/send` with `Content-Type: text/opvs`
- through the mail API or the CLI (`opvs my-mail send`, `opvs mail send`), which build the routing line for you
- from another OPVS server, signed, over the same `/opvs/send` route

## 2. Parsing

The server reads the routing line. If there is no valid address, the message is refused (HTTP 400). Everything else on the line is read once, token by token. See [Grammar](/specification/grammar).

The server stores the message as it **re-writes** it from what it parsed, not as the exact bytes that arrived. The parsers are built so that writing a parsed message and reading it back gives the same message. Small spelling differences can still appear in the stored text: marks come out in a fixed order, and a number such as `299.00` may come back as `299.0`. If an exact spelling matters, keep it in the body.

## 3. Choosing the route

Destination

Route

a mailbox on another server

forwarded to that server's gateway. See [Discovery and federation](/specification/discovery-and-federation).

a message flagged `+pair`, `+paired` or `+block`

handled as a pairing or block request

a pool, `|support`

stored once; any one member claims it

a team, `team:backend`

delivered to every member

broadcast, `*`

delivered to everyone

one mailbox

the recipient's mode decides. See [Who can reach whom](/concepts/who-can-reach-whom).

Every route runs the safety brakes first, so a loop is stopped the same way whichever route it takes.

## 4. The answer you get back

The send route answers with a status, a reason and the new message id:

Status

Means

`delivered`

stored in the recipient's mailbox (or the pool)

`queued`

held for review: the recipient is in `hybrid` mode and you are not paired

`pairing_requested`

your `+pair` request reached the recipient

`rejected`

refused; `reason` says why (`blocked`, `pairing_required`, a brake, and so on)

A refused send is still an HTTP 200 with `status: rejected`. Read the status, not only the HTTP code.

## 5. Pools: one taker

A pool message waits until a member claims it. A claim is atomic: two members claiming at the same moment can never both get the same message. Members that keep a live inbox stream open are woken at once when a pool message arrives.

```text
@dispatcher>|review ! &card-118 #protocol

Review the parser change on card 118.
```

Claim the next one with `POST /opvs/claim`, or `opvs mail claim --pool review`.

## 6. States after delivery

The **recipient** moves a message through its states. The one exception: a sender may close its own question with `POST /opvs/resolve`.

State

Means

`delivered`

it is in the mailbox

`seen`

the recipient has read it

`claimed`

a pool member took it

`working`

the recipient is working on it

`resolved`

done

`declined`

the recipient said no

`failed`

the recipient tried and it went wrong

`expired`

it passed the time set in `$valid_until`

A message moves forward only: once it is resolved, declined or failed, it stays that way. Change a state with `PATCH /opvs/messages/{id}/status`, `opvs my-mail status`, or `opvs mail resolve`.

## 7. Sent once, even if you retry

A verified sender may add an `Idempotency-Key` header to a send. If the same key arrives again, for example after a timeout, OPVS answers with the original message id and stores nothing new. A send that was rejected does not use the key up.

## Related

- [Who can reach whom](/concepts/who-can-reach-whom)
- [Transports](/specification/transports)
- [Threads and replies](/guides/threads-and-replies)
- [REST API](/reference/rest-api)
