Click here to Skip to main content
Click here to Skip to main content

SMTP Client

By , 16 Jul 2010
 

Introduction

The CSmtp class allows to send emails from your program. The inspiration to write the CSmtp class was the article: CFastSmtp - A fast and easy SMTP class... I have used the code of CFastSmtp and introduced the following changes:

  • some bags have been removed (i.e., free memory)
  • logging in with authentication has been applied (AUTH LOGIN)
  • sending attachments has been added
  • error handling has been modified
  • new headlines from MIME specifications (i.e., X-Priority) have been added
  • non-blocking mode has been added
  • exceptions have been used
  • compatibility with Linux systems has been ensured

Typical scenarios while sending emails

After successful connection to an SMTP server, our client starts the conversation with the remote SMTP server. Each line sent by the client ought to be finished by "\r\n". If you want to know more details, check the References: [2], [3], [4], [5], [6], [7], [8], and [9]. In [2] is described the original SMTP protocol (1982), in [4] is discussed the SMTP extensions for authentication, and the MIME specification is improved in [5]-[9]. Below there are shown typical scenarios while sending e-mails. Example 3 fails because no TLS procedures were implemented in the CSmtp class. If you want to add TLS, see OpenSSL. I have introduced the following notation: S is a remote server, C is our client, and xxx means information censured.

Example 1 - Connecting to smtp.wp.pl and using an incorrect login or password:

S: 220 smtp.wp.pl ESMTP
C: EHLO: mydomain.com
S: 250-smtp.wp.pl
   250-PIPELINING
   250-AUTH=LOGIN PLAIN
   250-AUTH LOGIN PLAIN
   250-STARTTLS
   250-SIZE
   250-X-RCPTLIMIT 100
   250-8BITMIME
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: Kioq
S: 334 UGFzc3dvcmQ6
C: Kioq
S: 535 blad autoryzacji, niepoprawny login lub haslo / auth failure

Example 2 - Connecting to smtp.wp.pl and using the correct login and password:

S: 220 smtp.wp.pl ESMTP
C: EHLO: mydomain.com
S: 250-smtp.wp.pl
   250-PIPELINING
   250-AUTH=LOGIN PLAIN
   250-AUTH LOGIN PLAIN
   250-STARTTLS
   250-SIZE
   250-X-RCPTLIMIT 100
   250-8BITMIME
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: xxx
S: 334 UGFzc3dvcmQ6
C: xxx
S: 235 go ahead
C: MAIL FROM:<me@mydomain.com>
S: 250 ok
C: RCPR TO:<friend@domain.com>
S: 250 ok
C: DATA
S: 234 go ahead
C: Date: Sun, 24 Aug 2008 22:43:45
   From: JP<mail@domain.com>
   X-Mailer: The Bat! (v3.02) Professional
   Replay-to:mail@domain.com
   X-Priority: 3 (Normal)
   To:<friend@domain.com>
   Subject: The message
   MIME Version 1.0
   Content-Type: multipart/mixed; boundary="__MESSAGE__ID__54yg6f6h6y456345"

   
   --__MESSAGE__ID__54yg6f6h6y456345
   Content-type: text/plain; charset=US-ASCII
   Content-Transfer-Encoding: 7bit

   This is my message.

   --__MESSAGE__ID__54yg6f6h6y456345
   Content-Type: application/x-msdownload; name="test.exe"
   Content-Transfer-Encoding: base64
   Content-Disposition: attachment; filename="test.exe"
   
   TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   (...)
   SU5HWFhQQURESU5HUEFERElOR1hYUEFERElOR1BBRERJTkdYWFBBRERJTkdQQURESU5HWA==
   --__MESSAGE__ID__54yg6f6h6y456345
   Content-Type: application/x-msdownload; name="test2.jpg"
   Content-Transfer-Encoding: base64
   Content-Disposition: attachment; filename="test2.jpg"
   
   /9j/4Sv+RXhpZgAASUkqAAgAAAAJAA8BAgAGAAAAegAAABABAgAWAAAAgAAAABIBAwABAAAA
   (...)
   A6YxR5YJJ5zUu6ZW4+NjC24E4q5Dcox5I+lRI0iWAAV9aay+lTctoYTjrml+9irRmz//2Q==
   
   --__MESSAGE__ID__54yg6f6h6y456345--
   
   .
   
S: 250 ok xxx qp xxx
C: QUIT
S: 221 smtp.wp.pl

Example 3 - Connecting to smtp.gmail.com:

S: 220 mx.google.com ESMTP
   w28sm1561195uge.4
C: EHLO: mydomain.com
S: 250-mx.google.com at your service [xxx.xxx.xxx.xxx],
   250-SIZE 28311552
   250-8BITMIME
   250-STARTTLS
   250 ENHANCEDSTATUSCODES
C: AUTH LOGIN
S: 530 5.7.0 Must issue a STARTTLS command first. w28sm1561195uge.4

Example 4 - Connecting to smtp.bizmail.yahoo.com and using an incorrect login or password:

