25

This is related to a question I asked the other day on how to send email.

My new, related question is this... what if the user of my application is behind a firewall or some other reason why the line client.Send(mail) won't work...

After the lines:

SmtpClient client = new SmtpClient("mysmtpserver.com", myportID);
client.Credentials = new System.Net.NetworkCredential("myusername.com", "mypassword");

is there something I can do to test client before I try sending?

I thought about putting this in a try/catch loop, but I'd rather do a test and then pop up a dialog saying: can't access smtp or something like that.

(I'm presuming that neither I, nor potentially my application user, has the ability to adjust their firewall settings. For example... they install the app at work and don't have control over their internet at work)

-Adeena

Community
  • 1
  • 1
adeena
  • 4,027
  • 15
  • 40
  • 52

6 Answers6

49

I think that if you are looking to test the SMTP it's that you are looking for a way to validate your configuration and network availability without actually sending an email. Any way that's what I needed since there were no dummy email that would of made sense.

With the suggestion of my fellow developer I came up with this solution. A small helper class with the usage below. I used it at the OnStart event of a service that sends out emails.

Note: the credit for the TCP socket stuff goes to Peter A. Bromberg at http://www.eggheadcafe.com/articles/20030316.asp and the config read stuff to the guys here: Access system.net settings from app.config programmatically in C#

Helper:

public static class SmtpHelper
{
    /// <summary>
    /// test the smtp connection by sending a HELO command
    /// </summary>
    /// <param name="config"></param>
    /// <returns></returns>
    public static bool TestConnection(Configuration config)
    {
        MailSettingsSectionGroup mailSettings = config.GetSectionGroup("system.net/mailSettings") as MailSettingsSectionGroup;
        if (mailSettings == null)
        {
            throw new ConfigurationErrorsException("The system.net/mailSettings configuration section group could not be read.");
        }
        return TestConnection(mailSettings.Smtp.Network.Host, mailSettings.Smtp.Network.Port);
    }

    /// <summary>
    /// test the smtp connection by sending a HELO command
    /// </summary>
    /// <param name="smtpServerAddress"></param>
    /// <param name="port"></param>
    public static bool TestConnection(string smtpServerAddress, int port)
    {
        IPHostEntry hostEntry = Dns.GetHostEntry(smtpServerAddress);
        IPEndPoint endPoint = new IPEndPoint(hostEntry.AddressList[0], port);
        using (Socket tcpSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
        {
            //try to connect and test the rsponse for code 220 = success
            tcpSocket.Connect(endPoint);
            if (!CheckResponse(tcpSocket, 220))
            {
                return false;
            }

            // send HELO and test the response for code 250 = proper response
            SendData(tcpSocket, string.Format("HELO {0}\r\n", Dns.GetHostName()));
            if (!CheckResponse(tcpSocket, 250))
            {
                return false;
            }

            // if we got here it's that we can connect to the smtp server
            return true;
        }
    }

    private static void SendData(Socket socket, string data)
    {
        byte[] dataArray = Encoding.ASCII.GetBytes(data);
        socket.Send(dataArray, 0, dataArray.Length, SocketFlags.None);
    }

    private static bool CheckResponse(Socket socket, int expectedCode)
    {
        while (socket.Available == 0)
        {
            System.Threading.Thread.Sleep(100);
        }
        byte[] responseArray = new byte[1024];
        socket.Receive(responseArray, 0, socket.Available, SocketFlags.None);
        string responseData = Encoding.ASCII.GetString(responseArray);
        int responseCode = Convert.ToInt32(responseData.Substring(0, 3));
        if (responseCode == expectedCode)
        {
            return true;
        }
        return false;
    }
}

Usage:

if (!SmtpHelper.TestConnection(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)))
{
    throw new ApplicationException("The smtp connection test failed");
}
Community
  • 1
  • 1
