jstedfast/MailKit · error · InvalidOperationException

Untagged handlers must be registered before the command has…

Error message

Untagged handlers must be registered before the command has been queued.

What it means

RegisterUntaggedHandler must be called while the ImapCommand is still in ImapCommandStatus.Created; once the command has been queued to the engine the server may already be streaming responses, so late registration is rejected with this InvalidOperationException. It protects against silently dropping untagged responses.

Solutions

  1. Move all RegisterUntaggedHandler calls to immediately after ImapCommand construction and before queuing.
  2. If the command status is unknown, check command.Status == ImapCommandStatus.Created before registering.
  3. Restructure so handler choice is decided before enqueue, or build a new command instead of mutating a queued one.

Example fix

// before
engine.QueueCommand(ic, r => ...);
ic.RegisterUntaggedHandler("FETCH", handler);

// after
ic.RegisterUntaggedHandler("FETCH", handler);
engine.QueueCommand(ic, r => ...);
Defensive patterns

Strategy: validation

Validate before calling

if (ic.Status != ImapCommandStatus.Created)
    throw new InvalidOperationException("Cannot register untagged handler: command already queued");
ic.RegisterUntaggedHandler(atom, handler);

Try / catch

try { ic.RegisterUntaggedHandler(atom, handler); } catch (InvalidOperationException) { log.Warn("Handler registered too late; rebuild command"); }

Prevention

When it happens

Trigger: Calling command.RegisterUntaggedHandler(...) after QueueCommand/queueing (or after the command started running); MailKit's own Queue* helpers register handlers before queuing, so this surfaces in custom command pipelines.

Common situations: Custom IMAP extension code that constructs an ImapCommand, queues it, then tries to attach handlers conditionally; refactors that moved handler registration after an enqueue call.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15). Data as JSON: /api/errors/3f7e14d1b4c714e8. Report an issue: GitHub.

Appendix: source

Thrown at MailKit/Net/Imap/ImapCommand.cs:493

		/// <param name="handler">The handler.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="atom"/> is <see langword="null" />.</para>
		/// <para>-or-</para>
		/// <para><paramref name="handler"/> is <see langword="null" />.</para>
		/// </exception>
		/// <exception cref="System.InvalidOperationException">
		/// Untagged handlers must be registered before the command has been queued.
		/// </exception>
		public void RegisterUntaggedHandler (string atom, ImapUntaggedHandler handler)
		{
			if (atom == null)
				throw new ArgumentNullException (nameof (atom));

			if (handler == null)
				throw new ArgumentNullException (nameof (handler));

			if (Status != ImapCommandStatus.Created)
				throw new InvalidOperationException ("Untagged handlers must be registered before the command has been queued.");

			UntaggedHandlers.Add (atom, handler);
		}

		static bool IsOkNoOrBad (string atom, out ImapCommandResponse response)
		{
			if (atom.Equals ("OK", StringComparison.OrdinalIgnoreCase)) {
				response = ImapCommandResponse.Ok;
				return true;
			}

			if (atom.Equals ("NO", StringComparison.OrdinalIgnoreCase)) {
				response = ImapCommandResponse.No;
				return true;
			}

			if (atom.Equals ("BAD", StringComparison.OrdinalIgnoreCase)) {
				response = ImapCommandResponse.Bad;

View on GitHub (pinned to 9d3859a785)