S: 220 smtp103.biz.mail.re2.yahoo.com ESMTP
C: EHLO: mydomain.com
S: 250-smtp103.biz.mail.re2.yahoo.com
   250-AUTH LOGIN PLAIN XYMCOOKIE
   250-PIPELINING
   250-8BITMIME
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: dG9t
S: 334 UGFzc3dvcmQ6
C: bmVyb24xMg==
S: 535 authorization failed (#5.7.0)

Implementation of the CSmtp class

Implementation of the CSmtp class is very similar for Windows and Linux OSs. There is nothing surprising in this, because Windows uses the generally accepted Berkeley sockets application programming interface (API) [10]. The differences are shown in Table 1 (applies only to the CSmtp class implementation).

Table 1. Differences in implementation.
Windows Linux
Winsock initialization needed No Winsock initialization
Uses function closesocket Uses function close
Uses function ioctlsocket Uses function ioctl
Defined helpful type aliases; i.e., SOCKET, SOCKADDR_IN, LPHOSTENT, LPSERVENT, LPIN_ADDR, LPSOCKADDR Additional types aliase should be defined

Below there are shown steps to be taken when connecting to a remote SMTP server.

  1. In Windows only, initialize Winsock2 (function: WSAStartup).
  2. Get socket descriptor on the local machine (function: socket).
  3. Convert port value (i.e., 25) to TCP/IP byte order (function: htons).
  4. Obtain Internet address of the remote machine (functions: inet_addr, gethostbyname).
  5. If non-blocking mode is used, set socket parameters (function: ioctl/ioctlsocket). Check necessarily what returns each function which will be called after ioctl/ioctlsocket (see next section - Using non-blocking mode).
  6. Connect to the remote server (function: connect).
  7. Introduce yourself - EHLO <SP> <domain> <CRLF>.
  8. Send AUTH LOGIN <CRLF> and another command described in the section "Typical scenarios while sending the email" (functions: send, recv).
  9. Finish the conversation with QUIT <CRLF>.
  10. Close connection with remote machine (function: close/closesocket).
  11. In Windows only, free Winsock2 resources (function: WSACleanup).

Using non-blocking mode

In the latest version of the program, I have used a non-blocking connection. There are many strategies to implement the non-blocking mode (i.e., Select model, WSAAsyncSelect model, WSAEventSelect model, or Completion port I/O model). In my code, I have decided to use the Select model. It is not so complicated as other methods, and works efficiently with a basic connection - one client to one server. The advantages of using non-blocking mode are: the program does not suspend if the remote server stops responding, data can be sent in uneven and unequal portions. Disadvantage of this approach is its complexity. After placing the socket in non-blocking mode, the next API calls are immediately closed. Typically, these calls fail with a an error WSAEWOULDBLOCK (Windows) or EINPROGRESS (Linux), which means that the requested operation is not completed so far. Therefore, in non-blocking mode, a lot of attention should be devoted to analyze errors returned by the API functions. In Select model, we are using the select function [11] after calling such API functions as: send, recv, connect, accept, and others. The parameter ndfs in select is ignored in Windows, but in Linux, it is the highest-numbered file descriptor in any of the three sets (fd_set *readfds, fd_set *writefds, fd_set *exceptfds) plus 1. To illustrate the difference between blocking and non-blocking modes, presented here are two ways of connecting to the remote server. For greater legibility, I have only presented versions for Windows (preprocessor directives were omitted).

/*Connecting to the remote server in blocking mode*/
SOCKET CSmtp::ConnectRemoteServer(const char *szServer,const unsigned short nPort_)
{
    unsigned short nPort = 0;
    LPSERVENT lpServEnt;
    SOCKADDR_IN sockAddr;
    unsigned long ul = 1;
    int res = 0;

    SOCKET hSocket = INVALID_SOCKET;

    if((hSocket = socket(PF_INET, SOCK_STREAM,0)) == INVALID_SOCKET)
        throw ECSmtp(ECSmtp::WSA_INVALID_SOCKET);

    if(nPort_ != 0)
        nPort = htons(nPort_);
    else
    {
        lpServEnt = getservbyname("mail", 0);
        if (lpServEnt == NULL)
            nPort = htons(25);
        else 
            nPort = lpServEnt->s_port;
    }

    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = nPort;
    if((sockAddr.sin_addr.s_addr = inet_addr(szServer)) == INADDR_NONE)
    {
        LPHOSTENT host;

        host = gethostbyname(szServer);
        if (host)
            memcpy(&sockAddr.sin_addr,host->h_addr_list[0],host->h_length);
        else
        {
            closesocket(hSocket);
            throw ECSmtp(ECSmtp::WSA_GETHOSTBY_NAME_ADDR);
        }
    }

    if(connect(hSocket,(LPSOCKADDR)&sockAddr,sizeof(sockAddr)) == SOCKET_ERROR)
    {
        closesocket(hSocket);
        throw ECSmtp(ECSmtp::WSA_CONNECT);
    }

    return hSocket;
}

/*Connecting to the remote server in non-blocking mode*/
SOCKET CSmtp::ConnectRemoteServer(const char *szServer,const unsigned short nPort_)
{
    unsigned short nPort = 0;
    LPSERVENT lpServEnt;
    SOCKADDR_IN sockAddr;
    unsigned long ul = 1;
    fd_set fdwrite,fdexcept;
    timeval timeout;
    int res = 0;

    timeout.tv_sec = TIME_IN_SEC;
    timeout.tv_usec = 0;

    SOCKET hSocket = INVALID_SOCKET;

    if((hSocket = socket(PF_INET, SOCK_STREAM,0)) == INVALID_SOCKET)
        throw ECSmtp(ECSmtp::WSA_INVALID_SOCKET);

    if(nPort_ != 0)
        nPort = htons(nPort_);
    else
    {
        lpServEnt = getservbyname("mail", 0);
        if (lpServEnt == NULL)
            nPort = htons(25);
        else 
            nPort = lpServEnt->s_port;
    }

    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = nPort;
    if((sockAddr.sin_addr.s_addr = inet_addr(szServer)) == INADDR_NONE)
    {
        LPHOSTENT host;

        host = gethostbyname(szServer);
        if (host)
            memcpy(&sockAddr.sin_addr,host->h_addr_list[0],host->h_length);
        else
        {
            closesocket(hSocket);
            throw ECSmtp(ECSmtp::WSA_GETHOSTBY_NAME_ADDR);
        }
    }

    // start non-blocking mode for socket:
    if(ioctlsocket(hSocket,FIONBIO, (unsigned long*)&ul) == SOCKET_ERROR)
    {
        closesocket(hSocket);
        throw ECSmtp(ECSmtp::WSA_IOCTLSOCKET);
    }

    if(connect(hSocket,(LPSOCKADDR)&sockAddr,sizeof(sockAddr)) == SOCKET_ERROR)
    {
        if(WSAGetLastError() != WSAEWOULDBLOCK)
        {
            closesocket(hSocket);
            throw ECSmtp(ECSmtp::WSA_CONNECT);
        }
    }
    else
        return hSocket;

    while(true)
    {
        FD_ZERO(&fdwrite);
        FD_ZERO(&fdexcept);

        FD_SET(hSocket,&fdwrite);
        FD_SET(hSocket,&fdexcept);

        if((res = select(hSocket+1,NULL,&fdwrite,&fdexcept,&timeout)) == SOCKET_ERROR)
        {
            closesocket(hSocket);
            throw ECSmtp(ECSmtp::WSA_SELECT);
        }

        if(!res)
        {
            closesocket(hSocket);
            throw ECSmtp(ECSmtp::SELECT_TIMEOUT);
        }
        
        if(res && FD_ISSET(hSocket,&fdwrite))
            break;

        if(res && FD_ISSET(hSocket,&fdexcept))
        {
            closesocket(hSocket);
            throw ECSmtp(ECSmtp::WSA_SELECT);
        }
    } // while

    FD_CLR(hSocket,&fdwrite);
    FD_CLR(hSocket,&fdexcept);

    return hSocket;
}

Usage

#include "CSmtp.h"
#include <iostream>

int main()
{
    bool bError = false;

    try
    {
        CSmtp mail;

        mail.SetSMTPServer("smtp.domain.com",25);
        mail.SetLogin("***");
        mail.SetPassword("***");
        mail.SetSenderName("User");
        mail.SetSenderMail("user@domain.com");
        mail.SetReplyTo("user@domain.com");
        mail.SetSubject("The message");
        mail.AddRecipient("friend@domain2.com");
        mail.SetXPriority(XPRIORITY_NORMAL);
        mail.SetXMailer("The Bat! (v3.02) Professional");
        mail.AddMsgLine("Hello,");
        mail.AddMsgLine("");
        mail.AddMsgLine("How are you today?");
        mail.AddMsgLine("");
        mail.AddMsgLine("Regards");
        mail.AddMsgLine("--");
        mail.AddMsgLine("User");
        mail.AddAttachment("c:\\test.exe");
        mail.AddAttachment("c:\\test2.jpg");
    
        mail.Send();
    }
    catch(ECSmtp e)
    {
        std::cout << "Error: " << e.GetErrorText().c_str() << ".\n";
        bError = true;
    }

    if(!bError)
    {
        std::cout << "Mail was send successfully.\n";
        return 0;
    }
    else
        return 1;
}

Author's notes

  1. If you have problems sending an email, use Visual Studio's debugger and analyze the conversation between your SMTP server and the client; perhaps, your server needs a different kind of authentication or doesn't need it at all.
  2. You are not allowed to use the CSmtp class for spamming.

Bibliography

  1. CFastSmtp - Fast and easy SMTP class...
  2. Simple Mail Transfer Protocol RFC 821
  3. Standard for the Format of ARPA Internet Text Messages RFC 822
  4. SMTP Service Extension for Authentication RFC 2554
  5. MIME: Format of Internet Message Bodies RFC 2045
  6. MIME: Media Types RFC 2046
  7. MIME: Message Header Extensions for Non-ASCII Text RFC 2047
  8. MIME: Registration Procedures RFC 2048
  9. MIME: Conformance Criteria and Examples RFC 2049
  10. The Berkeley Sockets Application Programming Interface (API)
  11. select function

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jakub Piwowarczyk
Engineer Technical University of Lodz
Poland Poland
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionhow to send an email via a proxy?memberASERERTA@#@s8 May '13 - 8:00 
how to send email via a proxy?
does it have the setproxy function?
Questionnot able to send mail without username and password.memberrohitnegi26 Feb '13 - 21:47 
Hi,
 
I have used this code and able to send mail with username and password. But I have to send mail without username and password. Is this can not be done with this code or if it is then where do i have to change in the code.
 
thank you
QuestionGot an ErrormemberSonal Dave26 Feb '13 - 17:58 
I got an error saying server returned error after sending DATA.
 
I used the program a lot and its awesome but now suddenly I have this error message. Can you please tell me how to solve this or what is wrong?
QuestionSome Questionsmemberxx041723 Jan '13 - 22:34 
It's a good article,but for me it have some problems:
 
1.If the mail content have chinese word,the display may be wrong for some email receivers.
 
2.If you add the support for send html content,it will be great.
 
3.The previous api SetMessageBody() should keep so I can add all text content once.
BugNot working on Vista and windows 7membertrokfox6 Jan '13 - 10:56 
Hello,
 
I am not able to make it work for Windows 7 and Vista, i receive no mail,but it work great on Windows XP, any one had this problem?
 
I am using GetPrivateProfileString to get information from a ini file to use the smtp client.
ex: mail.SetLogin(VALUEFROM_ini_file);
QuestionNo password?memberMember 86882605 Dec '12 - 11:11 
Hello,
 
In a non SSL, non TLS environment how do you "login" with no password (can't seem to skip using mail.SetPassword)
 
or is just not possible with this code the way it is written?
 
thank you
GeneralMy vote of 5membergndnet12 Oct '12 - 8:55 
very good article
Questionportability bug w/ attachment namememberSpike!4 Sep '12 - 11:08 
The code does this:
strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("\\") + 1]);
 
