jstedfast/MailKit · error · TimeoutException

Operation timed out after

Error message

Operation timed out after {writeTimeout} milliseconds

What it means

NetworkStream.WriteAsync mirrors the read path: if the socket send doesn't complete within writeTimeout, the stream disconnects and throws a TimeoutException with the writeTimeout in the message. This indicates the server stopped consuming data (or the network is stalled) while MailKit was writing a command or payload.

Solutions

  1. Increase the client's Timeout property to accommodate slow upstream bandwidth for the payload size.
  2. Catch TimeoutException, reconnect, and resume/retry the send (MailKit has already disconnected the stream).
  3. Check TCP stack/network health (half-open connections, MTU issues) and server read-back pressure.

Example fix

// before
smtp.Timeout = 10_000;
await smtp.SendAsync(message); // large attachment, slow uplink

// after
smtp.Timeout = 300_000; // allow 5 min for large uploads
await smtp.SendAsync(message);
Defensive patterns

Strategy: try-catch

Try / catch

try { await smtp.SendAsync(message); }
catch (TimeoutException) {
    await ReconnectAsync(); // stream was disconnected mid-write
    await smtp.SendAsync(message);
}

Prevention

When it happens

Trigger: Writing large payloads (e.g. SMTP message body, APPEND data) over a stalled connection, or any command write that exceeds the client's Timeout; the AcceptProxyConnection path writing the proxy request when the backend isn't reading.

Common situations: Uploading large messages on slow/upstream-limited links with a small Timeout; server stuck and not reading the socket; dead peers kept alive by half-open TCP connections.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at MailKit/Net/NetworkStream.cs:238

			// Capture the WriteTimeout so even if we get an exception and disconnect the socket, we still have it.
			int writeTimeout = WriteTimeout;

			using (var timeout = new CancellationTokenSource (writeTimeout)) {
				using (var linked = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken, timeout.Token)) {
					using (var registration = linked.Token.Register (() => tcs.TrySetCanceled (), false)) {
						send!.SetBuffer (buffer, offset, count);
						send.UserToken = tcs;

						if (!Socket.SendAsync (send))
							AsyncOperationCompleted (null, send);

						try {
							await tcs.Task.ConfigureAwait (false);
						} catch (OperationCanceledException ex) {
							Disconnect ();
							if (timeout.IsCancellationRequested)
								throw new TimeoutException ($"Operation timed out after {writeTimeout} milliseconds", ex);
							throw;
						} catch (Exception ex) {
							Disconnect ();
							if (ex is SocketException)
								throw new IOException (ex.Message, ex);
							throw;
						}
					}
				}
			}
		}

		public override void Flush ()
		{
		}

		public override Task FlushAsync (CancellationToken cancellationToken)
		{

View on GitHub (pinned to 9d3859a785)