23

How can I test SMTP is up and running via C# without sending a message.

I could of course try:

try{
// send email to "nonsense@example.com"
}
catch
{
// log "smtp is down"
}

There must be a more tidy way to do this.

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
frosty
  • 5,330
  • 18
  • 85
  • 122

5 Answers5

69

You can try saying EHLO to your server and see if it responds with 250 OK. Of course this test doesn't guarantee you that you will succeed sending the mail later, but it is a good indication.

And here's a sample:

class Program
{
    static void Main(string[] args)
    {
        using (var client = new TcpClient())
        {
            var server = "smtp.gmail.com";
            var port = 465;
            client.Connect(server, port);
            // As GMail requires SSL we should use SslStream
            // If your SMTP server doesn't support SSL you can
            // work directly with the underlying stream
            using (var stream = client.GetStream())
            using (var sslStream = new SslStream(stream))
            {
                sslStream.AuthenticateAsClient(server);
                using (var writer = new StreamWriter(sslStream))
                using (var reader = new StreamReader(sslStream))
                {
                    writer.WriteLine("EHLO " + server);
                    writer.Flush();
                    Console.WriteLine(reader.ReadLine());
                    // GMail responds with: 220 mx.google.com ESMTP
                }
            }
        }
    }
}

And here's the list of codes to expect.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Liking it - liking it a lot! Good one Darin! – Vidar Oct 27 '10 at 19:46
  • @Darin Dimitrov tanx for your answer.. is there anyway to ckeck username & password ( smtp authentication ) is valid? i asked it in http://stackoverflow.com/questions/17319276/how-can-i-check-smtp-server-without-sending-email .. please help me – Mehdi Yeganeh Jun 26 '13 at 12:13
  • 4
    This seems insufficient. To properly test SMTP settings, you should at least do an `RCPT TO` with some external email address (ex. `postmaster@example.com`), but bail out before sending `DATA`. This will actually test that you have relay permissions, in effect validating your username/password and/or client certificate. – Jonathan Amend Sep 18 '14 at 16:10
10

I use this method and classes to validate the credentials (link to github):

public static bool ValidateCredentials(string login, string password, string server, int port, bool enableSsl) {
        SmtpConnectorBase connector;
        if (enableSsl) {
            connector = new SmtpConnectorWithSsl(server, port);
        } else {
            connector = new SmtpConnectorWithoutSsl(server, port);
        }

        if (!connector.CheckResponse(220)) {
            return false;
        }

        connector.SendData($"HELO {Dns.GetHostName()}{SmtpConnectorBase.EOF}");
        if (!connector.CheckResponse(250)) {
            return false;
        }

        connector.SendData($"AUTH LOGIN{SmtpConnectorBase.EOF}");
        if (!connector.CheckResponse(334)) {
            return false;
        }

        connector.SendData(Convert.ToBase64String(Encoding.UTF8.GetBytes($"{login}")) + SmtpConnectorBase.EOF);
        if (!connector.CheckResponse(334)) {
            return false;
        }

        connector.SendData(Convert.ToBase64String(Encoding.UTF8.GetBytes($"{password}")) + SmtpConnectorBase.EOF);
        if (!connector.CheckResponse(235)) {
            return false;
        }

        return true;
    }

SmtpConnectorBase:

internal abstract class SmtpConnectorBase {
    protected string SmtpServerAddress { get; set; }
    protected int Port { get; set; }
    public const string EOF = "\r\n";

    protected SmtpConnectorBase(string smtpServerAddress, int port) {
        SmtpServerAddress = smtpServerAddress;
        Port = port;
    }

    public abstract bool CheckResponse(int expectedCode);
    public abstract void SendData(string data);
}

SmtpConnectorWithoutSsl:

internal class SmtpConnectorWithoutSsl : SmtpConnectorBase {
    private Socket _socket = null;

    public SmtpConnectorWithoutSsl(string smtpServerAddress, int port) : base(smtpServerAddress, port) {
        IPHostEntry hostEntry = Dns.GetHostEntry(smtpServerAddress);
        IPEndPoint endPoint = new IPEndPoint(hostEntry.AddressList[0], port);
        _socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        //try to connect and test the rsponse for code 220 = success
        _socket.Connect(endPoint);

    }

    ~SmtpConnectorWithoutSsl() {
        try {
            if (_socket != null) {
                _socket.Close();
                _socket.Dispose();
                _socket = null;
            }
        } catch (Exception) {
            ;
        }

    }

    public override bool CheckResponse(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.UTF8.GetString(responseArray);
        int responseCode = Convert.ToInt32(responseData.Substring(0, 3));
        if (responseCode == expectedCode) {
            return true;
        }
        return false;
    }