But in Linux, the path separator is "/"....
AnswerRe: portability bug w/ attachment namememberDavid Johns3 Nov '12 - 16:32 
Great point. Is there better solution than:
 
		for(FileId=0;FileId<Attachments.size();FileId++)
		{
			strcpy(FileName,Attachments[FileId].c_str());
 
			sprintf(SendBuf,"--%s\r\n",BOUNDARY_TEXT);
			strcat(SendBuf,"Content-Type: application/x-msdownload; name=\"");
#ifndef LINUX
			strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("\\") + 1]);
#else
			strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("/") + 1]);
#endif
			strcat(SendBuf,"\"\r\n");
			strcat(SendBuf,"Content-Transfer-Encoding: base64\r\n");
			strcat(SendBuf,"Content-Disposition: attachment; filename=\"");
#ifndef LINUX
			strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("\\") + 1]);
#else
			strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("/") + 1]);
#endif
			strcat(SendBuf,"\"\r\n");
			strcat(SendBuf,"\r\n");
			.
			.
			.

Questionportability bugs w/ std::string and exceptionsmemberSpike!4 Sep '12 - 8:01 
uSTL doesn't like it (blows up w/ an assert) when you set an xstring by doing this:
m_stdstring = m_stdstring.insert(0, newstring);
This kicks in a constructor but works:
m_stdstring = newstring;
 
