1. Introduction

Sending mails is a pretty typical action for web applications, and the Crails Framework comes with a library to help you quickly set up mail sending services.

1.1 Installation

Let's first install the mail plugin in your application using the following command:

$ crails plugins mail install

This command will add a few files in your config folder, where you'll be able to define the settings of the mail sending servers you'll be using. These could typically be SMTP servers, or email delivery services such as Mailgun or Sendgrid.

1.2 Configuration

1.2.1 SMTP server

The most typical way to send emails is through a SMTP server. Let's see how to add an SMTP server to our mail servers:
config/mailers.cpp
#include "mailers.hpp"

ApplicationMailServers::ApplicationMailServers()
{
  servers = {
    {
      "default", MailServer()
        .backend(MailServers::Smtp)
        .hostname("smtp.gandi.net")
        .username("no-reply@domain.com")
        .password("the-password")
    }
  };
}

1.2.2 Mailgun

The mail library also comes with support for Mailgun, a sending service provider. While some of these service provide an SMTP interface, they usually also come with an HTTP interface. That's what the mail library uses for Mailgun. Let's see how to connect our mailing library to Mailgun's HTTP interface, by adding a second server to our configuration:

config/mailers.cpp
#include "mailers.hpp"

ApplicationMailServers::ApplicationMailServers()
{
  servers = {
    {
      "default", MailServer()
        .backend(MailServers::Smtp)
        .hostname("smtp.gandi.net")
        .username("no-reply@domain.com")
        .password("the-password")
    },
    {
      "mailgun", MailServer()
        .backend(MailServers::Mailgun)
        .hostname("mg.domain.com")
        .password("your-mailgun-api-key")
    }
  };
}

2. Simple mail sending

2.1 Mail generation

2.1.1 Plain text mails

Let's tinker a bit with the Crails::Mail object first. We'll first create a function that returns a mail object. The mail will be plain text, with a subject, a body, a sender and one recipient:

#include <crails/mail.hpp>

