jstedfast/MailKit · error · ServiceNotConnectedException
The SmtpClient must be connected before you can send…
Error message
The SmtpClient must be connected before you can send commands.
What it means
AsyncSmtpClient.SendCommandAsync validates state before sending a raw protocol command. If the client has not completed a connection (IsConnected is false) it throws ServiceNotConnectedException with this message, since there is no protocol stream to write to.
Solutions
- Await ConnectAsync (with host, port and options) before calling SendCommandAsync.
- Check the IsConnected property before sending raw commands.
- If the connection dropped, reconnect (ConnectAsync) before issuing commands.
- Catch ServiceNotConnectedException and trigger reconnection logic.
Example fix
// before
await client.SendCommandAsync("NOOP\r\n");
// after
if (!client.IsConnected)
await client.ConnectAsync("smtp.example.com", 465, SecureSocketOptions.SslOnConnect);
await client.SendCommandAsync("NOOP\r\n"); Defensive patterns
Strategy: validation
Validate before calling
if (client == null || client.IsDisposed) throw new ObjectDisposedException(nameof(client));
if (!client.IsConnected)
await client.ConnectAsync(smtpHost, smtpPort, SecureSocketOptions.StartTls); Type guard
bool CanSendCommands(AsyncSmtpClient c) => c != null && !c.IsDisposed && c.IsConnected;
Try / catch
try {
var resp = await client.SendCommandAsync("NOOP\r\n");
} catch (ServiceNotConnectedException) {
await client.ConnectAsync(smtpHost, smtpPort, SecureSocketOptions.Auto);
// retry the command
} Prevention
- Always await ConnectAsync before issuing any protocol commands
- Check IsConnected before each custom command
- Wrap session usage in a connect/send/disconnect lifecycle, not ad-hoc commands
- Reconnect on ServiceNotConnectedException instead of retrying the command
When it happens
Trigger: Calling SendCommandAsync before ConnectAsync, or after the connection dropped, was disconnected via DisconnectAsync, or failed during the SMTP handshake.
Common situations: Sending custom commands at app startup before the connection task completes; reusing a client instance after a previous send failed and closed the session.
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
- The SmtpClient is not connected.
- No compatible authentication mechanisms found.
- The SMTP server does not support the STARTTLS extension.
- MessageNotAccepted
- The SmtpClient must be connected before you can send…
AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15).
Data as JSON: /api/errors/c609d33d4ab9fe4a.
Report an issue: GitHub.
Appendix: source
Thrown at MailKit/Net/Smtp/AsyncSmtpClient.cs:132
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation has been canceled.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="SmtpProtocolException">
/// An SMTP protocol exception occurred.
/// </exception>
protected Task<SmtpResponse> SendCommandAsync (string command, CancellationToken cancellationToken = default)
{
if (command == null)
throw new ArgumentNullException (nameof (command));
CheckDisposed ();
if (!IsConnected)
throw new ServiceNotConnectedException ("The SmtpClient must be connected before you can send commands.");
if (!command.EndsWith ("\r\n", StringComparison.Ordinal))
command += "\r\n";
return SendCommandInternalAsync (command, cancellationToken);
}
Task<SmtpResponse> SendEhloAsync (bool connecting, string helo, CancellationToken cancellationToken)
{
var command = CreateEhloCommand (helo);
if (connecting)
return Stream!.SendCommandAsync (command, cancellationToken);
return SendCommandInternalAsync (command, cancellationToken);
}
async Task EhloAsync (bool connecting, CancellationToken cancellationToken)View on GitHub (pinned to 9d3859a785)