If you're using it on an embedded system, you can't throw exceptions (they take up a lot of code space so they're generally disabled). You need to do the old C technique of returning error codes...
AnswerRe: portability bugs w/ std::string and exceptionsmemberDavid Johns3 Nov '12 - 16:26 
Thanks for the feedback. I'm thinking of changing all the conversions from const char* to using the = operator. This seems to be completely acceptable. Can anyone comment on if there is any advantage to the current method using the std::string::insert function instead?
GeneralMy vote of 5memberEvren Daglioglu28 Jun '12 - 0:45 
thanks for your contribution...well done
GeneralMy vote of 4memberxComaWhitex22 Apr '12 - 14:05 
Very nice library. I just have a question. Due to many mail servers using different security features. Do you have to make a socket for each type? How are attachments handled?
 
I only gave it a 4 due to some problems I see with your code.
- Why do you mix const char* with std::string? Why not just use const std::string& x rather than just mixing them? When you do need to use const char* x just convert it to it via c_str().
 
- Mixing Linux code with Windows code just makes the source code more cluttered and less maintainable. Why not just push them in their own source implementation then write another source file that does the platform checks?
 
#ifdef (Windows)
return WindowsFunction();
#else
return LinuxMacFunction();
#endif
QuestionServer returned error after sending MAIL FROMmembernamsaray26 Jan '12 - 8:41 
I consistently get this error
"Server returned error after sending MAIL FROM"
when I runn my application on a Windows Server 2008 R2.
The same application works fine on Windows 7.
Any suggestions?
Any help is appreciated,
 
Thank you,
Gene
Questionbug fixes GetLocalHostName(), Send()memberjerko30 Nov '11 - 23:14 
1st of all nice work...
 
your code could be a little prettier but work is done.
 
const char* CSmtp::GetLocalHostName() const
{
    char* str = NULL;
 
    if((str = new char[255]) == NULL)
        throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
    if(gethostname(str,255) == SOCKET_ERROR)
    {
        delete[] str;
        throw ECSmtp(ECSmtp::WSA_HOSTNAME);
    }
 
    // forgeting something ??? str -> m_sLocalHostName is missing

    delete[] str;
    return m_sLocalHostName.c_str();
}
 
instead i use this (so i can use it from domain):
 
void CSmtp::SetLocalHostName(const char *sLocalHostName)
{
    m_sLocalHostName.erase();
    m_sLocalHostName.insert(0,sLocalHostName);
}
 
const char* CSmtp::GetLocalHostName() const
{
    return m_sLocalHostName.c_str();
}
 

and from outside setting the comp name
 
...
mail.SetLocalHostName( GetComputerName() ); // GetComputerName() method to return the computername
...
 

for server that does not need authentication u must skip the authentication part..
so I use bool param to skip it
 
like this:
 
void CSmtp::Send(BOOL bAuthRequired)
{
    unsigned int i,rcpt_count,res,FileId;
    char *FileBuf = NULL, *FileName = NULL;
    FILE* hFile = NULL;
    unsigned long int FileSize,TotalSize,MsgPart;
    bool bAccepted;
 
    // ***** CONNECTING TO SMTP SERVER *****

    // connecting to remote host:
    if( (hSocket = ConnectRemoteServer(m_sSMTPSrvName.c_str(), m_iSMTPSrvPort)) == INVALID_SOCKET )
        throw ECSmtp(ECSmtp::WSA_INVALID_SOCKET);
 
    bAccepted = false;
    do
    {
        ReceiveData();
        switch(SmtpXYZdigits())
        {
            case 220:
                bAccepted = true;
                break;
            default:
                throw ECSmtp(ECSmtp::SERVER_NOT_READY);
        }
    }while(!bAccepted);
 
    // EHLO <SP> <domain> <CRLF>
    sprintf(SendBuf,"EHLO %s\r\n",GetLocalHostName()!=NULL ? m_sLocalHostName.c_str() : "domain");
    SendData();
    bAccepted = false;
    do
    {
        ReceiveData();
        switch(SmtpXYZdigits())
        {
            case 250:
                bAccepted = true;
                break;
            default:
                throw ECSmtp(ECSmtp::COMMAND_EHLO);
        }
    }while(!bAccepted);
 
    if ( bAuthRequired ) // this is skipped if auth is not required
    {
        // AUTH <SP> LOGIN <CRLF>
        strcpy(SendBuf,"AUTH LOGIN\r\n");
        SendData();
        bAccepted = false;
        do
        {
            ReceiveData();
            switch(SmtpXYZdigits())
            {
                case 250:
                    break;
                case 334:
                    bAccepted = true;
                    break;
                default:
                    throw ECSmtp(ECSmtp::COMMAND_AUTH_LOGIN);
            }
        }while(!bAccepted);
 
        // send login:
        if(!m_sLogin.size())
            throw ECSmtp(ECSmtp::UNDEF_LOGIN);
        std::string encoded_login = base64_encode(reinterpret_cast<const unsigned char*>(m_sLogin.c_str()),m_sLogin.size());
        sprintf(SendBuf,"%s\r\n",encoded_login.c_str());
        SendData();
        bAccepted = false;
        do
        {
            ReceiveData();
            switch(SmtpXYZdigits())
            {
                case 334:
                    bAccepted = true;
                    break;
                default:
                    throw ECSmtp(ECSmtp::UNDEF_XYZ_RESPONSE);
            }
        }while(!bAccepted);
 
        // send password:
        if(!m_sPassword.size())
            throw ECSmtp(ECSmtp::UNDEF_PASSWORD);
        std::string encoded_password = base64_encode(reinterpret_cast<const unsigned char*>(m_sPassword.c_str()),m_sPassword.size());
        sprintf(SendBuf,"%s\r\n",encoded_password.c_str());
        SendData();
        bAccepted = false;
        do
        {
            ReceiveData();
            switch(SmtpXYZdigits())
            {
                case 235:
                    bAccepted = true;
                    break;
                case 334:
                    break;
                case 535:
                    throw ECSmtp(ECSmtp::BAD_LOGIN_PASS);
                default:
                    throw ECSmtp(ECSmtp::UNDEF_XYZ_RESPONSE);
            }
        }while(!bAccepted);
    }
 
    // ***** SENDING E-MAIL *****

    // MAIL <SP> FROM:<reverse-path> <CRLF>
    if(!m_sMailFrom.size())
        throw ECSmtp(ECSmtp::UNDEF_MAIL_FROM);
    sprintf(SendBuf,"MAIL FROM:<%s>\r\n",m_sMailFrom.c_str());
    SendData();
    bAccepted = false;
    do
    {
        ReceiveData();
        switch(SmtpXYZdigits())
        {
            case 250:
                bAccepted = true;
                break;
            default:
                throw ECSmtp(ECSmtp::COMMAND_MAIL_FROM);
        }
    }while(!bAccepted);
 
    // RCPT <SP> TO:<forward-path> <CRLF>
    if(!(rcpt_count = Recipients.size()))
        throw ECSmtp(ECSmtp::UNDEF_RECIPIENTS);
    for(i=0;i<Recipients.size();i++)
    {
        sprintf(SendBuf,"RCPT TO:<%s>\r\n",(Recipients.at(i).Mail).c_str());
        SendData();
        bAccepted = false;
        do
        {
            ReceiveData();
            switch(SmtpXYZdigits())
            {
                case 250:
                    bAccepted = true;
                    break;
                default:
                    rcpt_count--;
            }
        }while(!bAccepted);
    }
    if(rcpt_count <= 0)
        throw ECSmtp(ECSmtp::COMMAND_RCPT_TO);
 
    for(i=0;i<CCRecipients.size();i++)
    {
        sprintf(SendBuf,"RCPT TO:<%s>\r\n",(CCRecipients.at(i).Mail).c_str());
        SendData();
        bAccepted = false;
        do
        {
            ReceiveData();
            switch(SmtpXYZdigits())
            {
                case 250:
                    bAccepted = true;
                    break;
                default:
                    ; // not necessary to throw
            }
        }while(!bAccepted);
    }
 
    for(i=0;i<BCCRecipients.size();i++)
    {
        sprintf(SendBuf,"RCPT TO:<%s>\r\n",(BCCRecipients.at(i).Mail).c_str());
        SendData();
        bAccepted = false;
        do
        {
            ReceiveData();
            switch(SmtpXYZdigits())
            {
                case 250:
                    bAccepted = true;
                    break;
                default:
                    ; // not necessary to throw
            }
        }while(!bAccepted);
    }
 
    // DATA <CRLF>
    strcpy(SendBuf,"DATA\r\n");
    SendData();
    bAccepted = false;
    do
    {
        ReceiveData();
        switch(SmtpXYZdigits())
        {
            case 354:
                bAccepted = true;
                break;
            case 250:
                break;
            default:
                throw ECSmtp(ECSmtp::COMMAND_DATA);
        }
    }while(!bAccepted);
 
    // send header(s)
    FormatHeader(SendBuf);
    SendData();
 
    // send text message
    if(GetMsgLines())
    {
        for(i=0;i<GetMsgLines();i++)
        {
            sprintf(SendBuf,"%s\r\n",GetMsgLineText(i));
            SendData();
        }
    }
    else
    {
        sprintf(SendBuf,"%s\r\n"," ");
        SendData();
    }
 
    // next goes attachments (if they are)
    if((FileBuf = new char[55]) == NULL)
        throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
 
    if((FileName = new char[255]) == NULL)
        throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
 
    TotalSize = 0;
    for(FileId=0;FileId<Attachments.size();FileId++)
    {
        strcpy(FileName,Attachments[FileId].c_str());
 
        sprintf(SendBuf,"--%s\r\n",BOUNDARY_TEXT);
        strcat(SendBuf,"Content-Type: application/x-msdownload; name=\"");
        strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("\\") + 1]);
        strcat(SendBuf,"\"\r\n");
        strcat(SendBuf,"Content-Transfer-Encoding: base64\r\n");
        strcat(SendBuf,"Content-Disposition: attachment; filename=\"");
        strcat(SendBuf,&FileName[Attachments[FileId].find_last_of("\\") + 1]);
        strcat(SendBuf,"\"\r\n");
        strcat(SendBuf,"\r\n");
 
        SendData();
 
        // opening the file:
        hFile = fopen(FileName,"rb");
        if(hFile == NULL)
            throw ECSmtp(ECSmtp::FILE_NOT_EXIST);
 
        // checking file size:
        FileSize = 0;
        while(!feof(hFile))
            FileSize += fread(FileBuf,sizeof(char),54,hFile);
        TotalSize += FileSize;
 
        // sending the file:
        if(TotalSize/1024 > MSG_SIZE_IN_MB*1024)
            throw ECSmtp(ECSmtp::MSG_TOO_BIG);
        else
        {
            fseek (hFile,0,SEEK_SET);
 
            MsgPart = 0;
            for(i=0;i<FileSize/54+1;i++)
            {
                res = fread(FileBuf,sizeof(char),54,hFile);
                MsgPart ? strcat(SendBuf,base64_encode(reinterpret_cast<const unsigned char*>(FileBuf),res).c_str())
                          : strcpy(SendBuf,base64_encode(reinterpret_cast<const unsigned char*>(FileBuf),res).c_str());
                strcat(SendBuf,"\r\n");
                MsgPart += res + 2;
                if(MsgPart >= BUFFER_SIZE/2)
                { // sending part of the message
                    MsgPart = 0;
                    SendData(); // FileBuf, FileName, fclose(hFile);
                }
            }
            if(MsgPart)
            {
                SendData(); // FileBuf, FileName, fclose(hFile);
            }
        }
        fclose(hFile);
    }
    delete[] FileBuf;
    delete[] FileName;
 
    // sending last message block (if there is one or more attachments)
    if(Attachments.size())
    {
        sprintf(SendBuf,"\r\n--%s--\r\n",BOUNDARY_TEXT);
        SendData();
    }
 
    // <CRLF> . <CRLF>
    strcpy(SendBuf,"\r\n.\r\n");
    SendData();
    bAccepted = false;
    do
    {
        ReceiveData();
        switch(SmtpXYZdigits())
        {
            case 250:
                bAccepted = true;
                break;
            default:
                throw ECSmtp(ECSmtp::MSG_BODY_ERROR);
        }
    }while(!bAccepted);
 
    // ***** CLOSING CONNECTION *****

    // QUIT <CRLF>
    strcpy(SendBuf,"QUIT\r\n");
    SendData();
    bAccepted = false;
    do
    {
        ReceiveData();
        switch(SmtpXYZdigits())
        {
            case 221:
                bAccepted = true;
                break;
            default:
                throw ECSmtp(ECSmtp::COMMAND_QUIT);
        }
    }while(!bAccepted);
 
