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.
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 :
- How work a smtp server and how communicate within it.
- 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 :
- D. J. Bernstein : https://cr.yp.to/smtp.html
- Wikipédia : https://en.wikipedia.org/wiki/Smtp
- Using Telnet : https://thedaneshproject.com/posts/send-mail-through-smtp-using-telnet/
- Interesting paper : https://email.about.com/cs/standards/a/smtp.htm
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 :
- 220 : Service Ready.
- 250 : Request completed.
- 334 : Waiting for Authentication.
- 235 : Authentication successful.
- 354 : Start mail input.
- 221 : Closing connection.
All the communication process between a client and a server can be described like this :
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
Comments
Anonymous
April 29, 2014
Thank you I searhed for this I will try now Thank you so muchAnonymous
July 01, 2014
Thanks a lot buddy. Finally I've something which works! Can you help me getting inbox messages and notifications on new email in windows 8.1 app using C# & XAML?Anonymous
August 07, 2014
Thanks you so much. Finally got something that works.. :) awesome work.Anonymous
September 01, 2014
The comment has been removedAnonymous
February 19, 2015
Thank You so much. Can you please help me that how to attached photo ?Anonymous
May 23, 2015
Thank you so much for this great article.. Its really a great job. But can we add a feature of sending attachment with this email? if possible then how?Anonymous
June 04, 2015
Can you please post the example how to attach file ?Anonymous
September 03, 2015
Hey Sebastien, thank you for the awesome work. I found a small bug you and the users visiting the site might be interested in. The problem: The date of the message does not take timezones into account. For example if I live in UTC +0200 timezone and send a message at 21:00 local time, the receiver will read that the date of the message is 23:00 +0000. Some email clients (such as outlook.com) are smart enough to correct this in the visualization, but some (like gmail or the default iOS mail app) are not, and will show a wrong date. The easy fix: In SmtpMessage.cs, in the definition of GetBody() change var dateFormat = "ddd, dd MMM yyyy HH:mm:ss +0000"; sb.AppendFormat("Date: {0}{1}", DateTime.Now.ToString(dateFormat), System.Environment.NewLine); to var dateFormat = "ddd, dd MMM yyyy HH:mm:ss +0000"; sb.AppendFormat("Date: {0}{1}", DateTime.UtcNow.ToString(dateFormat), System.Environment.NewLine); In my example, if I use UtcNow, the receiver will see 19:00 +0000 which is equivalent to 21:00 +0200. This means that all visualizers in his client will show the correct date. Anyway the message source should show 21:00 +0200, and this can only be achieved by changing dateFormat using the active timezone.Anonymous
February 01, 2016
Can You Please Describe How To add Html <br /> and href Tag in Body Content Send To User.Anonymous
February 07, 2016
while sending the mail with outlook server the mail is Queued and I get this message ""250 2.6.0 <BLU437-SMTP42cM3ory00004cff@BLU437-SMTP42.smtp.hotmail.com> Queued mail for delivery". And while sending it with gmail I receive and email from Gmail saying that someone is traying to connect to my account from unsecured app. What should I do in these cases?Anonymous
March 13, 2016
Hi, It is working fine. I want to know, how to add bcc?Anonymous
June 20, 2016
The comment has been removed