Simple TLS/SSL SMTP client for Qt5

For one of my projects I wanted to develop a crash reporting dialog where user can enter the steps he did to crash the application and then by pressing send button the app automatically will send me user report and the log to my e-mail address. Unfortunatelly Qt doesn't have a Smtp library, and I don't want anything heavy just to send a simple string of characters through gmail smtp server.


Update

Both implementations are now available on github

 

 

There are some examples and codes on github like:
https://github.com/bluetiger9/SmtpClient-for-Qt

The above one actually looks like very decent peace of code, however again, it is a little bit too heavy for my taste and it didn't compile on Qt5 immidiatly (even the example) and I didn't want to go through to try and fix it.

So I found an example code for SMTP from Qt3. Which was almost exactly what I was looking for, it was simple for sure. However, it lacked two major features, it didn't support TLS/SSL communication and authentification I need it to use gmail as my smtp server. Then I thought how hard would it be to improve that code?

 

SMTP protocol for sending email

First of all every communication with the server will result a response from server. It is important to extract first 3 characters from the message which are response codes. Those codes obviously will be used for error checking whether communication is going smoothly.

Basic algorithm for sending email through smtp:

  1. Connecting to the server and waiting for greeting message
  2. If response code = 220 then send a greeting back to server "EHLO yourdomain.com\r\n"
  3. If response code = 250 then identify from whom the mail is coming from by sending "MAIL FROM:<youremail@mail.com>\r\n"
  4. If response code = 250 then send whom the email is addressed to "RCPT TO:<send_to@mail.com>\r\n"
  5. If response code = 250 then now it is time to inititate sending the contents of the email by sending "DATA\r\n"
  6. If response code = 354 then send your contents for example "This is an email\r\n"
  7. If response code = 250 then your email has been sent and now you can close the connection by sending "QUIT\r\n"

 

Basically the above algorithm was used in the example code I found from Qt3. As you can see the above algorithm lacks authentification and SSL/TLS handshake. To do add those things to the code it turned out to be actually simpler than I first anticipated. 

Basic algorithm for TLS/SSL smtp email sending:

  1. Connecting to the server and waiting for greeting message
  2. If response code = 220 then send a greeting back to server "EHLO yourdomain.com\r\n"
  3. If response code = 250 then initiate TLS communication by making a SSL/TLS handshake, for this I use the methods provided in QSslSocket class, more precisely QSslSocket::startClientEncryption(). (make communictaion encrypted)
  4. If response code = 250 then you have successfully initiated a encrypted communication and now we can start authentification by sending "AUTH LOGIN\r\n"
  5. If response code = 334 then the server is waiting for username, since my aim was to use GMAIL as smtp server their API states that the username and password must be sent in base64 coding, it would look something like this "bXlfdXNlcm5hbWU=\r\n"  (my_username)
  6. If response code = 334 then send the server password, and again in base64 coding "bXlfcGFzc3dvcmQ=\r\n" (my_password)
  7. If response code = 235 then you have successfully logged in and now you can identify from whom the mail is coming from by sending "MAIL FROM:<youremail@mail.com>\r\n"
  8. If response code = 250 then send whom the email is addressed to "RCPT TO:<send_to@mail.com>\r\n"
  9. If response code = 250 then now it is time to inititate sending the contents of the email by sending "DATA\r\n"
  10. If response code = 354 then send your contents for example "This is an email\r\n"
  11. If response code = 250 then your email has been sent and now you can close the connection by sending "QUIT\r\n"

 

That's it, by following the above pseudo code, you should be able to write a simple TLS/SSL SMTP client for sending email using gmail as smtp server. You can use the above approach to send email through telnet aswell.

Important: Every command to server has to be finished with carriege return and new line symbol in other words you have to finish the line with "\r\n"

 

So by following the steps I wrote above, I slightly rewrote the example code which you can see below. However I would advise just trying to write the code yourself, belive it's not that hard, if you follow the steps.


First of all, for the code to run you will need Qt with ssl support compiled within and OpenSSL library . If you have ssl compiled in the Qt library you don't need to add anything to the pro file, except QT+= network.

 

Smtp.h

