public interface EmailSender {
void send(EmailMessage email);
}
Qui installe encore ces propres serveurs de mail pour envoyer les mails de ces applications ? Il existe aujourd’hui plusieurs services en ligne qui sont très simples à utiliser. Regardons comment le faire dans une application Java Spring Boot.
Tous le code source montré ci dessous, peut être récupéré sous Github. Nous allons voir différentes manières d’écrire un service qui implémente l’interface ci dessous
public interface EmailSender {
void send(EmailMessage email);
}
Un EmailMessage
est un bean Java comportant trois propriétés : destinataire, sujet et contenu du mail.
SMTP (Simple Mail Transfer Protocol) porte bien son nom car il permet d’envoyer simplement des mails. Nous verrons plus loin que cette simplicité se retrouve aussi dans les autres moyens de faire. Pour limiter les spams et monétier leur service, les fournisseurs de mails peuvent mettre certaines limites. Par exemple Gmail limite le nombre de mails quotidien envoyés via SMTP à 500. Pour en envoyer plus vous devez passer par leur API et payer un abonnement en fonction de vos besoins.
Revenons à nos moutons. Pour ajouter la gestion des mails dans une application Spring Boot, vous devez ajouter le module Gradle ou Maven spring-boot-starter-mail
. Par exemple en Gradle
compile('org.springframework.boot:spring-boot-starter-mail')
Vous pouvez ensuite paramétrer les accès SMTP dans les paramètres de l’application. Par exemple dans le fichier application.yml
spring:
mail:
protocol: smtp
host: smtp.gmail.com
port: 587
username: guillaume@dev-mind.fr
password: mypassword
properties:
mail:
smtp:
auth: true
starttls:
enable: true
Vous pouvez maintenant injecter un objet JavaMailSender
dans votre code et votre service peut s’écrire tout simplement de cette manière
@Component
public class GmailSender implements EmailSender {
private JavaMailSender javaMailSender;
public GmailSender(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
@Override
public void send(EmailMessage email) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(email.getTo());
helper.setSubject(email.getSubject());
helper.setFrom("guillaume@dev-mind.fr");
message.setContent(email.getContent(), "text/html");
javaMailSender.send(message);
}
catch (MessagingException e) {
throw new RuntimeException(e);
}
}
}
Depuis que tout se déploie dans le Cloud il existe un grand nombre d’API qui nous permettent de simplifier considérablement notre développement. Elles sont facilement intégrables dans tout type d’application car elles proposent souvent des accès HTTP via une clé d’API.
Sendgrid est un des acteurs les plus connus pour envoyer des mails. SendGrid vous permet de gérer des templates afin de mettre en forme les emails envoyés. Vous disposez également de nombreuses statisiques permettant de suivre les mails envoyés, ouvert ou non….
Vous bénéficiez d’un quota gratuit de 100 emails/jour (soit 3.000 emails par mois). Si vous déployez votre application sur CloudFoundry, les quotas gratuits sont plus élevés (25.000 emails par mois).
Dans tous les cas vous devrez aller sur leur site pour activer votre clé d’API
Une fois que vous avez votre clé d’API vous devez intégrer dans votre application la dépendance Send Grid
compile('com.sendgrid:sendgrid-java:4.1.2')
Spring Boot contient un auto configurer pour SendGrid quand la dépendance est ajoutée à votre projet. Vous pouvez déclarer la clé dans votre fichier application.yml
sous cette forme
spring:
sendgrid:
api-key: ${SENDGRID_APIKEY:Bar12345Bar12345}
Vous pouvez maintenant utiliser l’objet SendGrid
dans votre code
@Component
public class SendgridSender implements EmailSender {
private SendGrid sendGrid;
public SendgridSender(SendGrid sendGrid) {
this.sendGrid = sendGrid;
}
@Override
public void send(EmailMessage email) {
Mail mail = new Mail(
new Email("guillaume@dev-mind.fr", "Dev-Mind"),
email.getSubject(),
new Email(email.getTo()),
new Content("text/html", email.getContent()));
try {
Request request = new Request();
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
sendGrid.api(request);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Vous pouvez voir que ce n’est pas plus compliqué que précédemment.
Dans la dernière partie nous allons utiliser un autre service en ligne similaire à SendGRid qui se nomme Elastic email. L’avantage est que vous disposez d’un quota gratuit un peu plus important(150.000 emails par mois).
Le but est surtout de vous montrer comment faire un appel HTTP tout simple en utilisant RestTemplate dans une application spring-web, ou WebClient dans une application réactive spring-web-flux. Je vais vous montrer ici l’utilisation de WebClient.
Dans le fichier Gradle nous allons importer le starter webflux
compile("org.springframework.boot:spring-boot-starter-webflux")
testCompile("com.squareup.okhttp3:mockwebserver:3.9.1")
La deuxième librairie importée okhttp est un client HTTP minimaliste que nous allons utiliser dans nos tests. En effet le projet spring-test doit encore intégrer de nouveaux utilitaires pour tester facilement WebClient (voir ticket ouvert).
J’ajoute la configuration Elastic Mail (clé d’API) dans mon fichier application.yml
devmind:
elasticmail:
apikey: ${ELASTICMAIL_APIKEY:Bar12345Bar12345}
host: ${ELASTICMAIL_HOST:https://api.elasticemail.com}
version: ${ELASTICMAIL_VERSION:v2}
Et je peux écrire mon service
@Component
public class ElasticMailSender implements EmailSender {
@Autowired
private EmailProperties properties;
private WebClient webClient;
public ElasticMailSender() {
webClient = WebClient.create(properties.getElasticmail().getHost());
}
public ElasticMailSender(EmailProperties properties, WebClient webClient) {
this.properties = properties;
this.webClient = webClient;
}
@Override
public void send(EmailMessage email) {
ElasticEmailResponseDTO response = webClient.post()
.uri(String.format("/%s/email/send", properties.getElasticmail().getVersion()))
.body(BodyInserters
.fromFormData("apikey", properties.getElasticmail().getApikey())
.with("from", "guillaume@dev-mind.fr")
.with("fromName", "DEv-Mind")
.with("to", email.getTo())
.with("subject", email.getSubject())
.with("isTransactional", "true")
.with("body", email.getContent())
)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(ElasticEmailResponseDTO.class)
.block();
if (response.getSuccess() == false) {
throw new RuntimeException(response.getError());
}
}
}
ElasticEmailResponseDTO
est un bean Java comprenant deux propriétés : succes (boolean) et error (message d’erreur éventuel). Le constructeur avec deux arguments est utilisés pour les tests afin de sucharger le Webclient. Voici la classe de test de ce service
public class ElasticMailSenderTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private EmailProperties properties;
private MockWebServer server;
private WebClient webClient;
private ElasticMailSender elasticMailSender;
@Before
public void setUp(){
ExternalApi api = new ExternalApi();
api.setApikey("mykey");
given(properties.getElasticmail()).willReturn(api);
this.server = new MockWebServer();
this.webClient = Mockito.spy(WebClient.create(this.server.url("/").toString()));
elasticMailSender = new ElasticMailSender(properties, webClient);
}
@Test
public void send() {
prepareResponse(response -> response
.setHeader("Content-Type", "application/json")
.setBody("{ "success" : true }"));
elasticMailSender.send(new EmailMessage(
"guillaume@test.fr",
"Email test",
"<h1>Hi Guillaume</h1><p>Waow... you are able to send an email</p>")
);
verify(webClient, atLeastOnce()).post();
}
@Test
public void sendWithError() {
prepareResponse(response -> response
.setHeader("Content-Type", "application/json")
.setBody("{ "success" : false, "error" : "error expected" }"));
assertThatThrownBy(() -> elasticMailSender.send(new EmailMessage(
"guillaume@test.fr",
"Email test",
"<h1>Hi Guillaume</h1><p>Waow... you are able to send an email</p>")))
.isExactlyInstanceOf(RuntimeException.class)
.hasMessage("error expected");
}
private void prepareResponse(Consumer< MockResponse> consumer) {
MockResponse response = new MockResponse();
consumer.accept(response);
this.server.enqueue(response);
}
}
Comme je le disais plus haut l’ensemble du code est disponible sous Github. J’espère vous avoir montrer qu’il était assez simple d’envoyer un mail dans une application Java.