#ifdef LINUX
    close(hSocket);
#else
    closesocket(hSocket);
#endif
    hSocket = NULL;
}
 
bye
AnswerRe: bug fixes GetLocalHostName(), Send()memberAlan P Brown1 Dec '11 - 22:06 
There is a later derivative of this project within CodeProject at SMTP Client with SSL/TLS[^].
For this korisk also supplies a solution (see comments on that page) which includes the ability to modify various features, including suppression of authentication.
Unfortunately korisks mods are based on v1.9 but the source is now at 2.0; so it is necessary to merge the differences into 2.0.
AnswerRe: bug fixes GetLocalHostName(), Send()memberSpike!4 Sep '12 - 8:12 
FWIW, I modified Send so that if m_sLogin.size() was > 0, then it does authentication.
 
Not every mail server requires authentication....
 
I also had to replace GetLocalHostName as you did...embedded systems don't really have hostnames....
GeneralErrormembermin_2_max12 May '11 - 4:57 
Thanks. But I got an error message-
 
Error: Server returned error after sending EHLO.
 
Max
GeneralRe: Errormembermin_2_max17 May '11 - 18:12 
The solution found:
 
http://www.codeproject.com/Messages/3587166/Re-Possible-Solution-To-Misc-EHLO-Errors.aspx[^]
 
