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.
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();
}
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.