Saturday, August 11, 2012

Send Email in C++ ( SMTP Client in C++ )

Boost! Yes! It's a good library for you to help you develop cross-platform C++ applications; headache free. among all libraries in boost, boost::asio is for implementing asynchronous input output operations and working with network. After having a look at boost asio samples and SMTP RFC : http://www.ietf.org/rfc/rfc2821.txt I implemented this simple class to be used in your C++ programs.
the only tricky part is base64 encoding which is required for sending user/password to SMTP Server.
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
using boost::asio::ip::tcp;
using namespace boost::archive::iterators;
typedef base64_from_binary<transform_width<const char *,6,8> > base64_text;
class SMTPClient
{
public:
 SMTPClient(std::string pServer,unsigned int pPort,std::string pUser,std::string pPassword):
   mServer(pServer),mPort(pPort),mUserName(pUser),mPassword(pPassword),mSocket(mIOService),mResolver(mIOService)
   {
    tcp::resolver::query qry(mServer,boost::lexical_cast<std::string>( mPort ));
    mResolver.async_resolve(qry,boost::bind(&SMTPClient::handleResolve,this,boost::asio::placeholders::error,
     boost::asio::placeholders::iterator));
   }
   bool Send(std::string pFrom,std::string pTo,std::string pSubject,std::string pMessage)
   {
    mFrom=pFrom;
    mTo=pTo;
    mSubject=pSubject;
    mMessage=pMessage;
    mIOService.run();
    return mHasError;
   }
private:
 std::string encodeBase64(std::string pData)
 {
  std::stringstream os;
  size_t sz=pData.size();
  std::copy(base64_text(pData.c_str()),base64_text(pData.c_str()+sz),ostream_iterator<char>(os));
  return os.str();
 }
 void handleResolve(const boost::system::error_code& err,tcp::resolver::iterator endpoint_iterator)
 {
  if(!err)
  {
   tcp::endpoint endpoint=*endpoint_iterator;
   mSocket.async_connect(endpoint,
    boost::bind(&SMTPClient::handleConnect,this,boost::asio::placeholders::error,++endpoint_iterator));
  }
  else
  {
   mHasError=true;
   mErrorMsg= err.message();
  }
 }
 void writeLine(std::string pData)
 {
  std::ostream req_strm(&mRequest);
  req_strm << pData << "\r\n";
  boost::asio::write(mSocket,mRequest);
  req_strm.clear();
 }
 void handleConnect(const boost::system::error_code& err,tcp::resolver::iterator endpoint_iterator)
 {
  if (!err)
  {
   // The connection was successful. Send the request.
   std::ostream req_strm(&mRequest);
   writeLine("EHLO "+mServer);
   writeLine("AUTH LOGIN");
   writeLine(encodeBase64(mUserName));
   writeLine(encodeBase64(mPassword));
   writeLine( "MAIL FROM:<"+mFrom+">");
   writeLine( "RCPT TO:<"+mTo+">");
   writeLine( "DATA");
   writeLine( "SUBJECT:"+mSubject);
   writeLine( "From:"+mFrom);
   writeLine( "To:"+mTo);
   writeLine( "");
   writeLine( mMessage );
   writeLine( ".\r\n");
  }
  else
  {
   mHasError=true;
   mErrorMsg= err.message();
  }
 }
 std::string mServer;
 std::string mUserName;
 std::string mPassword;
 std::string mFrom;
 std::string mTo;
 std::string mSubject;
 std::string mMessage;
 unsigned int mPort;
 boost::asio::io_service mIOService;
 tcp::resolver mResolver;
 tcp::socket mSocket;
 boost::asio::streambuf mRequest;
 boost::asio::streambuf mResponse;
 bool mHasError;
 std::string mErrorMsg;

};
Yest ! that's it. you are free to copy and paste this code into your application:
SMTPClient mailc("yoursmtpserver.com",25,"user@yourdomain.com","password");
mailc.Send("from@yourdomain.com","to@somewhere.com","subject","Hello from C++ SMTP Client!");





