QMail Tell — Group 6, Code 71
Notifies a recipient’s beacon RAIDA that mail is waiting. The sender uploads stripes to many RAIDAs but sends one tell per recipient to that recipient’s pre-agreed beacon RAIDA.
Quick reference
| Command Group | 6 |
| Command Code | 71 |
| Server function | cmd_qmail_tell2 in cmd_qmail.c |
| Wire structures | qmail_v2_tell_req_t, qmail_v2_address_entry_t, qmail_v2_file_header_t |
| Body layout | Preamble (48) + Routing (48) + Addresses (N×32) + Blob (64 + M×32) + Terminator (2) |
| Encryption | Required — AES-128 keyed by sender’s coin AN |
| Side effect | One .tell2 file written per recipient under QMAIL_MAILBOX_ROOT/{denom}/{sn}/inbox/ (atomic publish via temp+rename) |
Purpose
After uploading the stripes, the sender calls tell on each recipient’s beacon RAIDA. The tell carries a pass-through blob: a 64-byte file header plus M 32-byte server-location entries that tell the recipient where the stripes live. The beacon writes the blob to the recipient’s inbox directory; when the recipient’s ping or peek arrives, the beacon reads back the blob and returns it.
The sender pays a per-recipient inbox fee by attaching a funded locker key to each address entry. The beacon verifies the locker holds at least the recipient’s configured fee before accepting the delivery.
Request body
Preamble (48)
Routing header (48) qmail_v2_tell_req_t
N × Address entry (32 each) qmail_v2_address_entry_t
File header (64) qmail_v2_file_header_t (start of blob)
M × Server location (32 each) pass-through; format defined by client
Terminator (2) 3E 3E
Server validates: body_size == 48 + 48 + N×32 + 64 + M×32 + 2.
Routing header (48 bytes, offsets 48–95)
Layout matches qmail_v2_tell_req_t:
| Offset | Size | Field | Description |
|---|---|---|---|
| 48–63 | 16 | email_id | The 16-byte GUID for this email. Same value used as File GUID in the upload calls and used by the recipient when they later call download. |
| 64–67 | 4 | total_file_size | Big-endian total size in bytes of the email’s primary file. Currently logged but not enforced server-side. |
| 68–71 | 4 | reserved_1 | Available for future use. Set to zero. |
| 72–75 | 4 | client_timestamp | Big-endian Unix timestamp (seconds) at the moment of build. Server rejects with ERROR_INVALID_PARAMETER if it differs from server time by more than 60 seconds. |
| 76 | 1 | tell_type | Must be 0 today. Server rejects other values. |
| 77 | 1 | address_count (N) | Number of recipients in this tell. Must be > 0. |
| 78 | 1 | server_count (M) | Number of 32-byte server-location entries that follow the file header. Must equal stripe_count in the file header. |
| 79–94 | 16 | beacon_payment_locker | Locker key paying the beacon’s tell-handling fee. ASCII, null-padded. |
| 95 | 1 | reserved_2 | Available for future use. Set to zero. |
Address entries (N × 32 bytes)
Layout per recipient matches qmail_v2_address_entry_t. The server iterates the N entries and writes one .tell2 file to each recipient’s inbox.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | type | Recipient kind: 0=To, 1=CC, 2=BCC, 3=Mass. |
| 1–2 | 2 | coin_id | Network ID. Fixed 00 06. |
| 3 | 1 | denomination | Recipient’s mailbox denomination (signed; valid range −8…+6). |
| 4–7 | 4 | serial_number | Recipient’s mailbox SN (big-endian). |
| 8–23 | 16 | locker_payment_key | Funded locker covering this recipient’s inbox fee. ASCII, null-padded. Must be non-empty (all-zero fails delivery for this recipient). |
| 24–31 | 8 | reserved | Available for future use, per-recipient. Set to zero. |
Notification blob (64 + M × 32 bytes)
The blob is the pass-through payload — the beacon stores it verbatim and the recipient reads it back through ping/peek. The first 64 bytes are the file header (qmail_v2_file_header_t), followed by M 32-byte server-location entries.
File header (64 bytes)
| Offset | Size | Field | Description |
|---|---|---|---|
| 0–15 | 16 | email_id | Same GUID as the routing header. Server validates they match. |
| 16–17 | 2 | sender_coin_id | Network ID. Must be 00 06. |
| 18 | 1 | sender_denomination | Must equal preamble denomination. |
| 19–22 | 4 | sender_serial_number | Big-endian. Must equal preamble SN. |
| 23 | 1 | sender_device_id | Per-sender device tag. Logged, not enforced. |
| 24–27 | 4 | client_timestamp | Big-endian. Independent of the routing-header timestamp; both are checked. |
| 28 | 1 | tell_type | Must be 0. |
| 29 | 1 | stripe_count | Number of server-location entries that follow. Must be 1…32 and equal the routing-header server_count. |
| 30–45 | 16 | locker_code | Standardized 16-byte locker code passed through to the recipient. |
| 46–49 | 4 | total_file_size | Big-endian total file size. |
| 50 | 1 | version | Set to 0x02. |
| 51–63 | 13 | reserved | Largest contiguous reserved region in the QMail protocol. Pass-through — whatever the sender writes here is what the recipient sees. Natural place for future per-email metadata. |
Server location entries (32 bytes each)
The 32-byte format is partially structured (the recipient’s ping parser reads the first three bytes), partially opaque. The rest_core sender writes:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 1 | stripe_index | Position 0…M−1 within the file’s stripe set. |
| 1 | 1 | stripe_type | Currently encoded as 0=data, 1=parity by the sender. The recipient’s receive-side code (qmail_receive_email) ALSO interprets values 10..14 here as “stripe belongs to attachment N” for placeholder creation. Today no sender writes 10…14. |
| 2 | 1 | server_id | RAIDA index (0…24) holding this stripe. |
| 3–19 | 17 | opaque | Currently zero-filled by the rest_core sender. Available bytes inside the server-location entry. |
| 20–25 | 6 | IPv4-mapped address | Bytes 20–21 are FF FF; bytes 22–25 hold the IPv4 octets of the storage RAIDA. |
| 26–27 | 2 | port | Big-endian TCP port of the storage RAIDA. |
| 28–31 | 4 | trailing | Currently zero-filled. |
Response
No body. Status only.
Status codes
| Decimal | Hex | Symbol | Meaning |
|---|---|---|---|
| 250 | 0xFA | STATUS_SUCCESS | At least one recipient inbox received the .tell2 file. |
| 8 | 0x08 | ERROR_COIN_NOT_FOUND | Sender’s (denom, SN) is not loaded on this RAIDA. |
| 16 | 0x10 | ERROR_INVALID_PACKET_LENGTH | Body too short, missing terminator, address_count or server_count = 0, or actual size doesn’t match the calculated size. |
| 18 | 0x12 | ERROR_WRONG_RAIDA | Zero deliveries succeeded — every recipient was rejected (bad denom, fee unpaid, filesystem error, empty locker key). |
| 40 | 0x28 | ERROR_INVALID_SN_OR_DENOMINATION | Sender denomination outside −8…+6. |
| 198 | 0xC6 | ERROR_INVALID_PARAMETER | Validation failed on the blob: bad sender_coin_id, denomination/SN mismatch with preamble, timestamp out of range, tell_type != 0, stripe_count out of 1..32, or stripe_count != server_count. |
| 200 | 0xC8 | ERROR_INVALID_AN | Preamble AN does not match server’s stored AN. |
Server-side delivery
For each accepted recipient the server writes:
Path: /opt/raidax/QMail_Data/mailboxes/{denom_hex}/{sn}/inbox/00000000{GUID_hex}.tell2
Body: pass-through blob (64-byte file header + M×32 server entries)
followed by a per-recipient footer:
byte 0 tag = 0x50
byte 1 length = 16
bytes 2-17 receiver_locker = sender's locker_payment_key for this recipient
Writes use atomic publish: open a hidden tempfile (.tmp.PID.TIME.SEQ.GUID.tell2) with O_CREAT|O_EXCL, write, fsync, and rename() into the final .tell2 path. The recipient’s long-poll inotify watch fires on IN_MOVED_TO only after the rename, guaranteeing the watcher never sees a partial file. Any .tmp.* orphan from a crashed sender is ignored by the directory scanner because the dotfile prefix.
After delivery, when the recipient’s ping or peek reads the file into the response, the server remove()s the file. Tells are one-shot; once read they are gone.
Common mistakes
Skew between client clock and server clock
The 60-second timestamp tolerance is checked twice (routing header + file header). If the sending machine’s clock is wrong by more than 60 seconds, every tell returns ERROR_INVALID_PARAMETER. NTP-sync the sender.
stripe_count vs server_count
The file header’s stripe_count and the routing header’s server_count describe the same M number from different angles. They must be equal — the server compares them and rejects on mismatch. stripe_count is also bounded by 1–32; sending 0 or 33+ is rejected.
Sender identity lives in two places
The preamble has (denom, SN), and the file header has (denom, SN). Both are validated and must match. If you change the preamble for any reason (per-RAIDA AN swapping during parallel send), make sure the blob still uses the original sender identity, not whatever you just wrote into the preamble.
One tell per beacon, not per recipient’s storage
tell is sent to the recipient’s beacon RAIDA only — not to the storage RAIDAs that hold the stripes. Sending tell to a storage RAIDA wastes a packet; it’ll be accepted but no recipient will ever poll that RAIDA for the notification.
Points of confusion
- The server doesn’t verify file existence. tell just writes the notification blob to the recipient’s inbox; it does not contact the storage RAIDAs to confirm the stripes are actually there. If you call tell before upload finishes, the recipient will be told about a file they cannot download.
- Inbox-fee verification is permissive on lookup failure. If the beacon’s call to rest_core for the recipient’s inbox fee fails, fee is treated as 0 and the locker check passes. Recipients who haven’t configured a fee in rest_core are effectively free to mail.
stripe_typeat byte 1 of each server-location entry is doubly-claimed. The current sender writes0/1for data/parity. The recipient’s receive code looks for10..14on this byte to identify attachment stripes — logic that was added in anticipation of a sender that emits per-(server, file) entries with the file’s file_type. Until the sender is updated, attachment stripes are uploaded but never announced to the recipient.- Atomic-publish is recent. Older raidax builds wrote the
.tell2file in place. The current build uses temp+rename so the inotify watcher never sees a partial file. Senders see no behavior change; recipients see a smaller race window. - Multiple reserved regions. Three structures in this command have reserved bytes:
reserved_1[4]+reserved_2[1]in the routing header,reserved[8]per recipient, andreserved[13]in the file header. The 13-byte file-header region is the most useful for protocol extension because it’s pass-through — whatever the sender writes there is delivered verbatim to the recipient.