And thank you very much! (with a vote of 5)
 
Max
modified on Wednesday, May 18, 2011 12:46 AM

GeneralRe: Errormemberhighersky16 Aug '12 - 21:32 
i also get this error
GeneralMy vote of 3memberorighost16 Apr '11 - 15:43 
there is a buo
GeneralRe: My vote of 3memberJakub Piwowarczyk10 Dec '11 - 6:22 
What is buo? Are you using your own language Smile | :)
Jakub Piwowarczyk

QuestionHTML bodymembernj9626 Jan '11 - 10:53 
This code is absolutely great! Maybe I'm missing something but how would I send an email with an HTML formatted message body?
 
Thanks again for the great class!
AnswerRe: HTML bodymemberJakub Piwowarczyk27 Jan '11 - 6:58 
If you want to send html use the following content types tags:
 
STRACTURE THE SAME LIKE BEFORE...
 
--_004_4D3BE6D5235E6B4386FE7FA971A355B71CACAF9127018DNAMSG0101_ <= set your own id
Content-Type: multipart/alternative;
boundary="_004_4D3BE6D5235E6B4386FE7FA971A355B71CACAF9127018DNAMSG0101_"
 
--_004_4D3BE6D5235E6B4386FE7FA971A355B71CACAF9127018DNAMSG0101_
Content-Type: text/plain; charset="iso-8859-2" <= here put your charset
Content-Transfer-Encoding: quoted-printable
 
PLAIN TEXT HERE...
 
--_004_4D3BE6D5235E6B4386FE7FA971A355B71CACAF9127018DNAMSG0101_
Content-Type: text/html; charset="iso-8859-2"
Content-Transfer-Encoding: quoted-printable
 
HTML HERE...
 
--_004_4D3BE6D5235E6B4386FE7FA971A355B71CACAF9127018DNAMSG0101_--
--_004_4D3BE6D5235E6B4386FE7FA971A355B71CACAF9127018DNAMSG0101_
Content-Type: image/jpeg; name="image001.jpg"
Content-Description: image001.jpg
Content-Disposition: inline; filename="image001.jpg"; size=4231;
creation-date="Thu, 29 Jul 2010 04:58:23 GMT";
modification-date="Thu, 29 Jul 2010 04:58:23 GMT"
Content-ID: &lt;image001.jpg@01CB2F26.1C363B50&gt;
Content-Transfer-Encoding: base64
 
IMAGE IN BASE64 HERE...
 
For additional informations read proper RFC documents.
Jakub Piwowarczyk

