Symptom
Your ASP.NET Core or .NET Framework application suddenly stops sending email through our SmarterMail server. The exception text typically includes one or more of:
MailKit.Security.SslHandshakeExceptionAn error occurred while attempting to establish an SSL or TLS connectionA required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed fileThe remote certificate is invalid according to the validation procedure
The error appears suddenly — your code did not change, your appsettings.json did not change, and the same SMTP host/port worked for weeks or months prior. Webmail (login via browser) continues to work normally.
Quick Fix (1 minute)
Recycle your IIS application pool. This clears the cached SMTP/TLS connection inside your .NET process. Email sending will resume immediately.
- Log into Plesk
- Open Websites & Domains → your domain → IIS Settings (or Dedicated IIS Application Pool)
- Click Recycle
If the recycle fixes it, the root cause is the connection-pool issue described below — and it will happen again on the next certificate rotation (every 60-90 days) unless your code is updated.
Why This Happens
Our SmarterMail server uses Let's Encrypt SSL certificates, which renew automatically every 60-90 days. The renewal swaps the live certificate on the server in milliseconds, but your .NET application is doing something completely normal that creates the problem:
MailKit Pools TLS Connections
The popular MailKit.Net.Smtp.SmtpClient library — and to a lesser extent System.Net.Mail.SmtpClient — keeps SMTP connections alive between send calls for performance. A connection established before the certificate rotation is bound to a TLS session that authenticated against the previous certificate. When you reuse that connection after the rotation:
- The TCP socket is still open
- The TLS session state still references the old certificate chain
- If the old certificate has now passed its
notAfterdate (which it has — that's why it was renewed), MailKit fails the validity check on the cached chain - You see the error above
This is not a server-side problem. The certificate on our server is healthy at the time of your error — we can verify this any time on demand. The problem is purely the cached TLS session in your application's memory.
Verifying Our Server-Side Certificate
From any Windows machine with internet access, you can verify the current cert on the SMTPS port (465) directly:
$tcp = New-Object Net.Sockets.TcpClient('ec2amaz-a4g4262.adaptivewebhosting.com', 465)
$ssl = New-Object Net.Security.SslStream($tcp.GetStream(), $false, { $true })
$ssl.AuthenticateAsClient('ec2amaz-a4g4262.adaptivewebhosting.com')
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]$ssl.RemoteCertificate
"Subject: $($cert.Subject)"
"Issuer: $($cert.Issuer)"
"NotBefore: $($cert.NotBefore)"
"NotAfter: $($cert.NotAfter)"
$tcp.Close()
Or from a Linux/macOS machine:
openssl s_client -connect ec2amaz-a4g4262.adaptivewebhosting.com:465 -servername ec2amaz-a4g4262.adaptivewebhosting.com < /dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
If the NotAfter date is in the future, the server cert is fine and the issue is in your application's connection pool.
The Permanent Fix
Update your email-sending code to either (a) create a fresh SmtpClient per send, or (b) catch and recover from cert-validation exceptions. Option (a) is simpler and recommended for typical notification-email volumes.
Option A — Fresh SmtpClient Per Send (recommended)
Replace any long-lived SmtpClient instance with a per-send using block. The TCP handshake overhead is negligible for typical notification emails (transactional, password reset, invoice, etc.).
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
public async Task SendEmailAsync(MimeMessage message)
{
using (var client = new SmtpClient())
{
await client.ConnectAsync(
"ec2amaz-a4g4262.adaptivewebhosting.com",
465,
SecureSocketOptions.SslOnConnect);
await client.AuthenticateAsync(_smtpUser, _smtpPass);
await client.SendAsync(message);
await client.DisconnectAsync(true);
}
}
Option B — Catch and Reconnect (if you need to keep pooling)
If high throughput requires keeping a pooled SmtpClient, wrap each send and reconnect on cert failures:
private SmtpClient _client;
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
public async Task SendEmailAsync(MimeMessage message)
{
await _lock.WaitAsync();
try
{
for (int attempt = 0; attempt < 2; attempt++)
{
try
{
if (_client == null || !_client.IsConnected)
{
_client?.Dispose();
_client = new SmtpClient();
await _client.ConnectAsync(
"ec2amaz-a4g4262.adaptivewebhosting.com",
465,
SecureSocketOptions.SslOnConnect);
await _client.AuthenticateAsync(_smtpUser, _smtpPass);
}
await _client.SendAsync(message);
return;
}
catch (Exception ex) when (
ex is SslHandshakeException ||
ex is System.IO.IOException ||
ex is ServiceNotConnectedException ||
ex is ServiceNotAuthenticatedException)
{
_client?.Dispose();
_client = null;
if (attempt == 1) throw;
}
}
}
finally
{
_lock.Release();
}
}
System.Net.Mail.SmtpClient (Older Code)
If you're using the older System.Net.Mail.SmtpClient (which Microsoft has marked as legacy), the same pattern applies — create a fresh instance per send, wrapped in a using block. System.Net.Mail.SmtpClient has its own internal connection pool which also caches TLS sessions. The fix is identical.
SMTP Settings (Reference)
| Setting | Value |
|---|---|
| Host | ec2amaz-a4g4262.adaptivewebhosting.com |
| Port (recommended) | 465 with implicit TLS (SslOnConnect / SSL=true) |
| Port (alternate) | 587 with STARTTLS |
| Authentication | Required |
| Username | Full email address (e.g., [email protected]) |
| Password | Mailbox password (set in SmarterMail) |
Same Issue, Other Languages
This connection-pooling pattern is not unique to .NET / MailKit. The same issue affects long-lived SMTP clients in any language. Equivalent fixes:
- Java (JavaMail / Jakarta Mail): Don't cache a
Session+Transportpair across requests. Create a freshTransportper send, or catchMessagingExceptionwith a cert-related cause and reconnect. - Python (smtplib): Don't hold an
SMTP_SSLinstance globally. Usewith smtplib.SMTP_SSL(...) as smtp:per send. - Node.js (nodemailer): Set
pool: falsefor low-volume transactional, or calltransporter.close()+ recreate onESOCKET/ECONNECTIONerrors.
Why We Use Let's Encrypt
Let's Encrypt is the industry-standard free certificate authority used by the majority of TLS deployments worldwide. Certificates are valid for 90 days and auto-renew. This is not specific to Adaptive Web Hosting — the same renewal cadence applies to any SmarterMail, Microsoft 365, Google Workspace, or AWS SES endpoint your application connects to. Code written to be resilient to certificate rotation (Option A above) will work across all providers without modification.
Summary
- Immediate: recycle your IIS application pool
- Permanent: create a fresh
SmtpClientper send (Option A) OR catchSslHandshakeExceptionand reconnect (Option B) - Don't need to change: SMTP host, port, credentials, or any SmarterMail settings
If your application continues to fail after both the recycle AND a code change above, open a support ticket and we'll dig deeper. The PowerShell snippet under "Verifying Our Server-Side Certificate" gives us a quick way to confirm the cert is healthy at the moment your error reproduces.