Click here to Skip to main content
15,893,564 members
Articles / Programming Languages / C#

SMTP and POP3 Mail Server

Rate me:
Please Sign up or sign in to vote.
4.88/5 (96 votes)
29 Sep 20031 min read 1M   18.9K   315  
An SMTP and POP3 mail server written using the .NET Framework and C#.
using System;
using System.IO;
using System.Data;
using System.Threading;
using System.Collections;
using LumiSoft.Net.SMTP.Client;
using LumiSoft.Net.Mime;

namespace LumiSoft.MailServer
{
	/// <summary>
	/// Mail relayer.
	/// </summary>
	public class Relay
	{
		private static bool      m_Delivering           	= false;
		private static bool      m_DeliveringRetry      	= false;
		private static int       m_MaxThreads           	= 10;
		private static int       m_RelayInterval        	= 10;
		private static int       m_RelayRetryInterval   	= 30;
		private static int       m_RelayUndelWarning    	= 1;
		private static int       m_RelayUndelivered     	= 1;
		private static string    m_UndelWarningTemplate 	= "";
		private static string    m_UndeliveredTemplate      = "";
		private static bool      m_StoreUndeliveredMessages = true;
		private static string    m_SmartHost           		= "";    // Smart host name eg. 'mail.neti.ee'.
		private static bool      m_UseSmartHost        		= true;  // 
		private static string    m_Dns1                		= "";    // Primary dns server IP.
		private static string    m_Dns2                		= "";    // Secondary dns server IP.
		private static Hashtable m_RelayTable          		= null;

		private MailServer m_pServer = null;		
		
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="server"></param>
		public Relay(MailServer server)
		{
			m_pServer = server;

			if(m_RelayTable == null){
				m_RelayTable = new Hashtable();
			}
		}


		#region function Deliver

		/// <summary>
		/// Sends relay mails.
		/// </summary>
		public void Deliver()
		{
			if(!m_Delivering){
				m_Delivering = true;

				RelayMails();

				m_Delivering = false;
			}
		}

		#endregion

		#region function DeliverRetry

		/// <summary>
		/// Sends retry(mails which couldn't be sent at immedeately) relay mails.
		/// </summary>
		public void DeliverRetry()
		{
			if(!m_DeliveringRetry){
				m_DeliveringRetry = true;

				SendRetryMails();

				m_DeliveringRetry = false;
			}
		}

		#endregion

		
		#region function RelayMails

		/// <summary>
		/// Relays all messages from relay directory.
		/// </summary>
		private void RelayMails()
		{
			try
			{				
				string path = m_pServer.m_MailStorePath + "Relay\\";

				// Check if Directory exists, if not Create
				if(!Directory.Exists(path)){
					Directory.CreateDirectory(path);
				}

				string[] files = Directory.GetFiles(path,"*.eml");
		
				foreach(string file in files){

					// If maximum relay threads are exceeded,
					// wait when some gets available.
					while(m_RelayTable.Count > m_MaxThreads){
						Thread.Sleep(200);
					}

					Thread tr = new Thread(new ThreadStart(this.RelayMessage));					
					AddThread(tr,file);
					tr.Start();		
				}
			}
			catch(Exception x)
			{
				Error.DumpError(x,new System.Diagnostics.StackTrace());
			}			
		}

		#endregion

		#region function RelayMessage