/*
Copyright (c) 2013 Raivis Strogonovs
 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/
 
#ifndef SMTP_H
#define SMTP_H
 
 
#include <QtNetwork/QAbstractSocket>
#include <QtNetwork/QSslSocket>
#include < QString >
#include < QTextStream >
#include < QDebug >
#include <QtWidgets/QMessageBox>
#include < QByteArray >
 
 
 
class Smtp : public QObject
{
    Q_OBJECT
 
 
public:
    Smtp( const QString &user, const QString &pass,
          const QString &host, int port = 465, int timeout = 30000 );
    ~Smtp();
 
    void sendMail( const QString &from, const QString &to,
                   const QString &subject, const QString &body );
 
signals:
    void status( const QString &);
 
private slots:
    void stateChanged(QAbstractSocket::SocketState socketState);
    void errorReceived(QAbstractSocket::SocketError socketError);
    void disconnected();
    void connected();
    void readyRead();
 
private:
    int timeout;
    QString message;
    QTextStream *t;
    QSslSocket *socket;
    QString from;
    QString rcpt;
    QString response;
    QString user;
    QString pass;
    QString host;
    int port;
    enum states{Tls, HandShake ,Auth,User,Pass,Rcpt,Mail,Data,Init,Body,Quit,Close};
    int state;
 
};
#endif

 

Smtp.cpp

/*
Copyright (c) 2013 Raivis Strogonovs
 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/
 
 
 
#include "smtp.h"
 
Smtp::Smtp( const QString &user, const QString &pass, const QString &host, int port, int timeout )
{    
    socket = new QSslSocket(this);
 
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(socket, SIGNAL(connected()), this, SLOT(connected() ) );
    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,SLOT(errorReceived(QAbstractSocket::SocketError)));   
    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(stateChanged(QAbstractSocket::SocketState)));
    connect(socket, SIGNAL(disconnected()), this,SLOT(disconnected()));
 
 
    this->user = user;
    this->pass = pass;
 
    this->host = host;
    this->port = port;
    this->timeout = timeout;
 
 
}
 
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
{
    message = "To: " + to + "\n";
    message.append("From: " + from + "\n");
    message.append("Subject: " + subject + "\n");
    message.append(body);
    message.replace( QString::fromLatin1( "\n" ), QString::fromLatin1( "\r\n" ) );
    message.replace( QString::fromLatin1( "\r\n.\r\n" ),
    QString::fromLatin1( "\r\n..\r\n" ) );
    this->from = from;
    rcpt = to;
    state = Init;
    socket->connectToHostEncrypted(host, port); //"smtp.gmail.com" and 465 for gmail TLS
    if (!socket->waitForConnected(timeout)) {
         qDebug() << socket->errorString();
     }
 
    t = new QTextStream( socket );
 
 
 
}
 
Smtp::~Smtp()
{
    delete t;
    delete socket;
}
void Smtp::stateChanged(QAbstractSocket::SocketState socketState)
{
 
    qDebug() <<"stateChanged " << socketState;
}
 
void Smtp::errorReceived(QAbstractSocket::SocketError socketError)
{
    qDebug() << "error " <<socketError;
}
 
void Smtp::disconnected()
{
 
    qDebug() <<"disconneted";
    qDebug() << "error "  << socket->errorString();
}
 
void Smtp::connected()
{    
    qDebug() << "Connected ";
}
 
void Smtp::readyRead()
{
 
     qDebug() <<"readyRead";
    // SMTP is line-oriented
 
    QString responseLine;
    do
    {
        responseLine = socket->readLine();
        response += responseLine;
    }
    while ( socket->canReadLine() && responseLine[3] != ' ' );
 
    responseLine.truncate( 3 );
 
    qDebug() << "Server response code:" <<  responseLine;
    qDebug() << "Server response: " << response;
 
    if ( state == Init && responseLine == "220" )
    {
        // banner was okay, let's go on
        *t << "EHLO localhost" <<"\r\n";
        t->flush();
 
        state = HandShake;
    }
    //No need, because I'm using socket->startClienEncryption() which makes the SSL handshake for you
    /*else if (state == Tls && responseLine == "250")
    {
        // Trying AUTH
        qDebug() << "STarting Tls";
        *t << "STARTTLS" << "\r\n";
        t->flush();
        state = HandShake;
    }*/
    else if (state == HandShake && responseLine == "250")
    {
        socket->startClientEncryption();
        if(!socket->waitForEncrypted(timeout))
        {
            qDebug() << socket->errorString();
            state = Close;
        }
 
 
        //Send EHLO once again but now encrypted
 
        *t << "EHLO localhost" << "\r\n";
        t->flush();
        state = Auth;
    }
    else if (state == Auth && responseLine == "250")
    {
        // Trying AUTH
        qDebug() << "Auth";
        *t << "AUTH LOGIN" << "\r\n";
        t->flush();
        state = User;
    }
    else if (state == User && responseLine == "334")
    {
        //Trying User        
        qDebug() << "Username";
        //GMAIL is using XOAUTH2 protocol, which basically means that password and username has to be sent in base64 coding
        //https://developers.google.com/gmail/xoauth2_protocol
        *t << QByteArray().append(user).toBase64()  << "\r\n";
        t->flush();
 
        state = Pass;
    }
    else if (state == Pass && responseLine == "334")
    {
        //Trying pass
        qDebug() << "Pass";
        *t << QByteArray().append(pass).toBase64() << "\r\n";
        t->flush();
 
        state = Mail;
    }
    else if ( state == Mail && responseLine == "235" )
    {
        // HELO response was okay (well, it has to be)
 
        //Apperantly for Google it is mandatory to have MAIL FROM and RCPT email formated the following way -> <email@gmail.com>
        qDebug() << "MAIL FROM:<" << from << ">";
        *t << "MAIL FROM:<" << from << ">\r\n";
        t->flush();
        state = Rcpt;
    }
    else if ( state == Rcpt && responseLine == "250" )
    {
        //Apperantly for Google it is mandatory to have MAIL FROM and RCPT email formated the following way -> <email@gmail.com>
        *t << "RCPT TO:<" << rcpt << ">\r\n"; //r
        t->flush();
        state = Data;
    }
    else if ( state == Data && responseLine == "250" )
    {
 
        *t << "DATA\r\n";
        t->flush();
        state = Body;
    }
    else if ( state == Body && responseLine == "354" )
    {
 
        *t << message << "\r\n.\r\n";
        t->flush();
        state = Quit;
    }
    else if ( state == Quit && responseLine == "250" )
    {
 
        *t << "QUIT\r\n";
        t->flush();
        // here, we just close.
        state = Close;
        emit status( tr( "Message sent" ) );
    }
    else if ( state == Close )
    {
        deleteLater();
        return;
    }
    else
    {
        // something broke.
        QMessageBox::warning( 0, tr( "Qt Simple SMTP client" ), tr( "Unexpected reply from SMTP server:\n\n" ) + response );
        state = Close;
        emit status( tr( "Failed to send message" ) );
    }
    response = "";
}

 

 

