QMail Object Delete — Group 6, Code 84
Owner-authorized atomic deletion of a committed object. Publishes a tombstone generation rather than removing history in place, keeping the object ID stable and the operation idempotent.
Quick reference
| Command Group | 6 |
| Command Code | 84 |
| Server function | cmd_qmail_object_delete in qmail_object_transfer.c |
| Wire structures | qmail_object_delete_req_t, qmail_object_delete_resp_t |
| Body layout | Preamble (48) + Common prefix (16) + Delete request (40) = 104 bytes request header |
| Transport | TCP only |
| Encryption | Required — AES-128 |
| Access | Owner only |
| Idempotent | Yes, for the same owner and generation pair |
Purpose
Only the authenticated owner — the identity that created the object, tracked since object_begin — may delete it. A successful delete atomically publishes a tombstone at the requested target generation, making the object unavailable to new object_info and object_get_range requests from that point on, and returns that tombstone generation in the response. Existing range responses already in progress at the moment of deletion may finish normally against the generation they were pinned to.
Deletion does not imply a payment refund — the storage payment made at begin time is not returned. Physical bytes may be reclaimed only after any active readers release the old generation and the configured deletion grace period expires; the tombstone itself is retained so repeated delete calls remain idempotent.
Request body
Preamble (48 bytes, offsets 0–47)
Standard QMail preamble. See QMail Overview — Universal preamble.
Common prefix (16 bytes, offsets 48–63)
See object_begin for the shared common-prefix field table. command_header_length must equal 40 for this command.
Delete request payload (40 bytes, offsets 64–103)
| Offset | Size | Field | Description |
|---|---|---|---|
| 64–79 | 16 | object_id | The stable object identifier. |
| 80 | 1 | file_type | Same encoding as cmd 70/74: 0x00 meta, 0x01 body, 0x0A+ attachments. |
| 81–87 | 7 | reserved | Must be zero. |
| 88–95 | 8 | expected_generation | Unsigned big-endian. Must exactly match the current live generation (compare-and-swap semantics). |
| 96–103 | 8 | target_generation | Unsigned big-endian. Must be strictly greater than expected_generation. Becomes the tombstone's generation number. |
| last 2 | 2 | Terminator | Fixed clear-text terminator. |
Response body
Successful response header length: 64 bytes (16-byte common prefix + 48-byte delete response). No data follows.
| Field | Size | Description |
|---|---|---|
| common_prefix | 16 | Echoes request_id. |
| object_id | 16 | Echo of the requested object_id. |
| file_type | 1 | Echo of the requested file_type. |
| object_state | 1 | 2 = tombstone. |
| reserved | 6 | Zero. |
| tombstone_generation | 8 | The generation just published as the tombstone — equal to the request's target_generation. |
| deleted_at | 8 | Unix seconds when the tombstone was published. |
| reserved | 8 | Zero. |
Status codes
| Code | Symbol | Meaning |
|---|---|---|
| 250 | SUCCESS | Tombstone published; see response body. |
| 202 | ERROR_FILE_NOT_EXIST | No committed object exists at this object_id/file_type, or it is already a tombstone. |
| 218 | ERROR_TCP_REQUIRED | Retry using TCP. |
| 219 | ERROR_UNSUPPORTED_PROTOCOL | Unsupported protocol_version or framing version. |
| 232 | ERROR_NOT_OBJECT_OWNER | The authenticated caller may read the object under the Phase I allow-all ACL but is not its owner, so it may not delete it. |
| 233 | ERROR_GENERATION_CONFLICT | expected_generation does not match the current live generation, or target_generation is not strictly greater than expected_generation. |
Idempotency
Deletion is idempotent when the same owner repeats the call with the same expected_generation and target_generation — the server recognizes the tombstone already matches and returns the original successful result rather than rejecting the retry. A retry that names a different target_generation than the tombstone actually published is treated as a fresh delete attempt against the now-current (tombstoned) generation and is rejected with ERROR_GENERATION_CONFLICT, since expected_generation no longer matches.
Common mistakes
Expecting a payment refund
Deleting an object does not reverse the storage payment made when it was created. Phase I's payment model charges once per (owner, object_id, locker_code) and does not track or settle a byte-accurate refund on deletion.
Assuming bytes are gone immediately
A successful delete makes the object logically unavailable to new reads immediately, but physical storage reclamation only happens after active readers release the old generation and the configured deletion grace period (qmail_delete_grace_seconds) elapses. Do not build capacity-planning logic that assumes space is freed synchronously.