GeneralRe: HTML bodymemberRobert Valentino26 Jan '12 - 14:12 
Using the CSmtp to send emails and it is working fine (one little problem is that when I send to a hotmail.com it always goes into the junk folder).
 
I need to send a HTML email and I just do not understand ANYTHING about the example you give above.
 
Could / would you please provide an actual example
 

Thanks
 
Bob Valentino
GeneralError:Server returned error after sending EHLOmembercaixin29 Nov '10 - 1:33 
Error:Server returned error after sending EHLO
 
I use version 1.5 code, can send mail normally, but when I use version 1.8, it show the error.
 
I sure login information is right.
GeneralRe: Error:Server returned error after sending EHLOmembertmxq5610 Jan '11 - 12:14 
I got the same problem with you,
Could someone tell us why? Sigh | :sigh:
AnswerRe: Error:Server returned error after sending EHLOmemberJakub Piwowarczyk11 Jan '11 - 8:25 
Could one of You make two tests for me:
1. in Smtp client class 1.5 ver copy buffer state after ReceiveData() was called
2. do the same for ver 1.8
 
and send me your results (on forum), it will be helpful for me to determine the reason for this bug
Jakub Piwowarczyk

GeneralRe: Error:Server returned error after sending EHLOmembersvyeo30 Jan '11 - 23:44 
I am using v1.8 on linux, i have the same problem. Is this bug fixed? How can i help?
GeneralRe: Error:Server returned error after sending EHLOmembermin_2_max17 May '11 - 18:12 
The solution found:
 
http://www.codeproject.com/Messages/3587166/Re-Possible-Solution-To-Misc-EHLO-Errors.aspx[^]
GeneralRe: Error:Server returned error after sending EHLOmemberAyyavu P1 Nov '11 - 5:15 
Hi All,
 
Here there is small syntax issue in SMTP send mail function.
change the ELHO to HELO.
HELO is the correct protocol.
Its working for me.
 
Regards,
Ayyavu P
Ayyavu P
Updating mind

GeneralRe: Error:Server returned error after sending EHLOmemberjerko30 Nov '11 - 23:26 
it's not problem here ...
 
read bug fixes.
..should correct issue
GeneralRe: Error:Server returned error after sending EHLOmembervoidcrafter9 Jan '13 - 19:56 
Prolly I post this a bit later, but started using your program and encountered the very same issue.
I managed to resolve it(for me atleast) tho, the problem is caused by those lines:
const char* CSmtp::GetLocalHostName() const
{
char* str = NULL;
 
if((str = new char[255]) == NULL)
throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
if(gethostname(str,255) == SOCKET_ERROR)
{
delete[] str;
throw ECSmtp(ECSmtp::WSA_HOSTNAME);
}
delete[] str;
return m_sLocalHostName.c_str();
}
I am using win7 and VS2008(yes, converted it to it), for me atleast m_sLocalHostName is not setting anywhere, causing the
sprintf(SendBuf,"EHLO %s\r\n",GetLocalHostName()!=NULL ? m_sLocalHostName.c_str() :"domain");
SendData();
line to fail
(since it's not NULL but still is empty string, whethere there should be the host name it gives an empty string).
That's about it.
There should be one "else" for the if(gethostname(str,255) == SOCKET_ERROR) that returns the proper local host name, or you can fill the m_sLocalHostName itself(but for that you should change the prototype of the function to not be "const" )
Hope I helped Smile | :)
GeneralIssue with AddMsgLine methodmemberEugeneKarnygin26 Nov '10 - 4:53 
Hi,
 
Thank you for the code.
I found issue with AddMsgLine method. I convert the code into static library and include into my application.
The application crashed when i call the method trying to add the line to message body (compiled on Windows XP and VC++8) in release configuration (in debug all is working fine)
I don't know why but access violation exception in the method. Actually it happens in STL vector assign.
 
void AddMsgLine(const char* Text)
{
MsgBody.insert(MsgBody.end(), Text);
}

 
I have replaced this for
 
void AddMsgLine(const char* Text)
{
MsgBody.push_back(Text);
}

 
and all became fine without exception.
GeneralMy vote of 5memberjacksp19 Nov '10 - 20:13 
very nice code, thank you.
QuestionRename Attachement.memberkirubalks8 Nov '10 - 16:43 
I want to rename the attachment.
Can you please tell me how it can be done?
Thanks.
AnswerRe: Rename Attachement.membermin_2_max17 May '11 - 18:02 
I would say it's more natural not to have the logic of renaming the attachments being a part of a SMTP lib.
 
Max
GeneralVirus Alert.memberkirubalks18 Oct '10 - 2:29 
This is just working like a charm..
but when i execute the application Kaspersky Internet Security pops up and warning me Its highly dangers coz the application is not digitally signed.
 
i just played with the code & i found that.
if i comment the mail.Send() line in main.cpp.. antivirus not warning me..
if i remove the comment then it warns..
any idea to solve this problem
thank u very much this is an awesome job.
GeneralRe: Virus Alert.memberJakub Piwowarczyk24 Oct '10 - 0:15 
There is nothing suprising that uncommenting Send() will turn off warnings - this function does the main job in the programe as well as connecting to the reomote server. If you are using firewall or antivirus with firewall it will block even commercial programs like iexplorer. So you ought to add this program to "trusted programs" and that is it.
Jakub Piwowarczyk

GeneralMy vote of 5memberkirubalks18 Oct '10 - 2:13 
Its working like a charm.
AnswerPossible Solution To Misc EHLO ErrorsmemberLeon Hauck23 Aug '10 - 11:12 
I just started using this code and found that my email server was rejecting the email that was being sent due to the EHLO command on line 351 was not sending the hostname of the system that it was running on, due to m_sLocalHostName never being initialized.
 
What I wound up doing was:
 
1) remove the trailing const in the definition of the GetLocalHostName() function on line 1149 of CSmtp.cpp
 