    public override void SendData(string data) {
        byte[] dataArray = Encoding.UTF8.GetBytes(data);
        _socket.Send(dataArray, 0, dataArray.Length, SocketFlags.None);
    }
}

SmtpConnectorWithSsl:

internal class SmtpConnectorWithSsl : SmtpConnectorBase {
    private SslStream _sslStream = null;
    private TcpClient _client = null;

    public SmtpConnectorWithSsl(string smtpServerAddress, int port) : base(smtpServerAddress, port) {
        TcpClient client = new TcpClient(smtpServerAddress, port);

        _sslStream = new SslStream(
            client.GetStream(),
            false,
            new RemoteCertificateValidationCallback(ValidateServerCertificate),
            null
            );
        // The server name must match the name on the server certificate.
        try {
            _sslStream.AuthenticateAsClient(smtpServerAddress);
        } catch (AuthenticationException e) {
            _sslStream = null;
            Console.WriteLine("Exception: {0}", e.Message);
            if (e.InnerException != null) {
                Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
            }
            Console.WriteLine("Authentication failed - closing the connection.");
            client.Close();
        }
    }

    ~SmtpConnectorWithSsl() {
        try {
            if (_sslStream != null) {
                _sslStream.Close();
                _sslStream.Dispose();
                _sslStream = null;
            }
        } catch (Exception) {
            ;
        }

        try {
            if (_client != null) {
                _client.Close();
                _client = null;
            }
        } catch (Exception) {
            ;
        }
    }

    // The following method is invoked by the RemoteCertificateValidationDelegate.
    private static bool ValidateServerCertificate(
          object sender,
          X509Certificate certificate,
          X509Chain chain,
          SslPolicyErrors sslPolicyErrors) {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

        Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

        // Do not allow this client to communicate with unauthenticated servers.
        return false;
    }

    public override bool CheckResponse(int expectedCode) {
        if (_sslStream == null) {
            return false;
        }
        var message = ReadMessageFromStream(_sslStream);
        int responseCode = Convert.ToInt32(message.Substring(0, 3));
        if (responseCode == expectedCode) {
            return true;
        }
        return false;
    }

    public override void SendData(string data) {
        byte[] messsage = Encoding.UTF8.GetBytes(data);
        // Send hello message to the server. 
        _sslStream.Write(messsage);
        _sslStream.Flush();
    }

    private string ReadMessageFromStream(SslStream stream) {
        byte[] buffer = new byte[2048];
        StringBuilder messageData = new StringBuilder();
        int bytes = -1;
        do {
            bytes = stream.Read(buffer, 0, buffer.Length);

            // Use Decoder class to convert from bytes to UTF8
            // in case a character spans two buffers.
            Decoder decoder = Encoding.UTF8.GetDecoder();
            char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
            decoder.GetChars(buffer, 0, bytes, chars, 0);
            messageData.Append(chars);
            // Check for EOF.
            if (messageData.ToString().IndexOf(EOF) != -1) {
                break;
            }
        } while (bytes != 0);

        return messageData.ToString();
    }
}
  • 1
    there is a infinite loop in SmtpConnectorWithoutSsl . In CheckResponse method the line while (_socket.Available == 0) should have a limit. I hit a scenario where _socket.Available was never going to equal anything other than 0. but, aside from that - this works perfect. been using it for about 6 months without issue. – Heriberto Lugo Dec 29 '17 at 17:36
  • @Samidjo I'm not sure where you see that variant at, but I do not see it anywhere in the code that produced the infinite loop. Writing this from my phone. And if 100 is default, it did not stop at 100 from what I remember. It was infinite loop in my use case. – Heriberto Lugo Feb 09 '20 at 19:23
  • @Samidjo my suggestion is try it. Set the line I stated to always = 0, and print out a count. Im willing to bet it will count past 100. – Heriberto Lugo Feb 09 '20 at 19:27
  • @HeribertoLugo take the new version, above is an old one. Go to the git repository, you will see the variable MAX_ATTEMPTS_COUNT. – Samidjo Feb 16 '20 at 11:08
  • @Samidjo not sure why you deleted your other comment, looks like im talking to myself. The code doesnt matter. This was years ago and I already fixed it where I needed it. No need to change code to new one. It works and thre minor issue was resolved. I made my comment to give others a heads up who might grab the code from here just as I did. – Heriberto Lugo Feb 16 '20 at 18:33
4

You could open up the port (25) with a socket or TcpClient and see if it responds.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
4

Open a socket connection to the smtp server on port 25 and see if you get anything. If not, no smtp server.

Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
0

Here is a nice open source tool (does more than MX): http://www.codeproject.com/KB/IP/DNS_NET_Resolver.aspx

Sarah
  • 19
  • 1