4 comments :

  1. Looks good.

    I couldn't get the code to work with gmail or hotmail smtp servers though. Maybe thats because they us SSL? I also tried sending with non ssl smtp server like Lavabit.com. No luck. Have you tried with any of these? In case you did, what smtp settings did you use?

    (I think I'll have a go with curl instead)

    Cheers

    ReplyDelete
  2. This works great, I've adapted it to our software and modified the sent data to support MIME and Attachements.
    The problem I have though, is that I can't seem to be able to run the mIOService twice? I've called a mIOService.reset() yet the second run still does not invoke the handlers.
    Any ideas?

    ReplyDelete
  3. Hi, Thanks.
    you are right. I corrected that class. Now it can sends multiple emails.
    actually I moved sending email outside of handleConnect in new method ( Send ).
    in this way with a single connection/authentication, you can send multiple emails.
    -----------------
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using boost::asio::ip::tcp;
    using namespace boost::archive::iterators;
    typedef base64_from_binary > base64_text;
    class SMTPClient {
    private :

    public:
    enum AUTH_STATUS{AUTH_NONE,AUTHENTICATING,AUTHENTICATED,AUTHENTICATIONFAILED} mauth_status;
    SMTPClient(std::string pServer, unsigned int pPort, std::string pUser, std::string pPassword) :
    mServer(pServer), mPort(pPort), mUserName(pUser), mPassword(pPassword), mSocket(mIOService), mResolver(
    mIOService) {
    mauth_status = AUTH_NONE;

    }
    void Authenticate()
    {
    mauth_status = AUTHENTICATING;
    tcp::resolver::query qry(mServer, boost::lexical_cast(mPort));
    mResolver.async_resolve(qry, boost::bind(&SMTPClient::handleResolve, this, boost::asio::placeholders::error,
    boost::asio::placeholders::iterator));
    mIOService.run();
    }
    void Quit()
    {
    writeLine("QUIT");
    mIOService.stop();
    }
    bool Send(std::string pFrom, std::string pTo, std::string pSubject, std::string pMessage) {
    if(mauth_status != AUTHENTICATED || mHasError)
    {
    std::cout << "not authenticated ('" << mErrorMsg << "'). returning...";
    return false; // has error
    }
    std::cout << "SENDING ....";
    writeLine("MAIL FROM: \"ProWeb Alert\"<" + pFrom+">");
    writeLine("RCPT TO:" + pTo);
    writeLine("DATA");
    writeLine("FROM:<" + pFrom+">");
    writeLine("SUBJECT:" + pSubject);
    writeLine("");
    writeLine(pMessage);
    writeLine("");
    writeLine(".");
    return true;//TODO: smtp server response must be checked.
    }



    private:
    std::string encodeBase64(std::string pData) {
    std::stringstream os;
    size_t sz=pData.size();
    std::copy(base64_text(pData.c_str()),base64_text(pData.c_str()+sz),ostream_iterator(os));
    return os.str();
    }
    void handleResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator) {
    if (!err) {
    std::cout << " resolving done\n";
    tcp::endpoint endpoint = *endpoint_iterator;
    mSocket.async_connect(endpoint, boost::bind(&SMTPClient::handleConnect, this,
    boost::asio::placeholders::error, ++endpoint_iterator));
    } else {
    std::cout << " can't resolve\n";
    mHasError = true;
    mErrorMsg = err.message();
    }
    }
    void writeLine(std::string pData) {
    std::ostream req_strm(&mRequest);
    req_strm << pData << "\r\n";
    //std::cout << pData << "\r\n";
    boost::asio::write(mSocket, mRequest);
    req_strm.clear();
    }
    void handleConnect(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator) {
    if (!err) {
    std::cout << " connected \n";
    // The connection was successful. Send the request.
    mHasError=false;
    std::ostream req_strm(&mRequest);
    writeLine("EHLO " + mServer);
    writeLine("AUTH LOGIN");
    writeLine(encodeBase64(mUserName));
    writeLine(encodeBase64(mPassword));
    mauth_status = AUTHENTICATED;
    } else {
    std::cout << " can not connect\n";
    mauth_status = AUTHENTICATIONFAILED;
    mHasError = true;
    mErrorMsg = err.message();
    }
    }

    std::string mServer;
    std::string mUserName;
    std::string mPassword;
    unsigned int mPort;
    boost::asio::io_service mIOService;
    tcp::resolver mResolver;
    tcp::socket mSocket;
    boost::asio::streambuf mRequest;
    boost::asio::streambuf mResponse;
    bool mHasError;
    std::string mErrorMsg;
    std::ostringstream mResponseData;
    std::ostringstream mResponseHeader;

    };

    ReplyDelete
  4. I am able to successfully send mail with your code but i want to send the attachment file with mail too.Can you please help me .

    Thanks in Advance
    Sunil

    ReplyDelete