		private void RelayMessage()
		{			
			try
			{				
				if(!m_RelayTable.Contains(Thread.CurrentThread)){
					SCore.WriteLog(m_pServer.m_SartUpPath + "mailServiceError.log","RelayMails: params missing");
					return;
				}

				string messageFile = m_RelayTable[Thread.CurrentThread].ToString();

				bool sendOk = false;
				using(FileStream fs = File.Open(messageFile,FileMode.Open,FileAccess.ReadWrite)){
					// Get relay info
					RelayInfo relayInf = new RelayInfo(fs,1,1);

					string from = relayInf.From;
					if(from.Length == 0){
						from = relayInf.To;
					}

					SMTP_Client smtpClnt  = new SMTP_Client();
					smtpClnt.UseSmartHost = UseSmartHost;
					smtpClnt.SmartHost    = SmartHost;
					smtpClnt.DnsServers   = new string[]{Dns1,Dns2};
					sendOk = smtpClnt.Send(new string[]{relayInf.To},from,fs);						
				}

				if(sendOk){
					// Message sended successfuly, may delete it
					File.Delete(messageFile);
				}
				// send failed
				else{
					// Move message to Retry folder
					string msgFileName = Path.GetFileName(messageFile);
					File.Move(messageFile,m_pServer.m_MailStorePath + "Retry\\" + msgFileName);
				}
			}
			catch(Exception x){
				if(!(x is IOException)){
					Error.DumpError(x,new System.Diagnostics.StackTrace());
				}
			}
			finally{				
				RemoveThread(Thread.CurrentThread);
			}
		}

		#endregion


		#region function SendRetryMails

		/// <summary>
		/// Relay retry mails from retry directory.
		/// </summary>
		private void SendRetryMails()
		{
			try
			{				
				string path = m_pServer.m_MailStorePath + "Retry\\";

				// Check if Directory exists, if not Create
				if(!Directory.Exists(path)){
					Directory.CreateDirectory(path);
				}

				string[] files = Directory.GetFiles(path,"*.eml");
		
				foreach(string file in files){

					// If maximum relay threads are exceeded,
					// wait when some gets available.
					while(m_RelayTable.Count > m_MaxThreads){
						Thread.Sleep(200);
					}

					Thread t = new Thread(new ThreadStart(this.SendRetryMail));					
					AddThread(t,file);
					t.Start();					
				}
			}
			catch(Exception x)
			{
				Error.DumpError(x,new System.Diagnostics.StackTrace());
			}
		}

		#endregion

		#region function SendRetryMail

		private void SendRetryMail()
		{
			try
			{				
				if(!m_RelayTable.Contains(Thread.CurrentThread)){
					SCore.WriteLog(m_pServer.m_SartUpPath + "mailServiceError.log","SendRetryMail: params missing");
					return;
				}

				string messageFile = m_RelayTable[Thread.CurrentThread].ToString();

				using(FileStream fs = File.Open(messageFile,FileMode.Open,FileAccess.ReadWrite)){
					// Get relay info
					RelayInfo relayInf = new RelayInfo(fs,RelayUndelivered,RelayUndelWarning);

					string from = relayInf.From;
					if(from.Length == 0){
						from = relayInf.To;
					}
					
					SMTP_Client smtpClnt  = new SMTP_Client();
					smtpClnt.UseSmartHost = UseSmartHost;
					smtpClnt.SmartHost    = SmartHost;
					smtpClnt.DnsServers   = new string[]{Dns1,Dns2};
					if(smtpClnt.Send(new string[]{relayInf.To},from,fs)){
						fs.Close();
						// Message sended successfuly, may delete it
						File.Delete(messageFile);
					}
					// send failed
					else{
						string error = smtpClnt.Errors[0].ErrorText;
												
						// If destination recipient is invalid or Undelivered Date Exceeded, try to return message to sender
						if(smtpClnt.Errors[0].ErrorType != SMTP_ErrorType.UnKnown || relayInf.IsUndeliveredDateExceeded){							
							MakeUndeliveredNotify(relayInf,error,fs);
							fs.Close();

							// Undelivery note made, may delete message
							if(relayInf.From.Length > 0){
								File.Delete(messageFile);
							}
							// There isn't return address, can't send undelivery note
							else if(StoreUndeliveredMessages){
								// Check if Directory exists, if not Create
								if(!Directory.Exists(m_pServer.m_MailStorePath + "Undelivered\\")){
									Directory.CreateDirectory(m_pServer.m_MailStorePath + "Undelivered\\");
								}
								File.Move(messageFile,messageFile.Replace("Retry","Undelivered"));
							}
						}
						else if(relayInf.MustSendWarning){
							MakeUndeliveredWarning(relayInf,error,fs);

							byte[] mustSendWarningBit = System.Text.Encoding.ASCII.GetBytes("1");
							fs.Position = relayInf.WarningBitPos;
							fs.Write(mustSendWarningBit,0,mustSendWarningBit.Length);							
						}
					}
				}
			}
			catch(Exception x){
				if(!(x is IOException)){
					Error.DumpError(x,new System.Diagnostics.StackTrace());
				}
			}
			finally{				
				RemoveThread(Thread.CurrentThread);
			}
		}

