SMTP Client

Smtp::Client provides an SMTP email client for sending email messages. It supports authentication, STARTTLS encryption, and both SMTP and SMTPS protocols.

Smtp::Client is designed to be:

  • easy — simple API for sending emails
  • secure — supports TLS/SSL encryption
  • flexible — multiple authentication methods
  • complete — handles SMTP protocol automatically

Basic usage

Sending an email

// libjoin.
#include <join/smtpclient.hpp>

// C++.
#include <iostream>

using namespace join;

// Create message
MailMessage mail;
mail.sender(MailSender("sender@example.com", "Sender"));
mail.addRecipient(MailRecipient("recipient@example.com"));
mail.subject("Test Email");
mail.content("Hello, World!");

// Create client
Smtp::Client client("smtp.example.com", 587);

// Set credentials
client.credentials("username", "password");

// Send email
if (client.send(mail) == 0)
{
    std::cout << "Email sent successfully\n";
}
else
{
    std::cerr << "Failed to send email\n";
}

Construction

SMTP (unencrypted/STARTTLS)

// Port 25 (default)
Smtp::Client relay("smtp.example.com");

// Port 587 (submission with STARTTLS)
Smtp::Client submission("smtp.example.com", 587);

SMTPS (encrypted)

// Port 465 (default for SMTPS)
Smtps::Client client("smtp.example.com");

// Custom port
Smtps::Client custom("smtp.example.com", 2465);

Authentication

Setting credentials

Smtp::Client client("smtp.example.com", 587);

// Set username and password
client.credentials("myusername", "mypassword");

// Send email
client.send(mail);

Supported methods

The client automatically negotiates authentication:

  • LOGIN — Username/password sent separately
  • PLAIN — Username/password sent together
// Client automatically chooses based on server capabilities
client.credentials("user@example.com", "password");
client.send(mail);

Encryption

STARTTLS

// Connects unencrypted, upgrades to TLS
Smtp::Client client("smtp.example.com", 587);
client.credentials("user", "password");

// Client automatically uses STARTTLS if available
client.send(mail);

SMTPS (implicit TLS)

// Encrypted from the start
Smtps::Client client("smtp.example.com", 465);
client.credentials("user", "password");
client.send(mail);

TLS configuration

Certificate verification

Smtp::Client client("smtp.example.com", 587);

// Set CA certificate for verification
client.setCaFile("/etc/ssl/certs/ca-bundle.crt");

// Enable verification
client.setVerify(true);

// Or set CA path
client.setCaPath("/etc/ssl/certs/");

Client certificate

Smtp::Client client("smtp.example.com", 587);

// Set client certificate and key
client.setCertificate("/path/to/cert.pem", "/path/to/key.pem");

Cipher configuration

Smtp::Client client("smtp.example.com", 587);

// TLS 1.2 and below
client.setCipher("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256");

// TLS 1.3
client.setCipher_1_3("TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256");

Common email services

Gmail

Smtp::Client client("smtp.gmail.com", 587);
client.credentials("your-email@gmail.com", "app-password");
client.send(mail);

// Note: Use App Password, not regular password

Outlook/Office 365

Smtp::Client client("smtp.office365.com", 587);
client.credentials("your-email@outlook.com", "password");
client.send(mail);

Yahoo

Smtp::Client client("smtp.mail.yahoo.com", 587);
client.credentials("your-email@yahoo.com", "app-password");
client.send(mail);

Custom server

Smtp::Client client("mail.company.com", 25);
// No authentication needed for internal server
client.send(mail);

Complete examples

Simple notification

void sendNotification(const std::string& to, const std::string& message)
{
    MailMessage mail;
    mail.sender(MailSender("noreply@app.com", "App Notifications"));
    mail.addRecipient(MailRecipient(to));
    mail.subject("Notification");
    mail.content(message);

    Smtp::Client client("smtp.example.com", 587);
    client.credentials("app-email@example.com", "password");

    if (client.send(mail) != 0)
    {
        std::cerr << "Failed to send notification\n";
    }
}

Welcome email

struct User
{
    std::string name;
    std::string email;
};

void sendWelcomeEmail(const User& user)
{
    MailMessage mail;

    mail.sender(MailSender("welcome@company.com", "Company Team"));
    mail.addRecipient(MailRecipient(user.email, user.name));
    mail.subject("Welcome to our service!");

    std::string content =
        "Dear " + user.name + ",\n"
        "\n"
        "Welcome to our service!\n"
        "\n"
        "To get started, please verify your email address.\n"
        "\n"
        "Best regards,\n"
        "The Team";

    mail.content(content);

    Smtp::Client client("smtp.company.com", 587);
    client.credentials("noreply@company.com", "password");

    if (client.send(mail) != 0)
    {
        std::cerr << "Failed to send welcome email\n";
    }
}