Four
  • 900
  • 1
  • 10
  • 18
  • 1
    Great solution. Works like a charm and allows me to easily switch between SMTP servers if the first one is not available for some reason. – Catch22 Aug 01 '12 at 13:37
  • 2
    Great. I changed `Dns.GetHostEntry` to `Dns.GetHostAddresses` which should be faster and will not fail if IP address is passed and no reverse dns entry found. – Jan Zahradník Mar 09 '15 at 12:36
  • 1
    Note, that this code will randomly fail due to use of `socket.Available`. – usr Jun 27 '16 at 08:58
  • Is there more code involved not shown here? The C# compiler does not know what a Configuration is. I am assuming it's part of the Configuration class in the application. What do I need to do to resolve the compilation error? I am using Visual Studio 2015. – octopusgrabbus Apr 18 '17 at 17:46
  • @octopusgrabbus You need to add a reference `System.Configuration` and a `using System.Configuration;` statement to your class. Also needs `using` statments for `System`, `System.Net`, `System.Net.Configuration`, `System.Net.Sockets`, `System.Text`. – vapcguy May 05 '17 at 14:30
  • As @Jan Zahradník indicated in my case I use `IPAddress.TryParse()` to detect if the string given is an IP or a host name and following the case I use `Dns.GetHostEntry` or `Dns.GetHostAddresses` in the function. – bau May 10 '19 at 10:09
8

I think this is a case where exception handling would be the preferred solution. You really don't know that it will work until you try, and failure is an exception.

Edit:

You'll want to handle SmtpException. This has a StatusCode property, which is an enum that will tell you why the Send() failed.

Jon B
  • 51,025
  • 31
  • 133
  • 161
  • but isn't it true that there could be other reasons why an exception is caught... and I know of this one specific possibility, and would like to handle it as it's own case... does that make sense? – adeena Dec 16 '08 at 21:18
  • I'm trying to implement transactional email sender and exceptions can't be used because the test should be in different part of transaction flow. – Jan Zahradník Mar 09 '15 at 12:39
2

You could try to send an HELO command to test if the server is active and running before to send the email. If you want to check if the user exists you could try with the VRFY command, but this is often disabled on SMTP servers due to security reasons. Further reading: http://the-welters.com/professional/smtp.html Hope this helps.

Jaime Febres
  • 1,267
  • 1
  • 10
  • 15
2

Catch the SmtpException exception, it will tell you if it failed because you couldn't connect to the server.

If you want to check if you can open a connection to the server before any attempt, Use TcpClient and catch SocketExceptions. Though I don't see any benefit to doing this vs just catching problems from Smtp.Send.

Darryl Braaten
  • 5,229
  • 4
  • 36
  • 50
  • 2
    Uh, because maybe it's onload of your app, or you need to verify connectivity to your SMTP server in a polling basis, and you don't have an e-mail to send yet and you want to test/ensure your ability to send, when you do - that would be why. – vapcguy May 04 '17 at 22:08
-1
    private bool isValidSMTP(string hostName)
    {
        bool hostAvailable= false;
        try
        {
            TcpClient smtpTestClient = new TcpClient();
            smtpTestClient.Connect(hostName, 25);
            if (smtpTestClient.Connected)//connection is established
            {
                NetworkStream netStream = smtpTestClient.GetStream();
                StreamReader sReader = new StreamReader(netStream);
                if (sReader.ReadLine().Contains("220"))//host is available for communication
                {
                    hostAvailable= true;
                }
                smtpTestClient.Close();
            }
        }
        catch
        {
          //some action like writing to error log
        }
        return hostAvailable;
    }
-2

I also had this need.

Here's the library I made (it send a HELO and checks for a 200, 220 or 250):

using SMTPConnectionTest;

if (SMTPConnection.Ok("myhost", 25))
{
   // Ready to go
}

if (SMTPConnectionTester.Ok()) // Reads settings from <smtp> in .config
{
    // Ready to go
}
talles
  • 14,356
  • 8
  • 45
  • 58