Sending an email within a Windows 8.1 application : using StreamSocket to emulate a SmtpClient

Hi,

Today we will see how to send a simple email from a Windows 8.1 Modern UI application.

image

For now, there is no API in the WinRT stack dedicated to mail. The System.Net.Mail namespace, containing classes like SmtpClient or MailMessage, is not present in WinRT, like it is in the .NET Framework.

Here is the source code of this articl : SendMail.zip

To Solve this problem, we need to know :

  1. How work a smtp server and how communicate within it.
  2. How send and receive messages to and from a smtp Server, with the StreamSocket API.

Smtp Communication

First of all, some interesting reading. Here are some interesting articles on the smtp protocol :

In this article, we’ll just send a simple mail, using our own SmtpClient, communicating with an existing smtp server (Outlook.com or Gmail.com) using some key words like :

  • Ehlo : Send a command to the smtp server to know some properties like authentication mode, SSL, TSL support etc …
  • Auth Login : Send a command requesting authentication on the smtp server.
  • StartTls : Send a command requesting a TSL communication.
  • Mail From : Send a command with the mail author.
  • Rcpt To : Send a command with one or more receivers.
  • Data : Send a command for the body mail.
  • Quit : Send a command ending the connection with the smtp server.

The smtp server in turn, will respond to us, using some status string (int code) with one or more string rows. Each message are specific for each smtp server meanwhile each status string is shared by all smtp server (come from the smtp specification)

Here are some codes that we will use in our sample :

  1. 220 : Service Ready.
  2. 250 : Request completed.
  3. 334 : Waiting for Authentication.
  4. 235 : Authentication successful.
  5. 354 : Start mail input.
  6. 221 : Closing connection.

All the communication process between a client and a server can be described like this :

image6

For example, here is a complete communication between a client (C) and the gmail.com smtp server (S) :

Connect smtp.google.com 465
S : 220 mx.google.com ESMTP o47sm20731478eem.21 - gsmtp
C : EHLO www.contoso.com
S : 250-mx.google.com at your service, [94.245.87.37]
    250-SIZE 35882577
    250-8BITMIME
    250-AUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN
    250-ENHANCEDSTATUSCODES
    250 CHUNKING
C : LOGIN AUTH
S : 334 VXNlcm5hbWU6
C : john.doe@gmail.com
S : 334 UGFzc3dvcmQ6
C : MyPassword@5o5tr0ng
S : 235 2.7.0 Accepted
C : MAIL FROM:<john.doe@gmail.com>
S : 250 2.1.0 OK o47sm20731478eem.21 - gsmtp
C : RCPT TO:<spertus@microsoft.com>
S : 250 2.1.5 OK o47sm20731478eem.21 - gsmtp
C : DATA
S : 354  Go ahead o47sm20731478eem.21 - gsmtp
C : Date: Wed, 27 Nov 2013 16:47:26 +0000
    X-Priority: 0
    To: spertus@microsoft.com
    MIME-Version: 1.0
    Content-Transfer-Encoding: 7bit
    Content-Disposition: inline
    Subject: Hi Guy !
    Content-Type: text/plain; charset="utf-8"

    Hi Sebastien, how are you ??
    John D.
    .
S : 250 2.0.0 OK 1385567306 o47sm20731478eem.21 - gsmtp
C : QUIT
S : 221 2.0.0 closing connection o47sm20731478eem.21 – gsmtp

and here the same communication between a client (C) and the Outlook.com smtp server (S) :

Connect smtp-mail.outlook.com 587
S : 220 BLU0-SMTP180.phx.gbl Microsoft ESMTP MAIL Service, Version: 6.0.3790.4675 ready at  Wed, 27 Nov 2013 08:28:59 -0800
C : EHLO www.contoso.com
S : 250-BLU0-SMTP180.phx.gbl Hello [94.245.87.37]
    250-TURN
    250-SIZE 41943040
    250-ETRN
    250-PIPELINING     
    250-DSN
    250-ENHANCEDSTATUSCODES
    250-8bitmime
    250-BINARYMIME
    250-CHUNKING
    250-VRFY
    250-TLS
    250-STARTTLS
    250 OK
C : STARTTLS
S : 220 2.0.0 SMTP server ready
C : EHLO www.contoso.com
S : 250-BLU0-SMTP180.phx.gbl Hello [94.245.87.37]
    250-TURN
    250-SIZE 41943040
    250-ETRN
    250-PIPELINING
    250-DSN
    250-ENHANCEDSTATUSCODES
    250-8bitmime
    250-BINARYMIME
    250-CHUNKING
    250-VRFY
    250-AUTH LOGIN PLAIN XOAUTH2
    250 OK