		#endregion

		
		#region function MakeUndeliveredNotify

		/// <summary>
		/// Creates undelivered notify for user and places it to relay folder.
		/// </summary>
		/// <param name="relayInfo">Relay info</param>
		/// <param name="error">SMTP returned error text.</param>
		/// <param name="file">Messsage file.</param>
		private void MakeUndeliveredNotify(RelayInfo relayInfo,string error,Stream file)
		{
			try
			{
				// If sender isn't specified, we can't send undelivery notify to sender.
				// Just skip undelivery notify sending.
				if(relayInfo.From.Length == 0){
					return;
				}

				file.Position = relayInfo.MessageStartPos;

				// Make new message
				MimeConstructor mime = new MimeConstructor();
				mime.From    = "postmaster";
				mime.To      = new string[]{relayInfo.From};
				mime.Subject = "Undelivered mail warning";
				mime.Attachments.Add(new Attachment("data.eml",file));

				string bodyTxt = Relay.UndeliveredTemplate;
				bodyTxt = bodyTxt.Replace("<#RECEPTIENT>",relayInfo.To);
				bodyTxt = bodyTxt.Replace("<#ERROR>",error);

				mime.Body    = bodyTxt;
				
				byte[] data = System.Text.Encoding.Default.GetBytes(mime.ConstructMime());
				using(MemoryStream strm = new MemoryStream(data)){
					m_pServer.ProcessAndStoreMessage("",new string[]{relayInfo.From},strm);
				}
			}
			catch(Exception x)
			{
				Error.DumpError(x,new System.Diagnostics.StackTrace());
			}
		}

		#endregion

		#region function MakeUndeliveredWarning

		/// <summary>
		/// Creates undelivered warning for user and places it to relay folder.
		/// </summary>
		/// <param name="relayInfo">Relay info</param>
		/// <param name="error">SMTP returned error text.</param>
		/// <param name="file">Messsage file.</param>
		private void MakeUndeliveredWarning(RelayInfo relayInfo,string error,Stream file)
		{	
			try
			{
				// If sender isn't specified, we can't send warning to sender.
				// Just skip warning sending.
				if(relayInfo.From.Length == 0){
					return;
				}

				file.Position = relayInfo.MessageStartPos;

				// Make new message
				MimeConstructor mime = new MimeConstructor();
				mime.From    = "postmaster";
				mime.To      = new string[]{relayInfo.From};
				mime.Subject = "Undelivered mail warning";
				mime.Attachments.Add(new Attachment("data.eml",file));

				string bodyTxt = Relay.UndelWarningTemplate;
					   bodyTxt = bodyTxt.Replace("<#RECEPTIENT>",relayInfo.To);
					   bodyTxt = bodyTxt.Replace("<#ERROR>",error);
					   bodyTxt = bodyTxt.Replace("<#UNDELIVERED_HOURS>",relayInfo.DeviveringForHours.ToString()); 

				mime.Body    = bodyTxt;

				byte[] data = System.Text.Encoding.Default.GetBytes(mime.ConstructMime());
				using(MemoryStream strm = new MemoryStream(data)){
					m_pServer.ProcessAndStoreMessage("",new string[]{relayInfo.From},strm);
				}
				
			//	byte[] data = System.Text.Encoding.Default.GetBytes(mime.ConstructMime());
			//	MailStore.StoreMessage("",new MemoryStream(data),relayInfo.From,"",true);
			}
			catch(Exception x)
			{
				Error.DumpError(x,new System.Diagnostics.StackTrace());
			}
		}

