Si queremos enviar correos desde el backend de nuestras aplicaciones, podemos formar el cuerpo de estos utilizando Thymeleaf. Éste es un motor de creación de plantillas html, que proporciona algunos atributos especiales para su construcción, lo que nos facilitará crear plantillas para los emails que queramos enviar, pudiendo editarlos viendo el resultado, y teniendo una mejor organización en nuestro proyecto. Vamos a ver en primer lugar la configuración y un ejemplo de uso, y después pasaremos a ver algunos de los atributos especiales que proporciona.

Configuración

En primer lugar, tendremos que añadir las siguientes dependencias a nuestro pom.xml:

		<!-- Thymeleaf -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity5</artifactId>
			<version>3.0.5.RELEASE</version>
		</dependency>

Después, para configurar Thymeleaf en nuestro proyecto tendremos que añadir una nueva clase de configuración. Si tenemos un paquete "config" donde tengamos la configuración de segurdad del proyecto, podemos añadirla ahí, si no donde quede más intuitivo en la organización de nuestro proyecto. La clase será la siguiente:

package es.um.atica.nombreproyecto.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.StringTemplateResolver;

@Configuration
public class MailConfig {

	@Bean
	public ResourceBundleMessageSource emailMessageSource() {
		final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		messageSource.setBasename( "mail/MailMessages" );
		return messageSource;
	}

	@Bean
	public TemplateEngine emailTemplateEngine() {
		final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
		// Resolver for HTML editable emails (which will be treated as a String)
		templateEngine.addTemplateResolver( templateResolver() );
		// Message source, internationalization specific to emails
		templateEngine.setTemplateEngineMessageSource( emailMessageSource() );
		return templateEngine;
	}

	private ITemplateResolver templateResolver() {
		final StringTemplateResolver templateResolver = new StringTemplateResolver();
		templateResolver.setOrder( 1 );
		templateResolver.setTemplateMode( TemplateMode.HTML );
		templateResolver.setCacheable( false );
		return templateResolver;
	}
}

Plantillas

Las plantillas en nuestro proyecto serán archivos html y se encontrarán en la carpeta src/main/resources/templates. Por defecto, Thymeleaf parte de esa ruta, a partir de ahí podemos organizar los archivos como queramos. Un ejemplo de plantilla sería el siguiente:

<meta charset="UTF-8">
<body>
	Buenos días,
	</br> Se ha solicitado una
	<strong>devolución de recibo</strong> . Los datos de la devolución son los siguientes:
	<ul>
		<li>Usuario: <a th:text="${usuario}" /></li>
		<li>documento: <a th:text="${documento}" /></li>
		<li>direccion: <a th:text="${direccion}" /></li>
		<li>telefono: <a th:text="${telefono}" /></li>
		<li>email: <a th:text="${email}" /></li>
		<li>centro: <a th:text="${centro}" /></li>
		<li>iban: <a th:text="${iban}" /></li>
		<li>recibo: <a th:text="${recibo}" /></li>
		<ul>
</body>

Ahí especificamos una serie de variables, que vamos a ver cómo asignarlas en la parte Java.

Uso de la plantilla

Para utilizar la plantilla y obtener el String correspondiente al mensaje, siguiendo con la plantilla anterior, lo haríamos así:

import org.thymeleaf.context.Context;

// Añadimos templateEngine a la clase utilizando autowired
@Autowired
private TemplateEngine templateEngine;

...

final Context ctx = new Context();
ctx.setVariable( "usuario", "willy" );
ctx.setVariable( "documento", "un pdf" );
ctx.setVariable( "direccion", "Calle Falsa 123" );
ctx.setVariable( "telefono", "666 666 666 7" );
ctx.setVariable( "email", "willy@willy.wi" );
ctx.setVariable( "centro", "Ática" );
ctx.setVariable( "iban", "ibienen" );
ctx.setVariable( "recibo", "231687496549" );

final String htmlContent = templateEngine.process( "email.html", ctx );

Como vemos, utilizamos el Context de Thymeleaf (cuidado no nos equivoquemos al importarlo), establecemos los valores de las variables, y lo procesamos para obtener el String. En este caso, el archivo email.html se encuentra en la ruta base que hemos mencionado anteriormente. Si se encontrase en una subcarpeta, la ruta seguiría siendo respecto a esa ruta base, seríe "carpeta/email.html".

Atributos de Thymeleaf

Veamos los principales atributos que podremos usar: enlazar variables y renderización condicional.

Enlazar variables

Es el caso que hemos visto en el ejemplo anterior. Especificamos la variable con ${variable}, y para indicar que se renderice el texto correspondiente, lo hacemos incluyendo th:text:

<li>recibo: <a th:text="${recibo}" /></li>

Si vemos documentación de Thymeleaf, veremos expresiones parecidas con otros caracteres que no sean el dólar. Por saber a qué hacen referencia, estas son las diferentes expresiones:

Podemos leer más sobre esta sintaxis en la documentación oficial.

Recorrer una lista

El equivalente a un for each en nuestras plantillas sería th:each. Este es el atributo para iterar sobre una lista, y su sintaxis sería la siguiente:

 <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
      <td th:text="${prod.name}">Onions</td>
      <td th:text="${prod.price}">2.41</td>
     <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
 </tr>

En este caso recorremos la lista que se pase como ${prods}, teniendo en prod el objeto y en iterStat estadísticas sobre la iteración, como el índice actual o el total de iteraciones.

Renderización condicional: if, unless, switch

Con th:if haremos que el elemento se renderice si el valor que pasamos es true:

th:if="${user.admin}"

El caso contrario es th:unless. Esto no es un else, indica que se mostrará si el valor que le pasemos es false:

th:unless="${user.admin}" (equivale a th:if="${!(user.admin)}")

Por último, tenemos th:switch, que es como el switch que conocemos en programación, con esta sintaxis:

 <div th:switch="${user.role}">
 	<p th:case="'admin'">User is an administrator</p>
 	<p th:case="#{roles.manager}">User is a manager</p>
 	<p th:case="*">User is some other thing</p>
 </div>

Para leer más sobre evaluación condicional, podemos ver la documentación oficial.