Example usage:

smtp = new Smtp("your_username@gmail.com", "your_password", "smtp.gmail.com");
connect(smtp, SIGNAL(status(QString)), this, SLOT(mailSent(QString))); 

smtp->sendMail("sent_from@gmail.com", "send_it_to@gmail.com" , "This is a subject","This is a body");

 

That's all you have to do, to send a simple text based email through Qt5 and most likely Qt4 as well. ENJOY!

 

The result:

 

 

Download: smtp.zip (32.82K)

To run the exectutable you have to install Qt5, Microsoft Visual C++ redistributables and OpenSSL on your computer.
To compile you just need OpenSSL and Qt5!

If you want file attachemnt support as well refer to my other article, which improves this piece of code for attachment support.

 

References:

Google. (2013). Gmail APIs. Available: https://developers.google.com/gmail/xoauth2_protocol. Last accessed 9th Jul 2013.

Network Sorcery Inc.. (Not Available). SMTP, Simple Mail Transfer Protocol. Available: http://www.networksorcery.com/enp/protocol/smtp.htm. Last accessed 9th Jul 2013.

Microsoft. (2003). What is TLS/SSL?. Available: http://technet.microsoft.com/en-us/library/cc784450%28v=ws.10%29.aspx. Last accessed 9th Jul 2013.



Related Articles



ADVERTISEMENT

  • Sritoma

    Sritoma in Implementing pulse oximeter using MAX30100

    Hi Raivis, I saw your post regarding max30100 but the same code when i am running in my arduino ide with max30100 sensor, everytime i am getting the following error while compiling is: 'pulseoxymeter_t' was not declared in this scope. Also i...

    Sritoma

    Sritoma in Implementing pulse oximeter using MAX30100

    Hi Raivis, I saw your post regarding max30100 but the same code when i am running in my arduino ide with max30100 sensor, everytime i am getting the following error while compiling is: 'pulseoxymeter_t' was not declared in this scope. Also i...

    Sritoma

    Sritoma in Implementing pulse oximeter using MAX30100

    Hi Raivis, I saw your post regarding max30100 but the same code when i am running in my arduino ide with max30100 sensor, everytime i am getting the following error while compiling is: 'pulseoxymeter_t' was not declared in this scope. Also i...

    Bogdan

    Bogdan in Implementing pulse oximeter using MAX30100

    Hello and big thumbs up for the explanations and also for FlexiPlot! I am trying to run your sketch with a MAX30102 board, but unfortunately the RED led doesn't not light up. Do you have any idea why this IC does not...

    Bogdan

    Bogdan in Implementing pulse oximeter using MAX30100

    Hello and big thumbs up for the explanations and also for FlexiPlot! I am trying to run your sketch with a MAX30102 board, but unfortunately the RED led doesn't not light up. Do you have any idea why this IC does not...

    Bogdan

    Bogdan in Implementing pulse oximeter using MAX30100

    Hello and big thumbs up for the explanations and also for FlexiPlot! I am trying to run your sketch with a MAX30102 board, but unfortunately the RED led doesn't not light up. Do you have any idea why this IC does not...

    Learning2code

    Learning2code in Adding file attachments to SMTP client for Qt5

    Hi, yes now it's working... So the mainwindows.cpp now looks like:  void MainWindow::FreeSmtp(Smtp* smtp){ delete...