Monday, March 9, 2009

Send Email in .Net Application without SMTP server

Usually, when we send an email, we need to have valid SMTP server along with access and deliver the email using that server. If we add the send email functionality to software, the user needs to configure an SMTP server address, the username, and the password. The SMTP server receive message and send it to another SMTP server or deliver it to local inbox. Why not send an email to the receiver's SMTP server directly? Why can’t we directly communicate to receiver SMTP server on port 25?

The problem is we don't know which SMTP server is responsible for receiving emails for a given email address. The secret is that this information can be obtained from Domain Name System (DNS) servers. This seems simple; however, it needs a lot work to implement the DNS protocol (
RFC 1035) because the .NET framework doesn't support getting mail server info from DNS.

There are certain fundamental concepts defined for SMTP
RFC 5321 SMTP servers which send message to another server are called MTAs. MTAs look for MX record (may find more than one MX records) for the domain (DNS look up for NS and MX record, in windows you can use dnsapi.dll, DnsQuery_W method suits to needs.). Once you have server IP you can connect to port 25 (generally those ports are Ephemeral) and exchange message (of course according to RFC 5321).

We can use Dnsapi.dll to query DNS server the code is....

/* **********************************************
* Developed by Dipak Bava
* Resolves MX records for domain name
* Date: 25 Feb 2009
* **********************************************/
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace SendSMTP
{
public class DnsLookUp
{
public DnsLookUp()
{
}
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)]ref string pszName, QueryTypes wType, QueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);

[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);

public static string[] GetMXRecords(string domain)
{

IntPtr ptr1 = IntPtr.Zero;
IntPtr ptr2 = IntPtr.Zero;
MXRecord recMx;
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
throw new NotSupportedException();
}
ArrayList list1 = new ArrayList();
int num1 = DnsLookUp.DnsQuery(ref domain, QueryTypes.DNS_TYPE_MX, QueryOptions.DNS_QUERY_BYPASS_CACHE, 0, ref ptr1, 0);
if (num1 != 0)
{
throw new Win32Exception(num1);
}
for (ptr2 = ptr1; !ptr2.Equals(IntPtr.Zero); ptr2 = recMx.pNext)
{
recMx = (MXRecord)Marshal.PtrToStructure(ptr2, typeof(MXRecord));
if (recMx.wType == 15)
{
string text1 = Marshal.PtrToStringAuto(recMx.pNameExchange);
list1.Add(text1);
}
}
DnsLookUp.DnsRecordListFree(ptr1, 0);
return (string[])list1.ToArray(typeof(string));
}

private enum QueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}

private enum QueryTypes
{
DNS_TYPE_MX = 15
}

[StructLayout(LayoutKind.Sequential)]
private struct MXRecord
{
public IntPtr pNext;
public string pName;
public short wType;
public short wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public IntPtr pNameExchange;
public short wPreference;
public short Pad;
}
}
}

Sounds good up to here but when you really try to connect MTAs to deliver message spamhaus comes in to picture. Most of the MTAs are now intelligent enough to fight against spammer and your ISP could be in the radar. So before you try, it’s wise to check you IP with http://www.spamhaus.org/query/bl?ip=xx.xx.xx.xx
Use the following code to deliver message if you do not want to study RFC 5321.

//Now prepare your message.
MailMessage mail = new MailMessage();
mail.To.Add("someone@somedomail.com");
mail.From = new MailAddress("tome@somedomain.com");
mail.Subject = "Send email without SMTP server";
mail.Body = "Yep, its workin!!!!";

//Send message
string domain = mail.To[0].Address.Substring(mail.To[0].Address.IndexOf('@') + 1);
//To Do :need to check for MX record existance before you send. Left intentionally for you.
string mxRecord = SendSMTP.DnsLookUp.GetMXRecords(domain)[0];
SmtpClient client = new SmtpClient(mxRecord);
client.Send(mail);

Best Luck
Dipak Goswami

1 comment:

Abdul Amin Khan said...

Trying to Send Mail Using my gmail ID

Showing Error this Code

Mailbox unavailable. The server response was: 5.7.1 [122.176.84.252] The IP you're using to send mail is not authorized to