Email

Envío de emails en HTML con Mailgun-Go

En este tutorial, te enseñaré cómo enviar emails en HTML con imágenes incrustadas usando mailgun-go. Antes de meternos en la programación, vamos a definir el problema y cómo podemos usar Mailgun para mejorar la experiencia de usuario de nuestra aplicación.
Imagen para Envío de emails en HTML con Mailgun-Go

En este tutorial, te enseñaré cómo enviar emails en HTML con imágenes incrustadas usando mailgun-go. Antes de meternos en la programación, vamos a definir el problema y cómo podemos usar Mailgun para mejorar la experiencia de usuario de nuestra aplicación.

Presentamos Channel-stats

Channel-stats es un bot de Slack para recopilar estadísticas de los mensajes que se envían a un canal de Slack. Además de contar los emojis y los enlaces que se comparten en el canal, también analiza la actitud de los mensajes y les asigna una puntuación positiva o negativa que, más tarde, los usuarios pueden revisar y representar en un gráfico como porcentaje del total de mensajes.

Image of statistics collected from Slack bot "Channel-stats"

Queremos ampliar esta funcionalidad enviando por email un informe semanal sobre las estadísticas de un canal a nuestros usuarios. Como el email incluirá datos en gráficos, los emails de texto sin formato serían muy aburridos. Por eso, queremos enviar un email en HTML interactivo con gráficos a la bandeja de entrada de nuestro usuario. Para lograrlo, tenemos que crear un poco de HTML, con CSS en línea e imágenes para que sea atractivo visualmente.

email en HTML

Se ha escrito mucho sobre el envío de HTML en los emails, pero aquí tienes unas cuantas reglas básicas:

  • SÍ: usa CSS en línea
  • SÍ: usa TABLAS HTML para el diseño
  • SÍ: usa imágenes (mejor .png)
  • SÍ: usa imágenes en línea
  • NO uses HTML5
  • NO uses CSS de animación
  • NO  enlaces a una hoja de estilos externa
  • NO uses estilos CSS en el HEAD
  • NO uses javascript
  • NO uses flash

A menudo, no basta con seguir estas reglas, ya que no existen estándares establecidos sobre cómo se visualiza el HTML en los emails. Si te empeñas en que tus emails se vean bien en el mayor número de clientes posible, quizás deberías usar un servicio como Litmus para diseñar, generar una previsualización y probar tu email en diferentes clientes. Sin embargo, para lo que queremos hacer y como channel-stats es un proyecto de código abierto, voy a mantener los costes de producción al mínimo y a usar algunas plantillas gratuitas que ofrece Mailgun (aunque me echó una mano el equipo de diseño UX/UI). Este es el resultado:

Channel report designed using Mailgun's free templates

Ahora que tenemos nuestro HTML y CSS, tenemos que poner el CSS en línea para que la mayoría de los clientes de email visualicen nuestro email correctamente. Hay varias herramientas en línea para hacerlo, pero te recomendamos Dialect Premailer para este fin.

El código

Como queremos que el email se envíe cada semana, usamos una biblioteca cron para crear una función que se ejecute todos los domingos a medianoche. A continuación, tenemos que generar las imágenes que irán en nuestro email. Channel-stats ya usa go-chart para renderizar las imágenes .png de los gráficos de la interfaz de usuario (UI), así que podemos adaptarlo a nuestras necesidades.

Además, a la hora de entregar el proyecto final, no queremos distribuir archivos HTML y CSS de manera separada a la de nuestro binario final de golang compilado, así que channel-stats usa el go-bindata proyecto para agrupar el HTML y el CSS en un único binario de channel-stats.