2) right before the delete of str added the following line:
 
m_sLocalHostName.assign(( char * ) str);
 
3) removed the trailing const on line 129 where GetLocalHostName() is declared in CSmtp.h
 
Without removing consts in steps 1 and 3 I was getting a compiler error for step 2.
 
Hope this helps.
GeneralRe: Possible Solution To Misc EHLO ErrorsmemberJakub Piwowarczyk3 Sep '10 - 3:08 
Hi,
To fix it you could do:
1. Change function GetLocalHostName
const char* CSmtp::GetLocalHostName() const
{
	return m_sLocalHostName.c_str();
}
 

 
2. Change CSmtp constructor
 
CSmtp::CSmtp()
{
	m_iXPriority = XPRIORITY_NORMAL;
	m_iSMTPSrvPort = 0;
 
#ifndef LINUX
	// Initialize WinSock
	WSADATA wsaData;
	WORD wVer = MAKEWORD(2,2);    
	if (WSAStartup(wVer,&wsaData) != NO_ERROR)
		throw ECSmtp(ECSmtp::WSA_STARTUP);
	if (LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) 
	{
		WSACleanup();
		throw ECSmtp(ECSmtp::WSA_VER);
	}
#endif
 
	char* str = NULL;
	if((str = new char[255]) == NULL)
		throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
	if(gethostname(str,255) == SOCKET_ERROR)
	{
		delete[] str;
		throw ECSmtp(ECSmtp::WSA_HOSTNAME);
	}
	m_sLocalHostName.insert(0,str);
	delete[] str;
	
	if((RecvBuf = new char[BUFFER_SIZE]) == NULL)
		throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
	
	if((SendBuf = new char[BUFFER_SIZE]) == NULL)
		throw ECSmtp(ECSmtp::LACK_OF_MEMORY);
}
 
 
3. Change one line in Send fiunction
 
	// EHLO <SP> <domain> <CRLF>
	sprintf(SendBuf,"EHLO %s\r\n",GetLocalHostName());
	SendData();
	bAccepted = false;
 
 
Let me know if it didn't help.
Jakub Piwowarczyk

GeneralRe: Possible Solution To Misc EHLO Errorsmembermin_2_max17 May '11 - 18:10 
Yes, This seems like fixed the bug.
GeneralRe: Possible Solution To Misc EHLO Errorsmemberhighersky16 Aug '12 - 21:54 
nice solution !! it works ! Laugh | :laugh:
Generaldont work on gmail.commembernill777nill19 Aug '10 - 5:01 
write this
 
mail.SetSMTPServer("smtp.gmail.com",25);
mail.SetLogin("moi_login@gmail.com");
mail.SetPassword("parol");
mail.SetSenderName("moi_login");
mail.SetSenderMail("moi_login@gmail.com");
mail.SetReplyTo("moi_login@gmail.com");
mail.SetSubject("The message");
mail.AddRecipient("moi_login@gmail.com");
mail.SetXPriority(XPRIORITY_NORMAL);
mail.SetXMailer("The Bat! (v3.02) Professional");
mail.AddMsgLine("Hello,");
mail.AddMsgLine("");
mail.AddMsgLine("...");
mail.AddMsgLine("How are you today?");
mail.AddMsgLine("hgh");
mail.AddMsgLine("Regards");
mail.ModMsgLine(5,"regards");
mail.DelMsgLine(2);
mail.AddMsgLine("User");
 
but always I have error
Error: Server returned error after sending AUTH LOGIN
Why this happend?
GeneralRe: dont work on gmail.commemberJakub Piwowarczyk3 Sep '10 - 3:12 
Hi,
See this article: smtp client with SSL/TLS[^]
Jakub Piwowarczyk

GeneralSSL/TLS added to SMTP clientmemberJohn TWC3 Aug '10 - 10:06 
Hi Jakub,
I have added SSL/TLS support to your CSMTP client, please see the article smtp client with SSL/TLS[^]
GeneralSize of body mailmemberjosthubert3 Aug '10 - 4:50 
My body mail is upper 10000 caractere and each time, i have an error.
How I can send a message with more 10000 caracteres
Thank you for our help
Hubert Jost
GeneralRe: Size of body mailmemberJakub Piwowarczyk3 Sep '10 - 3:32 
Hi,
But you don't have one line with 10000 c?
Each line is vector's element:
std::vector<std::string> MsgBody;
Jakub Piwowarczyk

Generalsmall csmtp bugmemberDr_Fuse30 Jul '10 - 0:53 
Great Code Man!
 
This is the first opensource working C++ SMTP class i've come accross.
 
Just wanted to share a small bug I found:
 
Some SMTP servers usually greet the client with more than one line on connection.
 
example,
220-gt2.websitewelcome.com ESMTP Exim 4.69 #1 Fri, 30 Jul 2010 05:41:08 -0500
220-We do not authorize the use of this system to transport unsolicited,
220 and/or bulk e-mail.
 
Since this class reads responses one line at a time, it will read the first line
220-gt2.websitewelcome.com ESMTP Exim 4.69 #1 Fri, 30 Jul 2010 05:41:08 -0500
Send
EHLO
Then read the second line
220-We do not authorize the use of this system to transport unsolicited,
parse the line and since it isn't the expected response ->
250
the ECSmtp
exception will be thrown. While all along the server gave a correct reply.
 
I got around this by reading thrice at the begining (I needed a quick fix)
 
......
	bAccepted = false;
	do
	{
        //REKEBISHO
		ReceiveData();
		ReceiveData();
		ReceiveData();
		switch(SmtpXYZdigits())
		{
			case 220:
				bAccepted = true;
				break;
			default:
				throw ECSmtp(ECSmtp::SERVER_NOT_READY);
......
 
Otherwise your code is a godsend.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 16 Jul 2010
Article Copyright 2008 by Jakub Piwowarczyk
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid