Skip to content

Trash: delete, restore and delete for good - #56

Merged
markmnl merged 2 commits into
mainfrom
trash
Oct 22, 2026
Merged

markmnl merged 2 commits into
mainfrom
trash

Conversation

@markmnl

@markmnl markmnl commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Lets a user get rid of sent and received messages. Each identity has its own trash; nothing in it changes what anyone else sees.

Depends on markmnl/fmsgd#51 (a transaction may purge a sent message) and markmnl/fmsgid#8 (usage release). CI loads fmsgd's dd.sql from the pinned module, so once fmsgd#51 is merged I'll bump the github.com/markmnl/fmsgd version in go.mod on this branch. Until then TestPurge fails in CI. It passes locally against the fmsgd branch.

API

  • DELETE /fmsg/:id on a sent message (sender or recipient): moves it into the caller's trash.
    • ?thread=true deletes the whole thread below it, including replies that arrive later.
    • ?permanent=true deletes it for good: the message and everything below it, or the whole thread with thread=true.
    • Drafts behave as before.
  • POST /fmsg/:id/restore (?thread=true): takes it out of the trash.
  • GET /fmsg, GET /fmsg/sent: ?trash=exclude (the default, so existing clients and agents see no deleted mail), include or only. Every message (list, get, WebSocket, thread messages) carries "trash": null | "message" | "thread".
  • Deleted for good, for that identity:
    • 404 on get, data, attachments, read, react, add-to and the thread routes. In a thread it counts as not visible.
    • Never listed, pushed or sent over the WebSocket, except a trash event {id, trash: "purged"}.
    • A reply to it gets 400 parent message not found.
  • Quota: what it counted against the identity's storage (sent, and each accepted delivery) is released to fmsgid, using the size it was recorded at. Daily usage is unchanged.
  • Removal from the host: once no local participant holds it and none of its deliveries or add-to notifications is pending, its rows and unshared files are deleted, with replies' pid set to NULL. fmsgd then rejects replies to it as parent not found.
  • Web Push: no push for a message in the recipient's trash, e.g. a reply in a deleted thread.
  • New trash WebSocket event (via pg_notify('msg_trash')), so other tabs and devices update.

Schema (dd.sql)

  • fmsg_api_trash (addr, msg_id, thread, time_deleted, time_purged).
  • fmsg_api_trash_state(addr, msg_id), which walks ancestors.
  • Lists only call it when the caller has anything in the trash.
  • The README now says this file extends, and is applied after, the fmsgd schema. The sub-account and API-key CLI tests apply the fmsgd schema first for that reason.

Tests

  • TestTrashAndRestore: message and thread, later replies, list modes, get, and the item for push.
  • TestPurge:
    • another local holder keeps it;
    • every route returns 404 for the purger;
    • reply refused;
    • releases;
    • removal once unheld: row and file gone, hash lookup empty, and a surviving reply unlinked;
    • purging again does nothing.
  • TestPurgeKeepsPendingDelivery, TestTrashNotifies, TestHubRoute_MsgTrashPurged.
  • The full suite passes against Postgres 17 with the fmsgd branch.

🤖 Generated with Claude Code

markmnl and others added 2 commits October 21, 2026 22:24
Each identity gets a trash. DELETE /fmsg/:id on a sent message moves it
(or with ?thread=true its whole thread, later replies included) into the
caller's trash; POST /fmsg/:id/restore takes it out; ?permanent=true
deletes it for good. Lists leave trashed messages out unless ?trash=include
or only, and every message carries its trash state. A message deleted for
good, and everything replying to it, is 404 on every route for that
identity, never listed, pushed or sent over the WebSocket, and its usage is
released to fmsgid. Once no local participant holds it and no delivery is
pending, it is removed from the host, so fmsgd rejects replies to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@markmnl
markmnl merged commit 6119786 into main Oct 22, 2026
1 check passed
@markmnl
markmnl deleted the trash branch October 22, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant