RocketChat/Rocket.Chat · warning
Mark deleted error
Error message
Mark deleted error
What it means
In the inbound-email IMAP interceptor with deleteAfterRead enabled, after an email is parsed it is flagged \Deleted via imap.seq.addFlags; the IMAP server rejected that STORE operation and the failure is only logged. The message therefore stays in the mailbox and will be fetched and re-emitted again on the next poll. Note the call passes the parsed email object rather than the seqno available in the enclosing messagecb — depending on the imap library version this itself can make the flag call fail for every message.
Source
Thrown at apps/meteor/server/email/IMAPInterceptor.ts:193
});
}
imapFetch(emailIds: number[]): Promise<number[]> {
return new Promise((resolve, reject) => {
const out: number[] = [];
const messagecb = (msg: ImapMessage, seqno: number) => {
out.push(seqno);
const bodycb = (stream: NodeJS.ReadableStream, _info: ImapMessageBodyInfo): void => {
simpleParser(new Readable().wrap(stream), (_err, email) => {
if (this.options.rejectBeforeTS && email.date && email.date < this.options.rejectBeforeTS) {
logger.error({ msg: 'Rejecting email on inbox', user: this.config.user, subject: email.subject });
return;
}
this.emit('email', email);
if (this.options.deleteAfterRead) {
this.imap.seq.addFlags(email, 'Deleted', (err) => {
if (err) {
logger.warn({ msg: 'Mark deleted error', err });
}
});
}
});
};
msg.once('body', bodycb);
};
const errorcb = (err: Error): void => {
logger.warn({ msg: 'Fetch error', err });
reject(err);
};
const endcb = (): void => {
resolve(out);
};
const fetch = this.imap.fetch(emailIds, {
bodies: ['HEADER', 'TEXT', ''],
struct: true,
markSeen: this.options.markSeen,View on GitHub (pinned to b2c16d5842)
Solutions
- Check the logged err text: 'Mailbox is read-only' means permissions; connection errors mean stability
- Grant the IMAP account write/delete permission on the mailbox
- If the warn fires for every message, verify the addFlags argument against your imap library — seq.addFlags expects the sequence number (seqno), not the parsed email
- Keep batches small and let the interceptor reconnect before flagging
Example fix
// before: parsed email object passed to the seq-based flag API
this.imap.seq.addFlags(email, 'Deleted', (err) => { ... });
// after: use the sequence number captured in messagecb
this.imap.seq.addFlags(seqno, 'Deleted', (err) => { ... }); Defensive patterns
Strategy: try-catch
Try / catch
this.imap.seq.addFlags(seqno, 'Deleted', (err) => {
if (err) {
// keep the message id so the next poll retries the delete instead of re-emitting forever
logger.warn({ msg: 'Mark deleted error', seqno, err });
}
}); Prevention
- Use a dedicated IMAP account with write/delete ACL on the mailbox
- Verify the flag call uses the sequence number, not the parsed email object
- Idempotently track processed message ids so a failed flag does not duplicate inbound emails
When it happens
Trigger: IMAP connection dropped between the fetch and the flag operation; mailbox opened read-only or the account lacks permission to set flags; server-side STORE failure (quota/ACL); passing an invalid message source to seq.addFlags (parsed email object instead of sequence number).
Common situations: Gmail/Office365 throttling long-lived connections; service mailboxes without write/delete ACL; flaky networks causing reconnect mid-batch; the same emails re-processed every poll because they never get flagged deleted.
Related errors
- Fetch error
- inbox-not-found
- user-without-verified-email
- CustomOAuth: emailPath is required
- CustomOAuth: Failed to extract email
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/99daac9c06cdec6c.
Report an issue: GitHub.