Ahora, echemos un vistazo al código del renderizado.

                                

                                    func NewReporter(conf Config, list ChanLister, notify Mailer, store Storer) (Reporter, error) {rn  r := Report{rn     log:   GetLogger().WithField("prefix", "reporter"),rn     cron:  cron.New(),rn     mail:  notify,rn     store: store,rn     conf:  conf,rn     list:  list,rn}rn  return &r, r.start()rn}rnrnfunc (r *Report) start() error {rn  err := r.cron.AddFunc(r.conf.Report.Schedule, func() {rn     timeRange := toTimeRange(r.conf.Report.ReportDuration.Duration)rn     r.log.Debugf("Creating report for %s to %s", timeRange.Start, timeRange.End)rnrn     for _, channel := range r.list.Channels() {rn        // Skip channels the bot is not inrn        if !channel.IsMember {rn           continuern        }rnrn        html, err := r.genHtml("html/templates/email.tmpl", channel.Name)rn        if err != nil {rn           r.log.Errorf("during email generate: %s", err)rn           returnrn        }rnrn        data := ReportData{rn           Images: make(map[string][]byte),rn           Html:   html,rn        }rnrn        // Generate the images for the reportrn        data.Images["most-active.png"] = r.genImage(RenderSum, timeRange, channel.Id, "messages")rn        data.Images["top-links.png"] = r.genImage(RenderSum, timeRange, channel.Id, "link")rn        data.Images["top-emoji.png"] = r.genImage(RenderSum, timeRange, channel.Id, "emoji")rn        data.Images["most-negative.png"] = r.genImage(RenderPercentage, timeRange, channel.Id, "negative")rn        data.Images["most-positive.png"] = r.genImage(RenderPercentage, timeRange, channel.Id, "positive")rnrn        // Email the reportrn        if err := r.mail.Report(channel.Name, data); err != nil {rn           r.log.Errorf("while sending report: %s", err)rn        }rn     }rn  })rn  if err != nil {rn     return errrn  }rnrn  r.cron.Start()rn  return nilrn}
                                
                            

En el método Start() iteramos a través de todos los canales de los que el bot es miembro y generamos un informe para cada canal. Luego, llamamos a genHtml(), que recupera nuestro email en HTML como una plantilla llamada templates/email.tmpl de nuestro almacén de recursos compilado en el paquete HTML. Después, pasamos la plantilla por el motor estándar de golang HTML/template para generar el HTML final. A continuación, genImage() llama a la función de renderizado con el rango de horas y el tipo de contador que queremos recuperar del almacén de datos. Cuando ReportData está completo, pasamos los datos a mail.Report() para su entrega.

Ahora que tenemos nuestras imágenes y el HTML, vamos a hacer una pausa para hablar un poco sobre el MIME de HTML y la codificación de las imágenes. MIME es el formato en el que se codifican los cuerpos de los emails cuando se envían mediante el protocolo SMTP. Es el formato que permite a los clientes de email codificar HTML, además de adjuntar y recuperar archivos e imágenes en un email.

Para que nuestras imágenes se muestren correctamente en el HTML, tenemos que codificar las imágenes en el MIME. Para ello, tenemos dos opciones: podemos añadir las imágenes como un adjunto, o podemos poner las imágenes en línea. El RFC sobre la disposición de contenido dice que inline indica que la entidad debe mostrarse de inmediato al usuario, mientras que attachment significa que el usuario debe realizar una acción adicional para visualizar la entidad. Como nuestras imágenes se deben mostrar de inmediato al usuario a través del HTML, elegimos en línea.

Llegados a este punto, podríamos usar cualquier número de bibliotecas MIME de golang para poner nuestras imágenes en línea y generar el cuerpo del email en formato MIME, pero con Mailgun, no hace falta que lo hagamos. Mailgun generará el MIME por nosotros y nos ofrece opciones para poner archivos en línea e imágenes mediante la API pública.

Ahora que sabemos cómo poner las imágenes en línea en el MIME, tenemos que hacer referencia a ellas desde nuestro HTML. Para ello, usamos el prefijo cid: en nuestras etiquetas . De este modo, si nuestra imagen en línea se llama most-active.png, la etiqueta de nuestra imagen sería 

Con el HTML listo, veamos cómo enviar el email y las imágenes con mailgun-go.

                                

                                    func NewMailgunNotifier(conf Config) (Mailer, error) {rn  return &Mailgun{rn    mg:   mailgun.NewMailgun(conf.Mailgun.Domain, conf.Mailgun.APIKey),rn    log:  GetLogger().WithField("prefix", "mailer"),rn    conf: conf,rn  }, nilrn}rnrn// Send a report to the designated email address (could be mailing list)rnfunc (m *Mailgun) Report(channelName string, data ReportData) error {rn  if m.conf.Mailgun.ReportAddr == "" {rn    m.log.Errorf("mailgun.enabled = true; however mailgun.report-address is empty; skipping..")rn    return nilrn  }rnrn  // Create a subject for the reportrn  subject := fmt.Sprintf("[channel-stats] Report for %s", channelName)rn  // Create a message with no text bodyrn  message := m.mg.NewMessage(m.conf.Mailgun.From, subject, "", m.conf.Mailgun.ReportAddr)rn  // Send the HTML to mailgun for MIME encodingrn  message.SetHtml(string(data.Html))rnrn  for file, contents := range data.Images {rn    message.AddReaderInline(file, ioutil.NopCloser(bytes.NewBuffer(contents)))rn  }rnrn  ctx, cancel := context.WithTimeout(context.Background(), m.conf.Mailgun.Timeout.Duration)rn  defer cancel()rnrn  _, id, err := m.mg.Send(ctx, message)rn  if err != nil {rn    return errrn  }rn  m.log.Infof("Sent report via mailgun (%s)", id)rn  return nilrn}
                                
                            

Primero, creamos una nueva instancia de Mailgun usando nuestro nombre de dominio y la clave de API en NewMailgunNotifier(). Luego, en el método Report(), llamamos a NewMessage() para crear un objeto al que añadiremos nuestro HTML y las imágenes. Fíjate en que el argumento text de NewMessage() es una cadena vacía. Aunque es posible codificar tanto texto sin formato como HTML en el mensaje MIME, aquí solo proporcionamos HTML porque las imágenes de gráficos en línea no servirían de nada en un cliente de solo texto. A continuación, llamamos a SetHtml() y agregamos nuestras imágenes en línea mediante un objeto ReadCloser que creamos sobre la marcha desde nuestro []byte buffer. Por último, enviamos la solicitud creada a la API de Mailgun para la generación del MIME y la entrega mediante el método Send().

Conclusión

Espero que este tutorial te haya dado una idea de cómo enviar emails en HTML de gran calidad usando mailgun-go y la API de Mailgun. Si tienes comentarios o encuentras errores en alguno de los proyectos, puedes encontrar todo el código y la biblioteca a continuación.

¿Te interesa trabajar en Mailgun? ¡Buscamos gente! Y tenemos varios puestos de desarrollo disponibles. Echa un vistazo a nuestras ofertas de trabajo actuales aquí.

Webinar destacado: predicciones y propósitos para el envío en 2019

¿Te perdiste este webinar a principios de 2019? No te preocupes. ¡Lo hemos grabado! Vuelve a ver a Nick y Natalie hablar sobre algunos hitos del email en 2018 y de lo que nos deparaba 2019. Los equipos técnico y de marketing aunaron posturas en este tema, y puedes volver a verlo aquí.

Mailgun's Predictions & Resolutions webinar banner featuring Nick Schafer and Natalie Hays