Overview
Quite frequently, you hear people asking how they can send an email from
their applications to a specific email address without prompting the user for a
relaying SMTP server. The most commonly suggested solution is to use MAPI and
get a list of SMTP servers from the user's registry, but this assumes that the
user has configured a MAPI compliant email client on his machine. I always reply
with my solution of querying the MX record for the target domain and then
SMTP-chatting to that server directly - it's easy to do, it works on any machine
and it's also the fastest way to send email (minimum number of SMTP hops).
Starting with VC++ 7, ATL includes the CSMTPConnection
class
which lets you send mail, provided you give it an SMTP server to connect to. I
derived a class (CSMTPConnection2
) from
CSMTPConnection
which lets you pass in the target domain name
directly and the class queries the MX records using the default DNS on the
client machine, gets a list of SMTP servers (depending on the number of MX
records returned) and connects to the first SMTP server that works okay. The
only visible change is to the Connect
method, and even for the
Connect
method, the method signature remains the same. The first
parameter (an LPCTSTR
) now represents the target domain name
instead of the SMTP hostname. So it should be pretty easy to modify your
existing code. Just change all references to CSMTPConnection
in
your code to CSMTPConnection2
and you are done.
Using the class
-
#include
the header file for the class
#include "smtpconnection2.h"
The header file includes a #pragma
linker directive to include
Dnsapi.lib
-
Use the CSMTPConnection2
class just as you would use the
CSMTPConnection
class (it's assumed that CoInitialize
has been called prior to invoking and using this class)
CMimeMessage msg;
msg.SetSender("nish@somedomain.com");
msg.SetSenderName("Nishter");
msg.AddRecipient("someone@vsnl.com");
msg.SetSubject("Hello World");
msg.AddText("Hmmm, this should work fine!");
CSMTPConnection2 conn;
if(conn.Connect("vsnl.com"))
{
if( conn.SendMessage(msg) == TRUE )
std::cout << "Mail sent successfully" << std::endl;
conn.Disconnect();
}
Class requirements
-
You'll need VC++ 7 or above (for the ATL classes)
-
This code will run only on Windows 2000 or later (because of the DNS API
that's used to query the MX record)
-
You do not need MFC (the CString
that's used is shared between
MFC/ATL and I specifically used CSimpleArray
instead of an MFC
array class)
Limitation
Note that, because the class looks up the MX record and SMTPs directly into
the mail server for each domain name, you cannot use this class to send mails to
multiple-domains in one go.
Source listings
Header file
#pragma once
#include <atlsmtpconnection.h>
#include <Windns.h>
#pragma comment(lib,"Dnsapi.lib")
class CSMTPConnection2 : public CSMTPConnection
{
public:
BOOL Connect(LPCTSTR lpszHostDomain, DWORD dwTimeout = 10000) throw();
private:
void _GetSMTPList(LPCTSTR lpszHostDomain, CSimpleArray<CString>& arrSMTP);
};
Cpp file
#include "StdAfx.h"
#include "smtpconnection2.h"
BOOL CSMTPConnection2::Connect(LPCTSTR lpszHostDomain,
DWORD dwTimeout ) throw()
{
CSimpleArray<CString> arrSMTP;
_GetSMTPList(lpszHostDomain, arrSMTP);
for(int i=0; i<arrSMTP.GetSize(); i++)
{
if(CSMTPConnection::Connect(arrSMTP[i], dwTimeout) == TRUE)
return TRUE;
}
return FALSE;
}
void CSMTPConnection2::_GetSMTPList(LPCTSTR lpszHostDomain,
CSimpleArray<CString>& arrSMTP)
{
PDNS_RECORD pRec = NULL;
if(DnsQuery(lpszHostDomain, DNS_TYPE_MX, DNS_QUERY_STANDARD,
NULL, &pRec, NULL) == ERROR_SUCCESS)
{
PDNS_RECORD pRecOrig = pRec;
while(pRec)
{
if(pRec->wType == DNS_TYPE_MX)
arrSMTP.Add(pRec->Data.MX.pNameExchange);
pRec = pRec->pNext;
}
DnsRecordListFree(pRecOrig,DnsFreeRecordList);
}
}
Conclusion
As usual, feedback is welcome and appreciated whether it's constructive or
not.