In previous section, I introduced how to convert email to HTML page. In this section, I will introduce how to parse Non-delivery report (NDR) in Visual C++.
Sections:
Some e-mail applications, such as Microsoft Office Outlook, employ a read-receipt tracking mechanism. A sender selects the receipt request option prior to sending the message. Upon opening the email, each recipient has the option of notifying the sender that the message was opened and read.
However, there is no guarantee that you will get a read-receipt. Some possible reason are that very few e-mail applications or services support read receipts, or simply because users disable the functionality. Those do support read-receipt aren’t necessarily compatible with or capable of recognizing requests from a different e-mail service or application
It is also called a DSN (delivery service notification), which is a request to the recipient’s email server to send you a notification about the delivery of an email you’ve just sent. The notification takes the form of an email, and will tell you if your delivery succeeded (Delivery Receipt), failed, got delayed (Failure Report).
For many email campaign applications, the very important task is detecting if the email is received by recipient or not. Parsing the delivery report is the common way to get the email status. EAGetMail .NET class provides a built-in function (GetReport) to parse the report. The following sample demonstrates how to parse the delivery-report.
If ReporType is DeliveryReceipt
or ReadReceipt
, the report probably
has only OriginalSender, OriginalRecipient and OriginalMessageID information in
the report, it depends on the mail server that generated the report.
Note
Remarks: All of examples in this section are based on first section: A simple Visual C++ project. To compile and run the following example codes successfully, please click here to learn how to create the test project and add reference to your project.
The following example codes demonstrate how to parse delivery report.
Note
To get the full sample projects, please refer to Samples section.
#include "stdafx.h"
#include <windows.h>
#include "eagetmailobj.tlh"
using namespace EAGetMailObjLib;
void ParseReport(LPCTSTR lpszEmlFile)
{
const int FailureReport = 0;
const int DeliveryReceipt = 1;
const int ReadReceipt = 2;
const int Receipt_Deleted = 3;
const int DelayedReport = 4;
IMailPtr oMail;
oMail.CreateInstance(__uuidof(EAGetMailObjLib::Mail));
oMail->LicenseCode = _T("TryIt");
oMail->LoadFile(lpszEmlFile, VARIANT_FALSE);
if(oMail->IsReport == VARIANT_FALSE)
{
_tprintf(_T("This is not a report"));
return;
}
// get report type
IMailReportPtr oReport = oMail->GetReport();
switch(oReport->ReportType)
{
case DeliveryReceipt:
_tprintf(_T("This is a delivery receipt\r\n"));
break;
case ReadReceipt:
_tprintf(_T("This is a read receipt\r\n"));
break;
case Receipt_Deleted:
_tprintf(_T("This is a unread receipt, this email was deleted without read!\r\n"));
break;
case DelayedReport:
_tprintf(_T("This is a delayed report, this email will be tried later by server automatically!\r\n"));
break;
default:
_tprintf(_T("This is a failure delivery report\r\n"));
break;
}
// get original message information
_tprintf(_T("OriginalSender: %s\r\n"), (TCHAR*)oReport->OriginalSender);
_tprintf(_T("OriginalRecipient: %s\r\n"), (TCHAR*)oReport->OriginalRecipient);
_tprintf(_T("OriginalMessageID: %s\r\n"),(TCHAR*)oReport->OriginalMessageID);
if(oReport->ReportType == FailureReport || oReport->ReportType == DelayedReport)
{
_tprintf(_T("ErrCode: %s\r\n"), (const TCHAR*)oReport->ErrCode);
_tprintf(_T("ErrDescription: %s\r\n"), (const TCHAR*)oReport->ErrDescription);
_tprintf(_T("OriginalSubject: %s\r\n"), (const TCHAR*)oReport->OriginalSubject);
_tprintf(_T("ReportMTA: %s\r\n"), (const TCHAR*)oReport->ReportMTA);
IHeaderCollectionPtr oHeaders;
oHeaders = oReport->OriginalHeaders;
int count = oHeaders->Count;
for(int i = 0; i < count; i++)
{
IHeaderItemPtr oHeader;
oHeader = oHeaders->Item(i);
::_tprintf(_T("%s: %s\r\n"),
(TCHAR*)oHeader->HeaderKey, (TCHAR*)oHeader->HeaderValue);
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
// Initialize COM environment
::CoInitialize(NULL);
try
{
ParseReport(_T("c:\\my folder\\test.eml"));
}
catch (_com_error &exp)
{
_tprintf(_T("%s\r\n"), (TCHAR*)exp.Description());
}
return 0;
}
To retrieve and parse Failure Report (NDR), you should monitor your sender mailbox. Here I will introduce how to use EAGetMail Service to monitor a mailbox and retrieve non-delivery report and insert it to SQL server on a regular basis.
To use EAGetMail Service, you need to download EAGetMail Service and install it on your machine at first.
Then create a table in your SQL database like this:
CREATE TABLE [dbo].[Failure_Report](
[reportid] [int] IDENTITY(1,1) NOT NULL,
[address] [nvarchar](255) NOT NULL,
[error_code] [nchar](10) NOT NULL,
[error_desc] [nchar](255) NOT NULL,
[error_datetime] [datetime] NOT NULL
) ON [PRIMARY]
GO
Create a Visual C++ console application named “parse_reports”, then
Input the following codes:
#include "stdafx.h"
#include <windows.h>
#include "eagetmailobj.tlh"
using namespace EAGetMailObjLib;
#import "C:\\Program Files\\Common Files\\System\\ado\\msado15.dll" \
no_namespace rename("EOF", "ADOEOF")
static bool ParseEmail(LPCTSTR lpszFileName, _ConnectionPtr& oConn)
{
const int FailureReport = 0;
const int DeliveryReceipt = 1;
const int ReadReceipt = 2;
const int Receipt_Deleted = 3;
IMailPtr oMail = NULL;
oMail.CreateInstance(__uuidof(EAGetMailObjLib::Mail));
oMail->LicenseCode = _T("TryIt");
oMail->LoadFile(lpszFileName, VARIANT_TRUE);
// detect if this is a report or receipt
if (oMail->IsReport == VARIANT_FALSE)
{
_tprintf(_T("Not a report or receipt!\r\n"));
return false;
}
IMailReportPtr oReport = oMail->GetReport();
// we only process failure report
if (oReport->ReportType != FailureReport)
{
_tprintf(_T("Not a failure report!\r\n"));
return false;
}
_tprintf(_T("OriginalRecipient: %s\r\n"), (LPCTSTR)oReport->OriginalRecipient);
_tprintf(_T("ErrorCode: %s\r\n"), (LPCTSTR)oReport->ErrCode);
_tprintf(_T("ErrorDesc: %s\r\n"), (LPCTSTR)oReport->ErrDescription);
TCHAR errorDesc[MAX_PATH + 1];
// limit maximun length of error description to 250 in database.
_tcsncpy_s(errorDesc, MAX_PATH, (LPCTSTR)oReport->ErrDescription, _TRUNCATE);
// INSERT the result to database.
LPCTSTR lpszSQL = _T("INSERT INTO [dbo].[Failure_Report] ")
_T(" ([address] ")
_T(" ,[error_code] ")
_T(" ,[error_desc] ")
_T(" ,[error_datetime]) ")
_T(" VALUES ( @address, @error_code, @error_desc, GETDATE())");
_CommandPtr oCommand = NULL;
oCommand.CreateInstance(__uuidof(Command));
oCommand->ActiveConnection = oConn;
oCommand->CommandText = lpszSQL;
oCommand->CommandType = adCmdText;
oCommand->Prepared = VARIANT_FALSE;
_variant_t vt = oReport->OriginalRecipient;
_ParameterPtr oParam = oCommand->CreateParameter(_T("@address"), adBSTR, adParamInput, 0, vt);
oCommand->Parameters->Append(oParam);
oParam.Release();
vt = oReport->ErrCode;
oParam = oCommand->CreateParameter(_T("@error_code"), adBSTR, adParamInput, 0, vt);
oCommand->Parameters->Append(oParam);
oParam.Release();
vt = errorDesc;
oParam = oCommand->CreateParameter(_T("@error_desc"), adBSTR, adParamInput, 0, vt);
oCommand->Parameters->Append(oParam);
oParam.Release();
oCommand->Execute(NULL, NULL, adExecuteNoRecords | adCmdText);
oCommand.Release();
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 2)
{
return 0;
}
if (_tcslen(argv[1]) > MAX_PATH)
{
return 0;
}
TCHAR szFolder[MAX_PATH + 1];
_tcscpy_s(szFolder, MAX_PATH, argv[1]);
HANDLE hFind = INVALID_HANDLE_VALUE;
TCHAR szFind[MAX_PATH + 1];
if (szFolder[_tcslen(szFolder) - 1] == _T('\\'))
{
szFolder[_tcslen(szFolder) - 1] = _T('\0');
}
::_sntprintf_s(szFind, MAX_PATH, MAX_PATH - 1, _T("%s\\*.eml"), szFolder);
WIN32_FIND_DATA findData;
memset(&findData, 0, sizeof(findData));
hFind = ::FindFirstFile(szFind, &findData);
if (hFind == INVALID_HANDLE_VALUE)
return 0;
::CoInitialize(NULL);
_ConnectionPtr oConn = NULL;
oConn.CreateInstance(__uuidof(Connection));
// For more connection string
// MS SQL Server 2000
// "Driver={SQL Server};Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// MS SQL Server 2005
// "Driver={SQL Server Native Client};Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// MS SQL Server 2005 Native Provider
// "Provider=SQLNCLI;Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// MS SQL Server 2008
// "Driver={SQL Server Native Client 10.0};Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// MS SQL Server 2008 Native Provider
// "Provider=SQLNCLI10;Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// MS SQL Server 2012
// "Driver={SQL Server Native Client 11.0};Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// MS SQL Server 2012 Native Provider
// "Provider=SQLNCLI11;Server=localhost; Database=myDB;Uid=myUser;Pwd=myPassword;"
// change it to your sql server address, database, user and password
// The server/instance name syntax used in the server option is the same for all SQL Server connection strings.
// e.g.: Server=serveraddress\\instancename;
// open database connection
LPCTSTR lpszConn = _T("Driver={SQL Server Native Client 11.0};Server=localhost;Database=myDB;Uid=myUser;Pwd=myPassword;");
try
{
oConn->Open(lpszConn,
_T(""), _T(""), adConnectUnspecified);
do
{
TCHAR szFile[MAX_PATH + 1];
::_sntprintf_s(szFile, MAX_PATH, MAX_PATH - 1, _T("%s\\%s"), szFolder, findData.cFileName);
if (ParseEmail(szFile, oConn))
{
// Delete the local report file
::DeleteFile(szFile);
}
} while (::FindNextFile(hFind, &findData));
oConn->Close();
}
catch (_com_error &ep)
{
::_tprintf(_T("Error: %s\r\n"), (const TCHAR*)ep.Description());
}
return 0;
}
Finally, open EAGetMail Service Manager -> Mail Pull Configuration -> New:
Input your sender mailbox account information
Create a folder named “inbox” on your machine, this folder is used to store .EML file.
Input the folder full path to “Save email file(s) to specified local folder:”;
Input application full path [SPACE] folder full path to: “Run specified application after download is finished”.
For example:
If your application full path is d:\parse_reports.exe
and your folder is d:\inbox
, then input:
"d:\parse_reports.exe" "d:\inbox"
With above setting, EAGetMail Service checks mailbox every 15 minutes and once there is non-delivery report, it will invoke parse_reports.exe to process non-delivery report and insert it to database like this:
Important
If you have “Leave a copy of message on mail server” unchecked, EAGetMail Service will delete all emails in your mailbox after the emails were retrieved to local folder. If your mailbox is only used to retrieve non-delivery report, then I recommend you have “Leave a copy of message on mail server” unchecked to get better performance.
You can run your application directly under DOS prompt without EAGetMail Service. If there is any error, you can debug and fix it.
"d:\parse_reports.exe" "d:\inbox"
EAGetMail Service is a common solution to process email on a regular basis, you can use above solution to download and process normal emails as well. You just need to change/extend the codes in parse_reports.exe
Common SQL Driver Download
If SQL Server is installed on a remote server, and you don’t have SQL driver installed on local machine, then you need to download and install corresponding driver on local machine.
Next Section
At next section I will introduce how to manage folders with IMAP4/Exchange Web Service (EWS)/WebDAV protocol.
Appendix
Comments
If you have any comments or questions about above example codes, please click here to add your comments.