Saturday, August 11, 2012

Send Email in C++ ( SMTP Client in C++ )

Boost! Yes! It's a good library for you to help you develop cross-platform C++ applications; headache free. among all libraries in boost, boost::asio is for implementing asynchronous input output operations and working with network. After having a look at boost asio samples and SMTP RFC : http://www.ietf.org/rfc/rfc2821.txt I implemented this simple class to be used in your C++ programs.
the only tricky part is base64 encoding which is required for sending user/password to SMTP Server.
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
using boost::asio::ip::tcp;
using namespace boost::archive::iterators;
typedef base64_from_binary<transform_width<const char *,6,8> > base64_text;
class SMTPClient
{
public:
 SMTPClient(std::string pServer,unsigned int pPort,std::string pUser,std::string pPassword):
   mServer(pServer),mPort(pPort),mUserName(pUser),mPassword(pPassword),mSocket(mIOService),mResolver(mIOService)
   {
    tcp::resolver::query qry(mServer,boost::lexical_cast<std::string>( mPort ));
    mResolver.async_resolve(qry,boost::bind(&SMTPClient::handleResolve,this,boost::asio::placeholders::error,
     boost::asio::placeholders::iterator));
   }
   bool Send(std::string pFrom,std::string pTo,std::string pSubject,std::string pMessage)
   {
    mFrom=pFrom;
    mTo=pTo;
    mSubject=pSubject;
    mMessage=pMessage;
    mIOService.run();
    return mHasError;
   }
private:
 std::string encodeBase64(std::string pData)
 {
  std::stringstream os;
  size_t sz=pData.size();
  std::copy(base64_text(pData.c_str()),base64_text(pData.c_str()+sz),ostream_iterator<char>(os));
  return os.str();
 }
 void handleResolve(const boost::system::error_code& err,tcp::resolver::iterator endpoint_iterator)
 {
  if(!err)
  {
   tcp::endpoint endpoint=*endpoint_iterator;
   mSocket.async_connect(endpoint,
    boost::bind(&SMTPClient::handleConnect,this,boost::asio::placeholders::error,++endpoint_iterator));
  }
  else
  {
   mHasError=true;
   mErrorMsg= err.message();
  }
 }
 void writeLine(std::string pData)
 {
  std::ostream req_strm(&mRequest);
  req_strm << pData << "\r\n";
  boost::asio::write(mSocket,mRequest);
  req_strm.clear();
 }
 void handleConnect(const boost::system::error_code& err,tcp::resolver::iterator endpoint_iterator)
 {
  if (!err)
  {
   // The connection was successful. Send the request.
   std::ostream req_strm(&mRequest);
   writeLine("EHLO "+mServer);
   writeLine("AUTH LOGIN");
   writeLine(encodeBase64(mUserName));
   writeLine(encodeBase64(mPassword));
   writeLine( "MAIL FROM:<"+mFrom+">");
   writeLine( "RCPT TO:<"+mTo+">");
   writeLine( "DATA");
   writeLine( "SUBJECT:"+mSubject);
   writeLine( "From:"+mFrom);
   writeLine( "To:"+mTo);
   writeLine( "");
   writeLine( mMessage );
   writeLine( ".\r\n");
  }
  else
  {
   mHasError=true;
   mErrorMsg= err.message();
  }
 }
 std::string mServer;
 std::string mUserName;
 std::string mPassword;
 std::string mFrom;
 std::string mTo;
 std::string mSubject;
 std::string mMessage;
 unsigned int mPort;
 boost::asio::io_service mIOService;
 tcp::resolver mResolver;
 tcp::socket mSocket;
 boost::asio::streambuf mRequest;
 boost::asio::streambuf mResponse;
 bool mHasError;
 std::string mErrorMsg;

};
Yest ! that's it. you are free to copy and paste this code into your application:
SMTPClient mailc("yoursmtpserver.com",25,"user@yourdomain.com","password");
mailc.Send("from@yourdomain.com","to@somewhere.com","subject","Hello from C++ SMTP Client!");





