fix(imap): a folder missing from one LIST no longer deletes its mail

MailboxSyncer removed every stored folder the server's LIST did not name, and
message.mailbox_id cascades, so an empty or partial LIST, or a rename done in
another client, deleted every message of those folders in one poll.

Now an empty listing is refused outright, a listing that would leave out most
of the stored folders is refused like VanishedMessageReconciler's bulk guard,
and a folder that is really gone is only marked (mailbox.missing_since and
missing_syncs, new migration) and skipped by the sync loop. It is removed only
after three consecutive listings and 24 hours, and its messages go through
MessageEraser so JMAP clients get a destroyed change for each. A new folder
with the same special use or the same UIDVALIDITY as exactly one missing
folder is treated as a rename: the row keeps its mail and takes the new path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paul LütznerandClaude Opus 5.5 committed 2026-09-23 19:14:14 +02:00
1 parent 5a19c3bddb
commit 63b236b134
7 files changed
+474 -32

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* A folder that drops out of the server's LIST is marked, not deleted.
*
* MailboxSyncer used to remove any mailbox row the listing did not name, and
* message.mailbox_id cascades — so one partial or empty LIST, or a rename done
* in another client, deleted every message of the folder. The mark and the
* counter let the syncer wait for the absence to last before believing it.
*/
final class Version20260923101100 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add mailbox.missing_since and mailbox.missing_syncs.';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE mailbox ADD missing_since TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mailbox ADD missing_syncs INT DEFAULT 0 NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE mailbox DROP missing_since');
$this->addSql('ALTER TABLE mailbox DROP missing_syncs');
}
}