C : AUTH LOGIN
S : 334 VXNlcm5hbWU6
C : john.doe@outlook.com
S : 334 UGFzc3dvcmQ6
C : MyF4bulousP@zzw0rd
S : 235 2.7.0 Authentication succeeded
C : MAIL FROM:<john.doe@outlook.com>
S : 250 2.1.0 john.doe@outlook.com....Sender OK
C : RCPT TO:<spertus@microsoft.com>
S : 250 2.1.5 spertus@microsoft.com
C : DATA
S : 354 Start mail input; end with <CRLF>.<CRLF>
C : Date: Wed, 27 Nov 2013 17:28:56 +0000
    X-Priority: 0
    To: sebastien.pertus@gmail.com, spertus@microsoft.com
    MIME-Version: 1.0
    Content-Transfer-Encoding: 7bit
    Content-Disposition: inline
    Subject: Hi Guy.
    Content-Type: text/plain; charset="utf-8"

    Hi Sebastien, how are you ??
     John D.
    .
S : 250 2.6.0 <BLU0-SMTP180Np7vXee0000a899@BLU0-SMTP180.phx.gbl> Queued mail for delivery
C : QUIT
S : 221 2.0.0 BLU0-SMTP180.phx.gbl Service closing transmission channel

For your information : In these two examples, the User name and the password are written in clear text. In the smtp Protocol you must send them in base 64.

StreamSocket

Communicating with a smtp server within a Windows 8.1 application can be done with the StreamSocket API.

Connection

Connecting a smtp server with StreamSocket is straightforward:

 if (this.isSsl)
    await socket.ConnectAsync(this.hostName, this.port.ToString(), SocketProtectionLevel.Ssl);
else
    await socket.ConnectAsync(this.hostName, this.port.ToString(), SocketProtectionLevel.PlainSocket);

Once the connections established, you can easily upgrade to SSL, like this :

 await socket.UpgradeToSslAsync(SocketProtectionLevel.Ssl, this.hostName);

Reading / Writing

The StreamSocket object is exposing two properties that can be used to read the input Stream (InputStream) and write in the output Stream (OutputStream)

Those two properties can be managed by two dedicated objects, respectfully the DataReader object and the DataWriter object (namespace Windows.Storage.Streams)

 this.reader = new DataReader(socket.InputStream);
this.reader.InputStreamOptions = InputStreamOptions.Partial;

this.writer = new DataWriter(socket.OutputStream);

First of all, and by the way the most simple operation : Writing in the output Stream :

 public async Task Send(String command)
{
    Debug.WriteLine(command);
    return await this.Send(Encoding.UTF8.GetBytes(command + System.Environment.NewLine), command);
}

public async Task Send(Byte[] bytes, string command)
{
    try
    {
        writer.WriteBytes(bytes);
        await writer.StoreAsync();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(command + ":" + ex.Message);
        return null;
    }

}

Reading the Input Stream is a little bit more complicated (but, not so complicated :) )

Initially, because we are in a streaming mode, we don’t know the stream length. We need to read the steam until we reach the end of the stream. The property UnconsummedBufferLength will help us to check how many bytes we don’t have read in the actual buffer.

We choose a buffer size (1024) and we read while … we have something to read !

 private async Task<MemoryStream> GetResponseStream()
{
    MemoryStream ms = new MemoryStream();

    while (true)
    {
        await reader.LoadAsync(bufferLength);

        if (reader.UnconsumedBufferLength == 0) { break; }

        Int32 index = 0;
        while (reader.UnconsumedBufferLength > 0)
        {
            ms.WriteByte(reader.ReadByte());
            index = index + 1;
        }

        if (index == 0 || index < bufferLength)
            break;
    }

    ms.Seek(0, SeekOrigin.Begin);
    return ms;
}

In my particular case, I can read multiple line, i m using a StreamReader :

 private async Task<List<String>> GetResponse()
{
    List<String> lines = new List<String>();
    using (MemoryStream ms = await GetResponseStream())
    {
        using (StreamReader sr = new StreamReader(ms))
        {
            while (!sr.EndOfStream)
            {
                var line = sr.ReadLine();

                if (String.IsNullOrEmpty(line))
                    break;

                lines.Add(line);
            }
        }
    }
    return lines;
}

For your information : In the sample provided within this article, you will find a more complex method, which will parse the stream in a single pass.

Smtp Communication

Each time we are sending a command message, we have to get the response from the server, before sending an other command.

Pretty Straightforward now that we have implemented the Send() method and the GetResponse() method:

 await this.smtpSocket.Send("EHLO " + this.Server);
var r = this.smtpSocket.GetResponse();

After a quick refactoring, integrating the GetResponse() in the Send() method, we are able to send and get the response within one line of code, like this :

 var r  = this.smtpSocket.Send("EHLO " + this.Server);

Sending an email