4 comments :

  1. Looks good.

    I couldn't get the code to work with gmail or hotmail smtp servers though. Maybe thats because they us SSL? I also tried sending with non ssl smtp server like Lavabit.com. No luck. Have you tried with any of these? In case you did, what smtp settings did you use?

    (I think I'll have a go with curl instead)

    Cheers

    ReplyDelete
  2. This works great, I've adapted it to our software and modified the sent data to support MIME and Attachements.
    The problem I have though, is that I can't seem to be able to run the mIOService twice? I've called a mIOService.reset() yet the second run still does not invoke the handlers.
    Any ideas?

    ReplyDelete
  3. Hi, Thanks.
    you are right. I corrected that class. Now it can sends multiple emails.
    actually I moved sending email outside of handleConnect in new method ( Send ).
    in this way with a single connection/authentication, you can send multiple emails.
    -----------------
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using boost::asio::ip::tcp;
    using namespace boost::archive::iterators;
    typedef base64_from_binary > base64_text;
    class SMTPClient {
    private :

    public:
    enum AUTH_STATUS{AUTH_NONE,AUTHENTICATING,AUTHENTICATED,AUTHENTICATIONFAILED} mauth_status;
    SMTPClient(std::string pServer, unsigned int pPort, std::string pUser, std::string pPassword) :
    mServer(pServer), mPort(pPort), mUserName(pUser), mPassword(pPassword), mSocket(mIOService), mResolver(
    mIOService) {
    mauth_status = AUTH_NONE;

    }
    void Authenticate()
    {
    mauth_status = AUTHENTICATING;
    tcp::resolver::query qry(mServer, boost::lexical_cast(mPort));
    mResolver.async_resolve(qry, boost::bind(&SMTPClient::handleResolve, this, boost::asio::placeholders::error,
    boost::asio::placeholders::iterator));
    mIOService.run();
    }
    void Quit()
    {
    writeLine("QUIT");
    mIOService.stop();
    }
    bool Send(std::string pFrom, std::string pTo, std::string pSubject, std::string pMessage) {
    if(mauth_status != AUTHENTICATED || mHasError)
    {
    std::cout << "not authenticated ('" << mErrorMsg << "'). returning...";
    return false; // has error
    }
    std::cout << "SENDING ....";
    writeLine("MAIL FROM: \"ProWeb Alert\"<" + pFrom+">");
    writeLine("RCPT TO:" + pTo);
    writeLine("DATA");
    writeLine("FROM:<" + pFrom+">");
    writeLine("SUBJECT:" + pSubject);
    writeLine("");
    writeLine(pMessage);
    writeLine("");
    writeLine(".");
    return true;//TODO: smtp server response must be checked.
    }



    private:
    std::string encodeBase64(std::string pData) {
    std::stringstream os;
    size_t sz=pData.size();
    std::copy(base64_text(pData.c_str()),base64_text(pData.c_str()+sz),ostream_iterator(os));
    return os.str();
    }
    void handleResolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator) {
    if (!err) {
    std::cout << " resolving done\n";
    tcp::endpoint endpoint = *endpoint_iterator;
    mSocket.async_connect(endpoint, boost::bind(&SMTPClient::handleConnect, this,
    boost::asio::placeholders::error, ++endpoint_iterator));
    } else {
    std::cout << " can't resolve\n";
    mHasError = true;
    mErrorMsg = err.message();
    }
    }
    void writeLine(std::string pData) {
    std::ostream req_strm(&mRequest);
    req_strm << pData << "\r\n";
    //std::cout << pData << "\r\n";
    boost::asio::write(mSocket, mRequest);
    req_strm.clear();
    }
    void handleConnect(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator) {
    if (!err) {
    std::cout << " connected \n";
    // The connection was successful. Send the request.
    mHasError=false;
    std::ostream req_strm(&mRequest);
    writeLine("EHLO " + mServer);
    writeLine("AUTH LOGIN");
    writeLine(encodeBase64(mUserName));
    writeLine(encodeBase64(mPassword));
    mauth_status = AUTHENTICATED;
    } else {
    std::cout << " can not connect\n";
    mauth_status = AUTHENTICATIONFAILED;
    mHasError = true;
    mErrorMsg = err.message();
    }
    }

    std::string mServer;
    std::string mUserName;
    std::string mPassword;
    unsigned int mPort;
    boost::asio::io_service mIOService;
    tcp::resolver mResolver;
    tcp::socket mSocket;
    boost::asio::streambuf mRequest;
    boost::asio::streambuf mResponse;
    bool mHasError;
    std::string mErrorMsg;
    std::ostringstream mResponseData;
    std::ostringstream mResponseHeader;

    };

    ReplyDelete
  4. I am able to successfully send mail with your code but i want to send the attachment file with mail too.Can you please help me .

    Thanks in Advance
    Sunil

    ReplyDelete