domingo, 27 de marzo de 2016

Email en aplicaciones Java (Con MailGun)

Nuestras aplicaciones tienen como objetivo facilitar tareas pesadas y tediosas al usuario maximizando la producción y minimizando costos, tiempo y personal, pero también es necesario entregar información al usuario en reportes y enviarle notificaciones a un sitio respectivamente confiable que el usuario pueda valerse y controlar para visualizar dicha información.

Para estos casos recurrimos al correo electrónico, pero en cuanto a desarrollo aveces nos presenta un desafío ya no sabemos configurarlo como es debido y tendemos a caer en spam o incluso a entrar a las listas negras por no tener un control de como se envían los correos.

Debido a algunos problemas presentados por amigos y colegas con respecto a este tema les mostraré como configurar un servicio de correo para nuestras aplicaciones.

Nota: Debido a que el servidor donde corre la aplicación debe tener servicio de correo (Software para email) obviaré esta parte y solo presentaré la configuración del servicio en cuanto al punto del desarrollador, por lo que daré por sentado que en el servidor ya se tiene configurado un servicio de correo (Consta decir que lo que mostraré a continuación es la forma de exponer nuestro servicio de correo al exterior). También daré por sentado que saben configurar los nombres de dominios en sus respectivos servicios.

Ahora bien, para poder configurar y enviar los correos en nuestra aplicación existen ciertos factores a tomar en cuenta y lo planteo con las siguientes preguntas:
  • Que servicio de correo voy a usar?.
  • Existe un API para trabajar con el servicio que seleccione?
  • Como enviaremos el correo?
  • Como creo el contenido del correo?
Entonces procedemos a resolver el caso respondiendo las preguntas

  • MailGun será nuestro servicio.
  • Podría encontrarse un API, pero es posible programar el asunto sin mucho juego y de forma sencilla.
  • Usaremos las librerías que nos aconseja MailGun
  • Para esto dispondremos de ciertas herramientas para hacer más elegante el correo. (Templates responsive)


Una vez definido procedemos a crear la cuenta en el servicio (nuestro caso MailGun). Una vez nos registramos tenemos que configurar un dominio o si prefieren para probar se puede usar el que viene al crear la cuenta.

Esta claro que si usaremos un dominio que compramos debemos registrarlo, pero eso no bastará, sino que tenemos que ir a configurar nuestro dominio para que tolere nuestro servicio. Para ello una vez registrado el dominio procedemos a ir a la parte de configuración del dominio en MailGun y en la sección de verificación de dominio y DNS veremos la información necesaria para configurar nuevo dominio y activar el correo.

Una vez tenemos la información procedemos a visitar nuestro proveedor del dominio y vamos directamente a configurar ciertos parámetros internos del mismo. Para realizar esto seguimos las siguientes indicaciones:

  • Creando un campo TXT insertamos como valor "v=spf1 include:mailgun.org ~all"  (este caso debe ser directo al servicio, es decir sin ningún subdominio).
  • Creando otro campo TXT, pero con subdominio "smtp._domainkey" y le insertamos como valor la llave que MailGun nos provee para ese dominio.
  • Creamos un campo CNAME con subdominio "email" y le insertamos como valor "mailgun.org".
  • Creamos un campo MX con valor "mxa.mailgun.org"
  • Creamos un campo MX con valor "mxb.mailgun.org"


Que conste que estos valores son para el caso actual y pueden variar para el dominio en cuestión, por lo que es recomendable que usen los valores que el servicio les da.

Una vez terminado el proceso salvamos los cambios en el dominio y procedemos a esperar que MailGun nos confirme que hemos validado correctamente el dominio haciendo uso de la herramienta que el mismo servicio nos provee para refrescar y validar los datos del dominio. Dependiendo del proveedor del nombre de dominio podrían tardar unas horas o incluso días para ver estos cambios.

Una vez todo este correcto ya podemos proceder a escribir lo que enviará nuestro correo en la aplicación. Se podrán ver ejemplos en otros lenguajes visitando la página del servicio en cuestión (MailGun Doc).

Como MailGun tiene un servicio Restful podremos usar a Jersey en nuestro caso para enviar nuestros correos y solo necesitaremos los módulos:

  • jersey-client.jar (version ~ 1.17 - 1.18.1)
  • jersey-core.jar (version ~ 1.17 - 1.18.1)
  • jersey-multipart.jar (version ~ 1.17 - 1.18.1).

Una ves incluimos estas librerías en nuestro proyecto procedemos a crear una clase de ejemplo:

?

  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.multipart.FormDataMultiPart;