Once you know how to send and get a response, and what kind of messages you can send to the smtp server, creating an email and sending is pretty … straightforward :

 public async Task<Boolean> SendMail(SmtpMessage message)
{

    if (!this.IsConnected)
        await this.Connect();

    if (!this.IsConnected)
        throw new Exception("Can't connect");

    if (!this.IsAuthenticated)
        await this.Authenticate();

    var rs = await this.smtpSocket.Send(String.Format("Mail From:<{0}>", message.From));

    if (!rs.ContainsStatus(SmtpCode.RequestedMailActionCompleted))
        return false;

    foreach (var to in message.To)
    {
        var toRs = await this.smtpSocket.Send(String.Format("Rcpt To:<{0}>", to));

        if (!toRs.ContainsStatus(SmtpCode.RequestedMailActionCompleted))
            break;
    }

    var rsD = await this.smtpSocket.Send(String.Format("Data"));

    if (!rsD.ContainsStatus(SmtpCode.StartMailInput))
        return false;

    var rsM = await this.smtpSocket.Send(message.GetBody());

    if (!rsM.ContainsStatus(SmtpCode.RequestedMailActionCompleted))
        return false;

    var rsQ = await this.smtpSocket.Send("Quit");

    if (!rsQ.ContainsStatus(SmtpCode.ServiceClosingTransmissionChannel))
        return false;

    return true;
}

You will find in the sample code attached with this article, the full code of the Authenticate() and Connect() methods.

About the mail body, you need to send some metadatas like priority, encoding, subject and body (of course) and terminating with a <CRLF>.<CRLF> :

 public String GetBody()
{
    StringBuilder sb = new StringBuilder();

    var dateFormat = "ddd, dd MMM yyyy HH:mm:ss +0000";
    sb.AppendFormat("Date: {0}{1}", DateTime.Now.ToString(dateFormat), System.Environment.NewLine);

    if (String.IsNullOrEmpty(this.From))
        throw new Exception("From is mandatory");

    sb.AppendFormat("X-Priority: {0}{1}", ((byte)this.Priority).ToString(), System.Environment.NewLine);

    if (this.to.Count == 0)
        throw new Exception("To is mandatory");

    sb.Append("To: ");
    for (int i = 0; i < this.to.Count; i++)
    {
        var to = this.to[i];
        if (i == this.to.Count - 1)
            sb.AppendFormat("{0}{1}", to, System.Environment.NewLine);
        else 
            sb.AppendFormat("{0}{1}", to, ", ");

    }
    foreach (var to in this.To)
  
    if (this.cc.Count != 0)
    {
        sb.Append("Cc: ");
        for (int i = 0; i < this.cc.Count; i++)
        {
            var cc = this.cc[i];
            if (i == this.cc.Count - 1)
                sb.AppendFormat("{0}{1}", cc, System.Environment.NewLine);
            else
                sb.AppendFormat("{0}{1}", cc, ", ");

        }
    }

    sb.AppendFormat("MIME-Version: 1.0{0}", System.Environment.NewLine);
    sb.AppendFormat("Content-Transfer-Encoding: {0}{1}", this.TransferEncoding, System.Environment.NewLine);
    sb.AppendFormat("Content-Disposition: inline{0}", System.Environment.NewLine);
    sb.AppendFormat("Subject: {0}{1}" , this.Subject, System.Environment.NewLine);

    if (this.IsHtml)
        sb.AppendFormat("Content-Type: text/html; {0}", System.Environment.NewLine);
    else
        sb.AppendFormat("Content-Type: text/plain; charset=\"{0}\"{1}", this.Encoding.WebName, System.Environment.NewLine);

    sb.Append(System.Environment.NewLine);
    sb.Append(this.Body);
    sb.Append(System.Environment.NewLine);
    sb.Append(".");

    return sb.ToString();

}

Using our own SmptClient API

Finally, using our implementation is straightforward (yes again). Here is the final code using two smtp servers (Gmail.com and Outlook.com) :

 // Outlook.com
// HostName : smtp-mail.outlook.com
// Port : 587
// SSL : No (upgarde ssl after STARTTLS)

// Gmail.com
// HostName : smtp.gmail.com
// Port : 465
// SSL : Yes

// Gmail
//SmtpClient client = new SmtpClient("smtp.gmail.com", 465, 
//                                   "your_mail@gmail.com", "password", true);

// Outlook
SmtpClient client = new SmtpClient("smtp-mail.outlook.com", 587, 
                                    "your_mail@outlook.com", "password", false);
         
SmtpMessage message = new SmtpMessage("your_mail@outlook.com", 
                                        "john.doe@contoso.com", null, "sujet", "corps du mail");
message.To.Add("spertus@microsoft.com");

await client.SendMail(message);

Conclusion

This sample is a quick sample. There is still a lot of work to be done. For example, supporting attached file or large email body (CHUNKING)

Meanwhile, it’s a good starting point to understand how work a smtp server and how work StreamSocket within WinRT !

Happy mailing :)

//seb

SendMail.zip