		#endregion


		#region function AddThread

		private void AddThread(Thread tr,string data)
		{
			lock(m_RelayTable){				
				m_RelayTable.Add(tr,data);				
			}
		}

		#endregion

		#region function RemoveThread

		private void RemoveThread(Thread t)
		{
			lock(m_RelayTable){				
				if(!m_RelayTable.ContainsKey(t)){
					SCore.WriteLog(m_pServer.m_SartUpPath + "mailServiceError.log","RemoveThread: doesn't contain");
				}
				m_RelayTable.Remove(t);				
			}
		}

		#endregion
        

		#region Properties Implementation

		/// <summary>
		/// Gets or sets maximum relay threads.
		/// </summary>
		public static int MaxRelayThreads
		{
			get{ return m_MaxThreads; }

			set{ m_MaxThreads = value; }
		}

		/// <summary>
		/// Gets or sets relay interval.
		/// </summary>
		public static int RelayInterval
		{
			get{ return m_RelayInterval; }

			set{ m_RelayInterval = value; }
		}

		/// <summary>
		/// Gets or sets relay retry(delayed relay) interval.
		/// </summary>
		public static int RelayRetryInterval
		{
			get{ return m_RelayRetryInterval; }

			set{ m_RelayRetryInterval = value; }
		}

		/// <summary>
		/// Gets or sets undelivered wanrning minutes.
		/// </summary>
		public static int RelayUndelWarning
		{
			get{ return m_RelayUndelWarning; }

			set{ m_RelayUndelWarning = value; }
		}

		/// <summary>
		/// Gets or sets undelivered wanrning reply template.
		/// </summary>
		public static string UndelWarningTemplate
		{
			get{ return m_UndelWarningTemplate; }

			set{ m_UndelWarningTemplate = value; }
		}

		/// <summary>
		/// Gets or sets undelivered hours.
		/// </summary>
		public static int RelayUndelivered
		{
			get{ return m_RelayUndelivered; }

			set{ m_RelayUndelivered = value; }
		}

		/// <summary>
		/// Gets or sets undelivered reply template.
		/// </summary>
		public static string UndeliveredTemplate
		{
			get{ return m_UndeliveredTemplate; }

			set{ m_UndeliveredTemplate = value; }
		}

		/// <summary>
		/// Gets or sets stroe undelivered messages.
		/// </summary>
		public static bool StoreUndeliveredMessages
		{
			get{ return m_StoreUndeliveredMessages; }

			set{ m_StoreUndeliveredMessages = value; }
		}

		/// <summary>
		/// Gets or sets if to use smart host.
		/// </summary>
		public static bool UseSmartHost
		{
			get{ return m_UseSmartHost; }

			set{ m_UseSmartHost = value; }
		}

		/// <summary>
		/// Gets or sets smart host.
		/// </summary>
		public static string SmartHost
		{
			get{ return m_SmartHost; }

			set{ m_SmartHost = value; }
		}

		/// <summary>
		/// Gets or sets smart host.
		/// </summary>
		public static string Dns1
		{
			get{ return m_Dns1; }

			set{ m_Dns1 = value; }
		}

		/// <summary>
		/// Gets or sets smart host.
		/// </summary>
		public static string Dns2
		{
			get{ return m_Dns2; }

			set{ m_Dns2 = value; }
		}


		/// <summary>
		/// Gets if delivering messages.
		/// </summary>
		public static bool IsDelivering
		{
			get{ return m_Delivering; }
		}

		/// <summary>
		/// Gets if delivering retry messages.
		/// </summary>
		public static bool IsDeliveringRetry
		{
			get{ return m_DeliveringRetry; }
		}

		#endregion
	
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions