In this article I'll try to explain how we can send emails using SMTP servers.
First of all, what is SMTP what is MTA, MUA, protocol? Let's start talking about these subjects.
|
Protocol Protocol is a group of rules which is used by applications that communicate each other from network. SMTP Simple Mail Transfer Protocol. Is a protocol used to transfer emails.First RFC was published in August 1982.We can say that when we compose a message in an outlook application and send it, the outlook application sends this email to mail server by using the smtp protocol. MTA Mail Transfer Agent. Is the mail servers which communicates both with other email servers and clients to fetch emails.For example : Qmail,Postfix,Courier... MUA Mail User Agent Is the applications that lets users to read & compose emails.For example: Outlook Express |
Here you can find a diagram that explains the network topology.
This diagram shows the communication between the MTA and MUA
What are the rules of SMTP protocol?
SMTP is a protocol (application free) therefore we are able to send an email without using C# which i prefer to show this using telnet.Now we'll send an email to [email protected] by using telnet.Our mail transfer agent's address is mail.somemta.com and we'll send this email by using our existing account [email protected] .
Run the command prompt and enter the following line;
C:\>telnet mail.somemta.com 25
When we enter this command the mta will respond us as follows;
220 mail.somemta.com ESMTP
To finish handshake with the mta enter the following line;
HELO mail.somemta.com
The mta will respond us, telling us that handshaking is ok;
250 mail.somemta.com
At this stage we will inform mta about the email's sender and recipient.
To inform the sender account enter the following line;
mail from:[email protected]
Mta will respond us telling us tht he has received the information.
250 ok
Now we'll send the recipient information by entering the following line;
rcpt to:[email protected]
Mta should respond us as follows;
250 ok
Hope you don't have any problems J
Now we are ready to write the content of our email.The information we'll enter here will be shown in mua.We declare mta tht we are ready to enter the email content by entering the following line;
data
Mta should respond as follows;
354 go ahead
Till now mta was responding us on each of our command by sending us the message "250 ok"
But after we enter the "data" command mta won't respond our comments until we enter the escape command.The escape command is "<Enter> . <Enter>" (press enter , press . and enter , press enter)
Let's write the contentFrom:Levent YILDIZ ACME Inc.
To:Levent YILDIZ MINDSTORM Inc.
Subject:our first mail sent by C#
This mail has been sent by C#
.
After we enter the escape command mta should respond as follows;
250 ok 1118680536 qp 21248
This ok message can vary by the type of the mta.The important section is the "250 ok" part.
Now we can check our email.
If you have experienced any problems till this stage, that problems may have been occured by the securty properties of your mta.Most of the mta systems use "POP-before-SMTP" securty which does not allow users to connect to SMTP port if the user has not been granted by the POP protocol.I advise you to get support from your mail administrator.
So let's get to the point.How we will achieve this by C#?Below code is all you need.Take a look at it...
using System;
using System.Net.Sockets;
using System.IO;
namespace SendMailviaSMTP
{
public class SMTPMailSender
{
string mvarSMTPServerAddress;
string mvarSenderName;
string mvarSenderEmailAddress;
string mvarRecipientName;
string mvarRecipientEmailAddress;
string mvarEmailSubject;
string mvarEmailBody;
int mvarSMTPTimeOut;
int mvarSMTPRemotePort;
TcpClient tclSMTP;
NetworkStream nstSMTP;
StreamReader strSMTP;
StreamWriter stwSMTP;
DateTime dteTimeOutCheck;
public SMTPMailSender()
{
mvarSMTPTimeOut = 60;
mvarSMTPRemotePort = 25;
}
public bool SendEmail()
{
//SMTP sunucusu ile bağlantı kuruluyor
//Connecting to SMTP Server
tclSMTP=new TcpClient();
try
{
tclSMTP.Connect(mvarSMTPServerAddress,mvarSMTPRemotePort);
}
catch
{
return false;
}
nstSMTP=tclSMTP.GetStream();
stwSMTP=new StreamWriter(nstSMTP);
strSMTP=new StreamReader(nstSMTP);
//mta'dan karşılama mesajı bekleniyor
//waiting for greeting message from MTA
if (WaitForResponse("220"))
{
//mta'ya karşılama mesajı gönderiliyor
//sending greeting message to MTA
stwSMTP.WriteLine("HELO " + mvarSMTPServerAddress);
stwSMTP.Flush();
}
else
{
tclSMTP.Close();
return false;
}
if (WaitForResponse("250"))
{
//gönderici email adresi gönderiliyor
//sending sender email address to MTA
stwSMTP.WriteLine("mail from:" + mvarSenderEmailAddress);
stwSMTP.Flush();
}
else
{
tclSMTP.Close();
return false;
}
if (WaitForResponse("250"))
{
//alıcı email adresi gönderiliyor
//sending recipient email address to MTA
stwSMTP.WriteLine("rcpt to:" + mvarRecipientEmailAddress);
stwSMTP.Flush();
}
else
{
tclSMTP.Close();
return false;
}
if (WaitForResponse("250"))
{
//mail'i yazmak için data moduna geçiliyor
//switching to data mode for entering body of the mail
stwSMTP.WriteLine("data");
stwSMTP.Flush();
}
else
{
tclSMTP.Close();
return false;
}
if (WaitForResponse("354"))
{
//mesajin body kısmı hazırlanıyor
//preparing the body section of the email
string strSMTPData="";
strSMTPData="From:" + mvarSenderName + Environment.NewLine;
strSMTPData+="To:" + mvarRecipientName + Environment.NewLine;
strSMTPData+="Subject:" + mvarEmailSubject + Environment.NewLine + Environment.NewLine;
strSMTPData+=mvarEmailBody + Environment.NewLine + "." + Environment.NewLine;
//mesaj gönderiliyor
//sending message
stwSMTP.Write(strSMTPData);
stwSMTP.Flush();
}
else
{
tclSMTP.Close();
return false;
}
if (WaitForResponse("250"))
{
//gönderim başarılı ise değer true döndürülüyor
//returns true if the send process succeeds
tclSMTP.Close();
return true;
}
else
{
tclSMTP.Close();
return false;
}
//gönderim başarısız ise değer false döndürülüyor
//returns false if the send process fails
tclSMTP.Close();
return false;
}
bool WaitForResponse(string strResponseCode)
{
//zamanaşımı kontrolü için mevcut tarih saat bilgisi alınıyor
//gathering current date time data for timeout check
dteTimeOutCheck=DateTime.Now;
//zamanaşımı değeri bulunuyor
//calculating timeout value
TimeSpan tsp=DateTime.Now-dteTimeOutCheck;
//zamanaşımı değeri kullanıcının belirlediği değeri aşıncaya kadar döngü çalıştırılıyor
//looping code until the timeout exceeds the user defined value
while(tsp.Seconds<mvarSMTPTimeOut)
{
//MTA bize herhangi bir mesaj göndermiş mi kontrol ediliyor.
//checking if MTA has sent a message to us
if (nstSMTP.DataAvailable)
{
//eğer göndermişşse mesaj okunuyor
//if so, get the message
string strIncomingData=strSMTP.ReadLine();
//gelen bilginin protokol numarası istenen koda uyuyormu kontrol ediliyor
//checking if the requested protocol number matches the server response
if (strIncomingData.Substring(0,strResponseCode.Length)==strResponseCode)
return true;
}
//zamanaşımı değeri yeniden hesaplanıyor
//recalculating the timeout value
tsp=DateTime.Now-dteTimeOutCheck;
}
return false;
}
public string SMTPServerAddress
{
get{return mvarSMTPServerAddress;}
set{mvarSMTPServerAddress=value;}
}
public string SenderName
{
get{return SenderName;}
set{mvarSenderName=value;}
}
public string SenderEmailAddress
{
get{return SenderEmailAddress;}
set{mvarSenderEmailAddress=value;}
}
public string RecipientName
{
get{return RecipientName;}
set{mvarRecipientName=value;}
}
public string RecipientEmailAddress
{
get{return RecipientEmailAddress;}
set{mvarRecipientEmailAddress=value;}
}
public string EmailSubject
{
get{return EmailSubject;}
set{mvarEmailSubject=value;}
}
public string EmailBody
{
get{return EmailBody;}
set{mvarEmailBody=value;}
}
public int SMTPTimeOut
{
get{return SMTPTimeOut;
}
set{mvarSMTPTimeOut=value;
}
}
public int SMTPRemotePort
{
get{return SMTPRemotePort;}
set{mvarSMTPRemotePort=value;}
}
}
}
Namespaces we used;
System
System.Net.Sockets
System.IO
We used sockets ns to connect to MTA by tcpclient class, IO ns to receive and send the information arrived to netstream object.
You can find the members of our class below
Member Name |
Description |
mvarSMTPServerAddress |
"fully qualified domain name" or the ip address of the MTA (string) |
mvarSenderName |
The sender name that will shown in MUA (string) |
mvarSenderEmailAddress |
The sender email account that we'll inform to MTA. (string) |
mvarRecipientName |
The recipient name that will shown in MUA (string) |
mvarRecipientEmailAddress |
The recipient email account that we'll inform to MTA (string) |
mvarEmailSubject |
Subject of our email (string) |
mvarEmailBody |
The body of our email (string) |
mvarSMTPTimeOut |
Timeout in seconds. (int, default=60) |
mvarSMTPRemotePort |
The port number of the MTA (int, default=25) |
SendEmail |
The method that sends the email. (bool) |
We've set the default values of our two properties in our default constructor method.
public SMTPMailSender()
{
mvarSMTPTimeOut = 60;
mvarSMTPRemotePort = 25;
}
Let's have a look at the heart of our class, the SendEmail() method.
- We connect to MTA
The method will return a false value at any line if any error occurs.This will help us to know that the mail is sent successfully or not.
tclSMTP=new TcpClient();
try
{
tclSMTP.Connect(mvarSMTPServerAddress,mvarSMTPRemotePort);
}
catch
{
return false;
}
If you look at the code above the method will return false value if the connection to the mta fails.
- After our connection is ready, we create an instance of the networkstream to handle the incoming/outgoing tcpip traffic.To send and receive string information via the networkstream object, we create the streamwriter and the streamreader classes.
nstSMTP=tclSMTP.GetStream();
stwSMTP=new StreamWriter(nstSMTP);
strSMTP=new StreamReader(nstSMTP);
- All of our tools for communicating with mta is ready so we can make a start.As i have mentioned in the beginning of this article we'll use the SMTP protocol.
I'll explain the WaitForResponse method later so it is ok for you to know that this method waits for the mta response.
//mta'dan karşılama mesajı bekleniyor
//waiting for greeting message from MTA
if (WaitForResponse("220"))
{
//mta'ya karşılama mesajı gönderiliyor
//sending greeting message to MTA
stwSMTP.WriteLine("HELO " + mvarSMTPServerAddress);
stwSMTP.Flush();
}
else
{
tclSMTP.Close();
return false;
}
As you can see above if we get the 220 response from the mta "HELO mail.somemta.com" message is being sent.If any problem occurs in the WaitForResponse method the connection will be closed and the SendEmail() method will return false.
The rest of the protocol messages will be send and the responses will be received just the same as the above code.The only difference will be the message that we send and the response we received.
Now we'll have a look at our WaitForResponse method;
bool WaitForResponse(string strResponseCode)
{
//zamanaşımı kontrolü için mevcut tarih saat bilgisi alınıyor
//gathering current date time data for timeout check
dteTimeOutCheck=DateTime.Now;
//zamanaşımı değeri bulunuyor
//calculating timeout value
TimeSpan tsp=DateTime.Now-dteTimeOutCheck;
//zamanaşımı değeri kullanıcının belirlediği değeri aşıncaya kadar döngü çalıştırılıyor
//looping code until the timeout exceeds the user defined value
while(tsp.Seconds<mvarSMTPTimeOut)
{
//MTA bize herhangi bir mesaj göndermiş mi kontrol ediliyor.
//checking if MTA has sent a message to us
if (nstSMTP.DataAvailable)
{
//eğer göndermişşse mesaj okunuyor
//if so, get the message
string strIncomingData=strSMTP.ReadLine();
//gelen bilginin protokol numarası istenen koda uyuyormu kontrol ediliyor
//checking if the requested protocol number matches the server response
if (strIncomingData.Substring(0,strResponseCode.Length)==strResponseCode)
return true;
}
//zamanaşımı değeri yeniden hesaplanıyor
//recalculating the timeout value
tsp=DateTime.Now-dteTimeOutCheck;
}
return false;
}
As you can see this method takes a string parameter, strResponseCode.This parameter represents the first 3 char of the response we wait from the MTA. Remember that when we connect with the telnet application we were sending commands and waiting for the mta's "250 ok" responses. By sending the first 3 char to this method we understand tht the mta has successfully received our message and sent us the ok command. If this method returns false we understand tht some problem occured and we cut off the connection.
At this stage the only thing we should consider is the timeout property and how we'll understand the timeout is exceeded or not. Mta can end the connection by any reason or we can have a network problem.If so how we'll solve this problem.If we do not create a timeout mechanizm and we have a network connectivity problem the WaitForResponse method will wait until we end our program.This will not be a good solution.
We should store the current datetime value to a variable and check if the difference exceeds the timeout value.
//zamanaşımı kontrolü için mevcut tarih saat bilgisi alınıyor
//gathering current date time data for timeout check
dteTimeOutCheck=DateTime.Now;
//zamanaşımı değeri bulunuyor
//calculating timeout value
TimeSpan tsp=DateTime.Now-dteTimeOutCheck;
//zamanaşımı değeri kullanıcının belirlediği değeri aşıncaya kadar döngü çalıştırılıyor
//looping code until the timeout exceeds the user defined value
while(tsp.Seconds<mvarSMTPTimeOut)
{
.....
.....
//zamanaşımı değeri yeniden hesaplanıyor
//recalculating the timeout value
tsp=DateTime.Now-dteTimeOutCheck;
}
We store the current datetime value to the dteTimeOutCheck variable. Using the TimeSpan class we can find out the difference between our dteTimeOutCheck value and the current datetime and if the difference exceeds the timeout value we return a false value telling the outer block that WaitForResponse method failed.
Have a look at the following code;
//MTA bize herhangi bir mesaj göndermiş mi kontrol ediliyor.
//checking if MTA has sent a message to us
if (nstSMTP.DataAvailable)
{
//eğer göndermişşse mesaj okunuyor
//if so, get the message
string strIncomingData=strSMTP.ReadLine();
//gelen bilginin protokol numarası istenen koda uyuyormu kontrol ediliyor
//checking if the requested protocol number matches the server response
if (strIncomingData.Substring(0,strResponseCode.Length)==strResponseCode)
return true;
}
//zamanaşımı değeri yeniden hesaplanıyor
//recalculating the timeout value
tsp=DateTime.Now-dteTimeOutCheck;
We check that if there is any data waiting to be received in the first line.
At first look this line seems unneccessary.Even we can think that we can write a smaller code with just using "strSMTP.ReadLine()".Correct but not all the time. If we have any problem with the mta or any problem occurs in the protocol flow you'll see how important this line is.
Assume that we are waiting the 250 response from the mta and somehow the mta responded us as "502 unimplemented". If we do not use the nstSMTP.DataAvailable check we'll receive the 502 command and because of the 502 is not the message we are waiting for we'll continue to wait.If this scenario happens you'll continue waiting the 250 command in the second loop.You will wait because the strSMTP.ReadLine() command waits until he has received any message.Finally you won't reach to the checkpoint of the timeout mechanizm until the mta sends us the message we wait. Beleive me it's good to write more code, thinking all the possibilities than writing a small but unsafe code.
Yahooo, the last stage , testing the code.
Copy the following code and do not forget to change the mta recipient sender properties.
using System;
namespace SendMailviaSMTP
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
SMTPMailSender sm=new SMTPMailSender();
sm.SMTPTimeOut=10;
sm.SenderEmailAddress="[email protected]";
sm.RecipientEmailAddress="[email protected]";
sm.SMTPServerAddress="mail.somemta.com";
sm.SenderName="Levent YILDIZ ACME Inc";
sm.RecipientName="Levent YILDIZ MINDSTORM Inc";
sm.EmailSubject="Test subject";
sm.EmailBody="Test Body";
if (sm.SendEmail())
{
Console.WriteLine("Email has been sent successfully.");
}
else
{
Console.WriteLine("Problem occured.");
}
}
}
}
Horaaayy i received the email.
Hope you do so.....