Send mail without specifying an SMTP server






4.76/5 (38 votes)
A class derived from CSMTPConnection that queries the MX record for a target domain and uses that to send mail
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 theCSMTPConnection
class (it's assumed thatCoInitialize
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"); //Optional msg.SetSubject("Hello World"); msg.AddText("Hmmm, this should work fine!"); //msg.AttachFile("some file path"); CSMTPConnection2 conn; //You need to specify the domain name of the recipient email address 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 usedCSimpleArray
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
/* Copyright(C) Nishant Sivakumar (nish#voidnish.com) Please maintain this copyright notice if you distribute this file in original or modified form. */ #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
/* Copyright(C) Nishant Sivakumar (nish#voidnish.com) Please maintain this copyright notice if you distribute this file in original or modified form. */ #include "StdAfx.h" #include "smtpconnection2.h" BOOL CSMTPConnection2::Connect(LPCTSTR lpszHostDomain, DWORD dwTimeout /*= 10000*/) 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.