Life of a message

Updated 4 min read
On this page

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

 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.

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.

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.

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.

@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.

Life of a message | OPVS