Flags, ack & expunge
This page covers what happens to a message after you retrieve it: acknowledging it, flagging it, and removing it. Retrieval itself never changes anything on the server.
All four calls act only on messages in mailboxes you own. An ID that is not yours, or does not exist, is silently skipped, so a message ID cannot be used to touch another peer’s mail. A reader of someone else’s shared or public mailbox cannot acknowledge or delete its messages.
Acknowledge with MarkDelivered
Section titled “Acknowledge with MarkDelivered”ack, err := cl.MarkDelivered(ctx, []string{msg.MessageID})if err != nil { return err}fmt.Println(ack.UpdatedCount) // how many of the IDs were yours and were acted onWhat MarkDelivered does depends on how the message was sent:
| Sent with | After MarkDelivered |
|---|---|
| default (non-persistent) | Deleted from the server. |
WithPersistent(true) |
Kept, with the \Seen flag set. It stays until you delete it, the mailbox’s retention removes it, or it expires. |
This is the call that drains a mailbox. A client that retrieves and never acknowledges leaves every
message on the server until the mailbox reaches its cap (1000 messages by default) and refuses all new
mail with client.ErrMailboxFull. Acknowledge once you have durably handled a message, not before.
The result is a *wire.MarkDeliveredAck with Success, UpdatedCount and ErrorMessage.
Message flags
Section titled “Message flags”Each stored message carries IMAP-style flags in wire.Message.MsgFlags (type wire.MessageFlags):
| Constant | Meaning |
|---|---|
wire.MsgFlagSeen |
Read. Set by MarkDelivered on persistent messages. |
wire.MsgFlagFlagged |
Marked for attention. |
wire.MsgFlagDeleted |
Marked for removal by Expunge. |
wire.MsgFlagDraft |
Draft. |
MessageFlags has IsSeen, IsFlagged, IsDeleted, IsDraft, HasFlag, WithFlag and
WithoutFlag.
These are separate from the protocol flags in wire.Message.Flags (wire.SFMessageFlags:
FlagEncrypted, FlagCompressed, FlagForwarded, …), which describe the payload and routing.
UpdateFlags
Section titled “UpdateFlags”UpdateFlags adds and removes flags on one message. Both arguments are uint32 bitmasks:
// Flag a messageflagAck, err := cl.UpdateFlags(ctx, msg.MessageID, uint32(wire.MsgFlagFlagged), 0)
// Mark it read and clear the flag in one callflagAck, err = cl.UpdateFlags(ctx, msg.MessageID, uint32(wire.MsgFlagSeen), uint32(wire.MsgFlagFlagged))
switch {case err != nil: return errcase !flagAck.Success: log.Println(flagAck.ErrorMessage) // "message not found": not yours, or gonedefault: fmt.Println(wire.MessageFlags(*flagAck.NewFlags)) // e.g. "seen"}A message you do not own reports Success == false with message not found in the result, not as an
error, so a guessed ID does not reveal that the message exists.
Expunge
Section titled “Expunge”Expunge permanently removes every message flagged \Deleted. This is the two-step delete IMAP clients
use: flag first, remove later.
_, err = cl.UpdateFlags(ctx, msg.MessageID, uint32(wire.MsgFlagDeleted), 0)
// Remove \Deleted messages in all your mailboxesexpungeAck, err := cl.Expunge(ctx)
// or only in one folderexpungeAck, err = cl.Expunge(ctx, client.WithExpungeFolderPath("orders"))fmt.Println(expungeAck.DeletedCount)DeleteMessages
Section titled “DeleteMessages”DeleteMessages removes messages by ID immediately, persistent or not, without the flag step:
deleteAck, err := cl.DeleteMessages(ctx, []string{msg.MessageID})fmt.Println(deleteAck.DeletedCount) // what was removed, not what was askedWhich to use
Section titled “Which to use”| Goal | Call |
|---|---|
| Handled a message, done with it | MarkDelivered |
| Keep a message as a record, but mark it read | Send it with WithPersistent(true), then MarkDelivered |
| Let the user delete and undo before it is final | UpdateFlags with MsgFlagDeleted, then Expunge |
| Remove now | DeleteMessages |
Mailbox-wide limits and retention are on Mailboxes & ACLs.