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

  1. Check the logged err text: 'Mailbox is read-only' means permissions; connection errors mean stability
  2. Grant the IMAP account write/delete permission on the mailbox
  3. 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
  4. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/99daac9c06cdec6c. Report an issue: GitHub.