2

I have a singleton SmtpClient that sends emails to AWS SES.

The problem is that I'm getting SmtpException way too often:

System.Net.Mail.SmtpException: Error in processing. The server response was: 4.4.2 Timeout waiting for data from client.

Basically, almost for every new MailMessage object the first request fails, then the next one succeeds.

I suspect there's a problem with SmtpClient's connection pooling, a client tries to reuse a connection from the pool, whilst it was closed on the other side (by SES). It catches up the exception, starts a new connection, successfully sends an email, the connection on the other side closes and the cycle goes all over again.

Does this mean I need a shorter lifetime for a smtpClient? Like per request rather than singleton? Or maybe something in-between - a short-lived smtpClient for a group of emails (all the emails within some timeframe go into a single group).

Also would be nice to find out what is SES connection timeout and to what extent a single smtpClient can be reused.

Here's the simplified version of the code that conveys the idea of how it works:


//resolved as a singleton
public class EmailService: IDisposable {
    private readonly BlockingCollection < MailMessage > _messages = new();
    private readonly SmtpClient _client;

    public EmailService() {
        _client = new SmtpClient() {
            //fill settings
        };

        new Thread(ProcessQueue) {  IsBackground = true }.Start();
    }

    public void SendEmail(MailMessage message) {
        _messages.Add(message);
    }

    private void ProcessQueue() {
        foreach(var item in _messages.GetConsumingEnumerable()) {
            ProcessItem(item);
        }
    }

    private void ProcessItem(MailMessage message) {
        using(message) {
            try {
                _client.Send(message);
            } catch (SmtpException) {
                // retry
            }

        }
    }

    public void Dispose() {
        //
    }
}    

  • 1
    What connection pooling? SmptClient is an obsolete class that doesn't use connection pooling. The class's docs have a very strong warning *against* using it, suggesting you use MailKit/MimeKit instead. Post the actual exception and your code. – Panagiotis Kanavos Feb 17 '21 at 12:49
  • @PanagiotisKanavos it does use a connection pooling, as per MSDN: https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient Also, the 'obsolescence' of smtp client is weird (https://github.com/dotnet/dotnet-api-docs/issues/2986#issuecomment-472993853). I intend to replace it with MailKit, but that will happen later and now I need the solution for SmtpClient – Mykola Klymyuk Feb 17 '21 at 12:57
  • 3
    Post your code and the exception. Don't make people guess. Why do you even assume this has to do with pooling instead of a sleeping SES service instance taking too long to wake up? What you describe is *typical startup timeout behavior*. Perhaps all you need to do is increase the connection timeout – Panagiotis Kanavos Feb 17 '21 at 12:59
  • 1
    `Does this mean I need a shorter lifetime for a smtpClient?` depends on the *email server* - if you end up trying to use dead connections, yes, you need a shorter lifetime. SES isn't a simple SMTP server, it's a service that lives only as long as there's traffic for it. All hosted services work this way, not just cloud services. Very few people use long-lived SMTP connections anyway, even email clients. In SES's case, it's actually an HTTP service with an SMTP facade. – Panagiotis Kanavos Feb 17 '21 at 13:08
  • 1
    Even email clients only open a connection when they have to, to avoid disconnection issues. If the SMTP server was in your local network there may be fewer disconnections, but in a cloud environment the chance of disconnection is *higher*. Even on a local server though, there are inactivity timeouts that would close a connection that's idle for too long to conserve server resources. In AWS you pay for messages, not a server's uptime, so AWS is *very aggressive* in disconnecting idle instances and connections – Panagiotis Kanavos Feb 17 '21 at 13:12
  • `Why do you even assume this has to do with pooling` The only configurable setting regarding timeouts that smtpClient has is `Timeout` property which is 100 seconds by default. Should be quite enough for SES to wake up. – Mykola Klymyuk Feb 17 '21 at 13:12
  • 1
    Unless it killed your connection because it was idle for too long. Again, no code, no exceptions, you're asking people to guess. My guess is that the SMTP *facade* is going down due to inactivity. If you keep any kind of server connection idle for long though, servers will kill it. No server product likes misbehaved clients that consume resources without actually doing any work. – Panagiotis Kanavos Feb 17 '21 at 13:15
  • 1
    If you want to avoid connections and disconnection issues altogether, use the HTTP API. The AWS docs themselves call SMTP an SMTP **interface**. SES isn't an SMTP server. – Panagiotis Kanavos Feb 17 '21 at 13:16
  • BTW now that I see the exception, it looks like a timeout due to inactivity. If you search for this error you'll find several similar questions, with the same explanations. Even exact duplicates [like this one](https://stackoverflow.com/questions/14563200/amazon-ses-stops-working) – Panagiotis Kanavos Feb 17 '21 at 13:40
  • @PanagiotisKanavos it's not like this https://stackoverflow.com/questions/14563200/amazon-ses-stops-working one, the exception is different, it's not throttling either. – Mykola Klymyuk Feb 17 '21 at 13:53
  • 1
    Did you read `When I do not dispose of the SmtpClient, I get the error... 'Service not available, closing transmission channel. The server response was: Timeout waiting for data from client.'` ? Your code doesn't dispose the SmptClient either, so sooner or later you'll get that exception – Panagiotis Kanavos Feb 17 '21 at 13:56
  • @PanagiotisKanavos disposing of a client after each use will cause loads of connections to be created. In the case of many emails in the queue that might be an issue. Connection pooling has its use and I want to utilize it properly. – Mykola Klymyuk Feb 17 '21 at 14:10
  • 2
    Maybe then each time you process the contents of the queue, you open a connection and use it to send all the emails currently in the queue. Then dispose of it afterwards. that ensures you don't make a fresh connection for every mail, but also you don't leave the connection open forever. Bottom line is, opening and closing connections rapidly is relatively normal with email (as mentioned earlier). It's not a dreadful evil. As long as you don't try to create dozens in parallel, you should be fine – ADyson Feb 17 '21 at 14:20
  • @ADyson yes, that is what it seems to me as the best option. Thanks. – Mykola Klymyuk Feb 17 '21 at 14:24
  • You can use the HTTP API instead of SMTP so you won't have to handle any connections – Panagiotis Kanavos Feb 17 '21 at 14:29
  • @PanagiotisKanavos unfortunately, at the moment I'm limited just to smtp, SES is only one of the smtp servers that the system needs to work with. – Mykola Klymyuk Feb 17 '21 at 14:54

0 Answers0