SmtpClient Timeout doesn't work

16,099

Solution 1

Reproducing your test - it works for me

You asked if anyone has encountered the same problem - I just tried your code on Windows 7, VS 2008 with .NET 2.0 - it worked just fine. With the timeout set to 1, as you have it, I get this error almost immediately:

Unhandled Exception: System.Net.Mail.SmtpException: The operation has timed out
   at System.Net.Mail.SmtpClient.Send(MailMessage message)
   at mailtimeout.Program.Main(String[] args) in c:\test\mailtimeout\Program.cs:line 29

I think the problem may be that you are expecting something different from timeout. Timeout means that the connection was made successfully, but the response did not come back from the server. This means you need to actually have a server listening on port 25 at your destination, but it doesn't respond. For this test, I use Tcl to create a socket on 25 that did nothing:

c:\> tclsh
% socket -server foo 25

When I changed the timout to 15000, I didn't get the timeout error unti l5s later.

Why Smtp.Timeout has no effect if the connection can't be made

If nothing is listening at port 25, or the host is not reachable, the timeout is not going to happen until at least 20s, when the system.net.tcpclient layer times out. This is below the system.net.mail layer. From an excellent article describing the problem and solution:

You will notice that neither of the two classes, System.Net.Sockets.TcpClient nor System.Net.Sockets.Socket has a timeout to connect a socket. I mean a timeout you can set. .NET Sockets do not provide a Connect Timeout when calling the Connect/BeginConnect method while establishing a Synchronous/Asynchronous socket connection. Instead, connect is forced to wait a very long time before an Exception is thrown if the server it tried to connect to is not listening or if there is any network error. The default timeout is 20 - 30 seconds.

There is no ability to change that timeout from mail (which makes sense, mail servers are usually up), and in fact there's no ability to change the connect from system.net.socket, which is really surprising. But you can do an asynchronous connect, and can then tell if your host is up and the port open. From this MSDN thread, and in particular this post, this code works:

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IAsyncResult result = socket.BeginConnect("192.168.1.180", 25, null, null);
// Two second timeout
bool success = result.AsyncWaitHandle.WaitOne(2000, true);
if (!success) {
    socket.Close();
    throw new ApplicationException("Failed to connect server.");
}

Solution 2

Adding to ckhan's answer I'd like to share with you a suggestion to implement shorter timeout:

var task = Task.Factory.StartNew(() => SendEmail(email));

if (!task.Wait(6000))
   // error handling for timeout on TCP layer (but you don't get the exception object)

then in the SendEmail():

using (var client = new SmtpClient(_serverCfg.Host, _serverCfg.Port)) 
{        
    try
    {
        client.Timeout = 5000;   // shorter timeout than the task.Wait()
        // ...
        client.Send(msg);
    }
    catch (Exception ex)
    {
        // exception handling
    }
}

This solution comes with the trade-off that you don't get the exception details in the task.Wait case, but maybe that's worth it?

Share:
16,099

Related videos on Youtube

croisharp
Author by

croisharp

Updated on June 04, 2022

Comments

  • croisharp
    croisharp about 2 years

    I have set the Timeout property of SmtpClient class, but it doesn't seem to work, when i give it a 1 millisecond value, the timeout actually is 15secs when the code is executed. The code i took from msdn.

    string to = "[email protected]";
    string from = "[email protected]";
    string subject = "Using the new SMTP client.";
    string body = @"Using this new feature, you can send an e-mail message from an application very easily.";
    MailMessage message = new MailMessage(from, to, subject, body);
    SmtpClient client = new SmtpClient("1.2.3.4");
    Console.WriteLine("Changing time out from {0} to 100.", client.Timeout);
    client.Timeout = 1;
    // Credentials are necessary if the server requires the client 
    // to authenticate before it will send e-mail on the client's behalf.
    client.Credentials = CredentialCache.DefaultNetworkCredentials;
    client.Send(message);
    

    I tried the implementation on mono, it also doesn't work.

    Do anyone encountered the same problem?

  • croisharp
    croisharp about 12 years
    But if you try an ip that doesn't listen at port 25, or maybe that ip isn't used by anyone, do the timeout work on your computer?
  • croisharp
    croisharp about 12 years
    I tried to open a socket that did nothing with the method you sad, and it gives me operation timeout exception when i ^C the socket from cmd.
  • ckhan
    ckhan about 12 years
    When no host listening: yes, I also get the 20s timeout. That's at the TCP layer. Expanded my answer to address that problem, too.
  • croisharp
    croisharp about 12 years
    Thanks a lot, i build a site when everyone can add smtp server to their account and send message. There will be a performance impact because of +1 request, but it's better than 20s delay. The ideal solution i thibnk is to build my own SmtpClient using TcpClient class.
  • mcmillab
    mcmillab over 9 years
    what do we do if it is a success, ie how do we send the msg via this socket?
  • ckhan
    ckhan over 9 years
    @mcmillab: well, if it does connect, then the code in the question should send the message, just as written
  • mcmillab
    mcmillab over 9 years
    @ckhan - thanks - so just to be clear, the solution code is just to test the socket connection. If it does work, then the SmtpClient code will open it's own connection and use. ?