Email with attachments (workaround)

// Current version doesn't support attachments directly
// Workaround: Include links or embed small data

void sendReportLink(const std::string& to, const std::string& reportUrl)
{
    MailMessage mail;
    mail.sender(MailSender("reports@company.com", "Reports"));
    mail.addRecipient(MailRecipient(to));
    mail.subject("Your report is ready");

    mail.content(
        "Your report is ready for download:\n"
        "\n" +
        reportUrl + "\n"
        "\n"
        "This link will expire in 24 hours.\n"
    );

    Smtp::Client client("smtp.company.com", 587);
    client.credentials("reports@company.com", "password");

    if (client.send(mail) != 0)
    {
        std::cerr << "Failed to send report link\n";
    }
}

Error handling

Checking send result

Smtp::Client client("smtp.example.com", 587);
client.credentials("user", "password");

if (client.send(mail) == 0)
{
    std::cout << "Email sent successfully\n";
}
else
{
    std::error_code ec = join::lastError;
    std::cerr << "Send failed: " << ec.message() << "\n";
}

Common errors

send() returns -1 when any step of the SMTP conversation fails, for example:

  • the host name could not be resolved, or the connection or the TLS handshake failed
  • the authentication was refused
  • a recipient was rejected: the message is then sent to nobody, not even to the valid recipients
  • the server refused the message

send() opens a new connection for each message and closes it afterwards, so the same client can be reused after a failure.

⚠️ A failure while closing the conversation (QUIT) also returns -1, although the message was already accepted: blindly retrying can deliver it twice.


Best practices

Use secure connections

// Good: Use TLS (port 587 with STARTTLS)
Smtp::Client submission("smtp.example.com", 587);

// Good: Use SMTPS (port 465)
Smtps::Client secure("smtp.example.com", 465);

// Avoid: Unencrypted (port 25)
Smtp::Client relay("smtp.example.com", 25);

Validate before sending

bool validateMail(const MailMessage& mail)
{
    if (mail.sender().empty())
    {
        return false;
    }
    if (mail.recipients().empty())
    {
        return false;
    }
    if (mail.subject().empty())
    {
        return false;
    }
    return true;
}

if (validateMail(mail))
{
    client.send(mail);
}

Handle credentials securely

// C.
#include <cstdlib>

// Good: Read the credentials from the environment
const char* user = std::getenv("SMTP_USER");
const char* pass = std::getenv("SMTP_PASSWORD");
if (user == nullptr || pass == nullptr)
{
    std::cerr << "SMTP_USER and SMTP_PASSWORD must be set\n";
    return;
}
client.credentials(user, pass);

// Bad: Hardcoded credentials
client.credentials("hardcoded@example.com", "password123");

Retry on failure

Retrying only helps with temporary failures (network, busy server): a rejected recipient or a wrong password fails again, and repeated failed logins can lock the account.

// C++.
#include <thread>
#include <chrono>

bool sendWithRetry(Smtp::Client& client, const MailMessage& mail, int maxRetries = 3)
{
    for (int i = 0; i < maxRetries; ++i)
    {
        if (client.send(mail) == 0)
        {
            return true;
        }

        // Wait before the next attempt
        if (i + 1 < maxRetries)
        {
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    }
    return false;
}

Protocol details

SMTP conversation

S: 220 smtp.example.com ESMTP
C: EHLO hostname
S: 250-smtp.example.com
S: 250-STARTTLS
S: 250 AUTH LOGIN PLAIN
C: STARTTLS
S: 220 Ready to start TLS
[TLS handshake]
C: EHLO hostname
S: 250-smtp.example.com
S: 250 AUTH LOGIN PLAIN
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: dXNlcm5hbWU=
S: 334 UGFzc3dvcmQ6
C: cGFzc3dvcmQ=
S: 235 Authentication successful
C: MAIL FROM: <sender@example.com>
S: 250 OK
C: RCPT TO: <recipient@example.com>
S: 250 OK
C: DATA
S: 354 Start mail input
C: [email headers and body]
C: .
S: 250 OK
C: QUIT
S: 221 Bye

The client handles this conversation automatically.


Ports

PortProtocolEncryptionUse case
25SMTPNone/STARTTLSServer-to-server
587SMTPSTARTTLSClient submission
465SMTPSImplicit TLSLegacy secure client

Summary

FeatureSupported
SMTP protocol✅
SMTPS (SSL/TLS)✅
STARTTLS✅
LOGIN authentication✅
PLAIN authentication✅
Multiple recipients✅
Certificate verification✅
Client certificates✅
Plain text email✅
HTML email❌
Attachments❌