import com.sun.jersey.multipart.file.FileDataBodyPart;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.util.List;
public class Email {
    private Client client;
    private WebResource wrSender;
    private String apiKey;
    private String urlDomain;
    public Email(String apiKey, String urlDomain) {
        this.apiKey = apiKey;
        this.urlDomain = urlDomain;
        init();
    }
    private void init() {
        client = Client.create();
        client.addFilter(new HTTPBasicAuthFilter("api", apiKey));
        wrSender = client.resource(urlDomain + "/messages");
    }
    public String sendEmail(String from, String subject, String data, boolean asHTML, List<String> tos) {
        if (tos == null || tos.size() == 0) {
            return null;
        }
        MultivaluedMapImpl formData = new MultivaluedMapImpl();
        formData.add("from", from);
        for (String toSend : tos) {
            formData.add("to", toSend);
        }
        formData.add("subject", subject);
        if (asHTML) {
            formData.add("html", data);
        } else {
            formData.add("text", data);
        }
        return wrSender.type(MediaType.APPLICATION_FORM_URLENCODED)
                .post(ClientResponse.class, formData).getEntity(String.class);
    }
    public String sendEmail(String from, String subject, String data, boolean asHTML, List<String> tos, List<File> files) {
        if (tos == null || tos.size() == 0) {
            return null;
        }
        FormDataMultiPart formData = new FormDataMultiPart();
        formData.field("from", from);
        for (String toSend : tos) {
            formData.field("to", toSend);
        }
        formData.field("subject", subject);
        if (asHTML) {
            formData.field("html", data);
        } else {
            formData.field("text", data);
        }
        for (File file : files) {
            formData.bodyPart(new FileDataBodyPart("attachment", file, MediaType.TEXT_PLAIN_TYPE));
        }
        return wrSender.type(MediaType.MULTIPART_FORM_DATA_TYPE)
                .post(ClientResponse.class, formData).getEntity(String.class);
    }
}

  
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.multipart.FormDataMultiPart;
import com.sun.jersey.multipart.file.FileDataBodyPart;

import javax.ws.rs.core.MediaType;
import java.io.File;
import java.util.List;

public class Email {

    private Client client;
    private WebResource wrSender;

    private String apiKey;
    private String urlDomain;

    public Email(String apiKey, String urlDomain) {
        this.apiKey = apiKey;
        this.urlDomain = urlDomain;
        init();
    }

    private void init() {
        client = Client.create();
        client.addFilter(new HTTPBasicAuthFilter("api", apiKey));
        wrSender = client.resource(urlDomain + "/messages");
    }

    public String sendEmail(String from, String subject, String data, boolean asHTML, List<String> tos) {
        if (tos == null || tos.size() == 0) {
            return null;
        }
        MultivaluedMapImpl formData = new MultivaluedMapImpl();
        formData.add("from", from);
        for (String toSend : tos) {
            formData.add("to", toSend);
        }
        formData.add("subject", subject);
        if (asHTML) {
            formData.add("html", data);
        } else {
            formData.add("text", data);
        }
        return wrSender.type(MediaType.APPLICATION_FORM_URLENCODED)
                .post(ClientResponse.class, formData).getEntity(String.class);
    }

    public String sendEmail(String from, String subject, String data, boolean asHTML, List<String> tos, List<File> files) {
        if (tos == null || tos.size() == 0) {
            return null;
        }
        FormDataMultiPart formData = new FormDataMultiPart();
        formData.field("from", from);
        for (String toSend : tos) {
            formData.field("to", toSend);
        }
        formData.field("subject", subject);
        if (asHTML) {
            formData.field("html", data);
        } else {
            formData.field("text", data);
        }
        for (File file : files) {
            formData.bodyPart(new FileDataBodyPart("attachment", file, MediaType.TEXT_PLAIN_TYPE));
        }
        return wrSender.type(MediaType.MULTIPART_FORM_DATA_TYPE)
                .post(ClientResponse.class, formData).getEntity(String.class);
    }
}

Por último para retocar nuestro contenido del correo y tener algo más empresarial podríamos usar un sistema de plantillas responsive como estos:

https://github.com/mailgun/transactional-email-templates
https://github.com/leemunroe/responsive-html-email-template
https://github.com/leemunroe/grunt-email-workflow

Masketfield en Vaadin

En algunos casos necesitamos filtrar la data que el cliente nos digita y esta sigue un patrón especifico, pero realizar esta tarea usando Listener del lado del servidor quizás no sea la forma más eficiente usando el Framework Vaadin.

Para mejorar y hacer más práctica esta tarea les traigo un plugin para realizar lo que se llama Mascara MasketField.

Con este plugin podrán tener máscaras rápidas en vaadin y todo del lado del cliente, sin la necesidad de fatigar el servidor con los listeners para atender dicha petición.

El plugin es Open y cualquier ayuda es bienvenida.