Crails::Mail make_mail()
{
  Crails::Mail mail;
  std::string_view content("Greetings dear sir or madam,

Thank you and goobye.");

  mail.set_subject("Words of greetings");
  mail.set_body(content.data(), content.length());
  mail.set_sender("no-reply@domain.com");
  mail.add_recipient("roger@gmail.com", "Roger Ackroyd");
  return mail;
}

2.1.2 Rich text mails

Plain text mails are a good start, as many mail client reject or limit the use of rich content to their users. Still, using HTML in your mails can make them much more attractive to users, and you may want to provide both plain text and html content for your mails.

To achieve that result, you'll have to tell the Mail object which type of content you're about to provide by setting a content type before calling set_body:

#include <crails/mail.hpp>

Crails::Mail make_mail()
{
  Crails::Mail mail;
  std::string_view text_content("Greetings dear sir or madam,

Thank you and goobye.");
  std::string_view html_content("<b>Bold greetings</b> dear sir <i>or</i> madam,<br/><br/>Thank you and goodbye.");

  mail.set_content_type("text/html");
  mail.set_body(html_content.data(), html_content.length());
  mail.set_content_type("text/plain");
  mail.set_body(text_content.data(), text_content.length());
  // ...
}

Note that you also have the option not to provide a plain text fallback for your mail. In which case, you will still have to call set_content_type("text/html") on your email before setting its content, as text/plain is the default content type.

2.1.3 Recipient options

An email can be sent to multiple mailboxes at once, by calling add_recipient multiple times:

#include <crails/mail.hpp>

Crails::Mail make_mail()
{
  Crails::Mail mail;

  // ...
  mail.add_recipient("roger@gmail.com", "Roger Ackroyd");
  mail.add_recipient("alain@gmail.com"); // Useful tip: the name parameter for recipients is optional
  mail.add_recipient("piaget@gmail.com", "Jean Piaget");
  return mail;
}

You may also want to differenciate the main recipients, from recipients who are merely receiving a copy of the email. This is called Carbon Copy, and can be set as following:

#include <crails/mail.hpp>

Crails::Mail make_mail()
{
  Crails::Mail mail;

  // ...
  mail.add_recipient("roger@gmail.com", "Roger Ackroyd");
  mail.add_recipient("alain@gmail.com", "", Crails::Mail::CarbonCopy);
  mail.add_recipient("piaget@gmail.com", "Jean Piaget", Crails::Mail::CarbonCopy);
  return mail;
}

Moreover, you may also want some recipients to be hidden from other recipients. This is called Blind Carbon Copy, and is supported with the Crails::Mail::Blind option:

#include <crails/mail.hpp>

Crails::Mail make_mail()
{
  Crails::Mail mail;

  // ...
  mail.add_recipient("roger@gmail.com", "Roger Ackroyd");
  mail.add_recipient("alain@gmail.com", "", Crails::Mail::CarbonCopy);
  mail.add_recipient("piaget@gmail.com", "Jean Piaget", Crails::Mail::Blind);
  return mail;
}

2.2 Sending an email

Now that we know both how to configure our mail servers and how to define our emails, let's see how to send mail through our servers.

#include <crails/mail.hpp>
#include <crails/mail_servers.hpp>

Crails::Mail make_mail()
{
  // ...
}

void send_mail()
{
  Crails::MailServers& mail_servers = Crails::MailServers::singleton::require();
  std::shared_ptr<Crails::MailServer> server;

  server = mail_servers.create("default");
  server->connect([server, mail]()
  {
    server->send(make_mail(), [server]()
    {
      // Done
    });
  });
}

In this example, we first asked for the instance of the Crails::MailServers singleton, which we then asked to create an instance of Crails::MailServer using the default configuration (defined in config/mailers.cpp.

We then use the connect method to start a connection with the remote server. Note that the server shared pointer is packed in connect's callback, as we must ensure that the Crails::MailServer instance remains in memory until the end of the process.

Once we've been connected to the remote server, we can start sending mails using the send method, which takes a Crails::Mail object and a callback as parameter. Again, make sure to pack the server's shared pointer in the callback.

When sending multiple mails, you do not have to wait for each individual mail to have been sent before calling the send method again.

3. Mailers

The Crails::Mailer object is designed to make it easier to integrate mails in a Crails application, by factoring in controllers and renderers. Let's see how to send a simple mail with a Mailer:

#include <crails/mailer.hpp>

Crails::Mail make_mail()
{
  // ...
}

void mailer_powered_mailing()
{
  auto mailer = std::make_shared<Crails::Mailer>("default");

  mailer->get_mail() = make_mail();
  mailer->send();
}
Note that we instantiated our mailer using std::shared_ptr: this is absolutely mandatory, as mails are sent asynchronously, and the Mailer's instance uses std::enable_shared_from_this to ensure it remains in memory at least until the sending process is over.

This is the most basic thing you may do with a mailer... but it doesn't help us do anymore than we've done before. Let's see what options the Mailer brings to us:

2.1 Rendering to mails

Instead of inlining your mail content in your C++ code, you may want to take advantage of the templating and rendering system of the crails framework.

The Mailer class provides a similar API as the RenderController class. We'll make an html template for our mail's html content, and render it with the render method:

app/views/mails/greetings.html
std::string_view @name;
// END LINKING
<h2>Greetings, dear <%= name %></h2>

<p>
  These are just some words of greetings,<br/>
  Thank you and goodbye.
</p>

Now, to generate and send the email using our template and the Mailer class:

#include <crails/mailer.hpp>

void mailer_powered_mailing()
{
  auto mailer = std::make_shared<Crails::Mailer>("default");

  mailer->get_mail().add_recipient("roger@yahoo.com");
  mailer->get_mail().set_sender("no-reply@domain.org");
  mailer->get_mail().set_subject("Words of greetings");
  mailer->render("mail/greetings", {
    {"name", "roger"}
  });
  mailer->send();
}

2.2 Integrating in controllers

You may connect a mailer to a controller to gain some interesting features, such as handling errors, or sharing the content of the controller's var attribute with templates rendered by the mailer.

To connect your mailer with your controller, just pass a shared pointer of your controller to the mailer's constructor:

#pragma once
#include "application_controller.h"
#include <crails/mailer.hpp>

class MyController : public ApplicationController
{
  std::shared_ptr<Crails::Mailer> mailer;
public:
  MyController(Crails::Context& context) : ApplicationController(context)
  {
    mailer = std::make_shared<Crails::Mailer>(
      *this,
      "default"
    );
  }

  void send_mail()
  {
    vars["name"] = "roger"; // controller vars will be bound to templates rendered by the mailer 
    mailer->get_mail().add_recipient(params["recipient"].as<std::string>());
    mailer->get_mail().set_sender("no-reply@me.org");
    mailer->render("mail/greetings");
    mailer->send([this]()
    {
      render(TEXT, "Email sent successfully");
    });
  }
};

Note that in this previous example, we wait until the mail has actually been sent before sending back an answer to the HTTP client.
This isn't a good way to handle this, as the client will receive no response if an error happens while sending the email. The next chapter will show you how to handle both success and error scenarios.

2.2.2 Error management

Overloading the Mailer class can bring you more control to what happens after the mail sending process has been completed or aborted, using the on_sent and on_error_occured virtual methods:

#pragma once
#include "application_controller.h"
#include <crails/mailer.hpp>

class MyController : public ApplicationController
{
  class MyMailer : public Crails::Mailer
  {
  public:
    MyMailer(MyController& controller) : Crails::Mailer(controller, "default")
    {
      callback = std::bind(&MyController::on_mail_completed, &controller, std::placeholders::_1);
    }

    void on_sent() override
    {
      callback(true);
    }

    void on_error_occured(const std::exception& error) override
    {
      Crails::logger << Crails::Logger::Error
                     << "MyController failed to send mail: "
                     << error.what() << Crails::Logger::endl;
      callback(false);
    }

  private:
    std::function<void(bool)> callback;
  };

  std::shared_ptr<MyMailer> mailer;
public:
  MyController(Crails::Context& context) : ApplicationController(context)
  {
    mailer = std::make_shared<MyMailer>(*this);
  }

  void send_mail()
  {
    vars["name"] = "roger";
    mailer->get_mail().add_recipient(params["recipient"].as<std::string>());
    mailer->get_mail().set_sender("no-reply@me.org");
    mailer->render("mail/greetings");
    mailer->send();
  }

  void on_mail_completed(bool success)
  {
    render(TEXT, success ? "Email sent successfully" : "Failed to send email");
  }
};

In the previous example, we overloaded Crails::Mailer and connected the on_sent and on_error_occured to the on_mail_completed method in our controller, allowing us to send a response to the HTTP client regardless of whether the process fails or succeed.

This is merely a simple example of what you may do from overloading mailers: the possibilities are legion. Good luck.