Saltar al contenido
Docs

Tutorial

Envía emails desde Go con el SDK emailit-go o net/smtp, gestiona los errores de la API y verifica las firmas de los webhooks de Emailit en un handler de net/http.

Actualizado el 1 oct 2026

En esta guía se explica cómo enviar emails desde un servicio en Go con el SDK oficial emailit-go, cómo usar en su lugar net/smtp y cómo verificar los webhooks en un handler de net/http.

Requisitos previos

  • Go 1.21 o posterior.
  • Un dominio de envío verificado, por ejemplo acme.com.
  • Una clave de API. Basta con una clave Sending Only limitada a tu dominio.
  • Hasta que tu espacio de trabajo tenga acceso de producción, solo puedes enviar a las direcciones de email de las cuentas de los miembros del espacio de trabajo.

Instalar el SDK

Terminal
go get github.com/emailit/emailit-go/v2

El SDK solo depende de la biblioteca estándar de Go.

Configurar la clave de API

Pasa la clave a tu proceso como variable de entorno:

Terminal
export EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••

Enviar un email

main.go
package main

import (
	"log"
	"os"

	"github.com/emailit/emailit-go/v2"
)

func main() {
	client := emailit.NewClient(os.Getenv("EMAILIT_API_KEY"))

	email, err := client.Emails.Send(&emailit.SendEmailRequest{
		From:    "Acme <hello@acme.com>",
		To:      []string{"ada@example.com"},
		Subject: "Your login code",
		Text:    "Your code is 482913. It expires in 10 minutes.",
		Html:    "<p>Your code is <strong>482913</strong>. It expires in 10 minutes.</p>",
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Println("sent", email.Id) // em_…
}

Crea el cliente una vez y compártelo; se puede reutilizar sin problemas entre peticiones. Para enviar una plantilla guardada, asigna a Template su alias o su ID tem_ y pasa Variables. Para cambiar los tiempos de espera, pasa tu propio cliente: emailit.NewClient(key, emailit.WithHTTPClient(&http.Client{Timeout: 10 * time.Second})).

Gestionar los errores

El SDK devuelve errores normales de Go, con funciones auxiliares para clasificarlos:

Go
email, err := client.Emails.Send(req)
if err != nil {
	switch {
	case emailit.IsRateLimitError(err):
		// 429: back off and retry
	case emailit.IsAuthenticationError(err):
		// 401: the API key is missing or invalid
	case emailit.IsUnprocessableEntityError(err):
		// 422: for example, the from domain isn't verified
	}

	var apiErr *emailit.APIError
	if errors.As(err, &apiErr) {
		log.Printf("emailit: %d %s", apiErr.StatusCode, apiErr.Message)
	}
	return err
}

Enviar por SMTP

El paquete net/smtp de la biblioteca estándar puede enviar a través del relay de Emailit. smtp.SendMail actualiza la conexión con STARTTLS antes de autenticarse:

smtp.go
package main

import (
	"log"
	"net/smtp"
	"os"
	"strings"
)

func main() {
	auth := smtp.PlainAuth("", "emailit", os.Getenv("EMAILIT_API_KEY"), "smtp.emailit.com")

	msg := strings.Join([]string{
		"From: Acme <hello@acme.com>",
		"To: ada@example.com",
		"Subject: Your login code",
		"MIME-Version: 1.0",
		"Content-Type: text/plain; charset=UTF-8",
		"",
		"Your code is 482913. It expires in 10 minutes.",
	}, "\r\n")

	err := smtp.SendMail("smtp.emailit.com:587", auth, "hello@acme.com", []string{"ada@example.com"}, []byte(msg))
	if err != nil {
		log.Fatal(err)
	}
}

net/smtp es muy básico: tienes que construir el mensaje tú mismo y no admite TLS implícito en el puerto 465. Para HTML, adjuntos o reutilizar conexiones, usa una biblioteca de correo mantenida o la API. Si el puerto 587 está bloqueado, usa smtp.emailit.com:2525. Consulta Configuración SMTP.

Recibir webhooks

Crea un webhook y guarda su secreto de firma en EMAILIT_WEBHOOK_SECRET. El handler lee el cuerpo en bruto, comprueba la firma y después decodifica el array de eventos:

webhooks.go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"
)

type emailitEvent struct {
	EventID string `json:"event_id"`
	Type    string `json:"type"`
	Data    struct {
		Object map[string]any `json:"object"`
	} `json:"data"`
}

func validSignature(body []byte, signature, timestamp, secret string) bool {
	ts, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil || signature == "" {
		return false
	}
	if age := time.Now().Unix() - ts; age > 300 || age < -300 {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(timestamp + "."))
	mac.Write(body)
	expected := hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(signature))
}

func emailitWebhook(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 10<<20))
	if err != nil {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	if !validSignature(body, r.Header.Get("X-Emailit-Signature"), r.Header.Get("X-Emailit-Timestamp"), os.Getenv("EMAILIT_WEBHOOK_SECRET")) {
		http.Error(w, "invalid signature", http.StatusUnauthorized)
		return
	}

	var events []emailitEvent // up to 100 events per request
	if err := json.Unmarshal(body, &events); err != nil {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	for _, event := range events {
		if event.Type == "email.bounced" {
			// Stop emailing event.Data.Object["to"].
		}
	}

	w.WriteHeader(http.StatusOK)
}

func main() {
	http.HandleFunc("POST /webhooks/emailit", emailitWebhook)
	http.ListenAndServe(":8080", nil)
}

El patrón POST /path requiere Go 1.22 o posterior; en Go 1.21, registra /webhooks/emailit y comprueba r.Method tú mismo. Devuelve un 2xx en menos de 30 segundos; las demás respuestas se reintentan. Consulta Firma de las peticiones.

Consejos para producción

  • No superes tu límite de velocidad. Por defecto, los espacios de trabajo nuevos pueden enviar 2 emails por segundo y 5000 al día. Haz los envíos masivos desde un worker con un limitador de velocidad como golang.org/x/time/rate. Consulta Límites y cuotas.
  • Haz que los reintentos sean seguros. Si reintentas después de que se agote el tiempo de espera, envía una cabecera Idempotency-Key con net/http para que el email no se envíe dos veces. Consulta Idempotencia.
  • Elimina los webhooks duplicados guardando cada EventID que procesas.
  • Define tiempos de espera. Usa un contexto o un tiempo de espera en el cliente HTTP para que una red lenta no retenga las goroutines de las peticiones.

Próximos pasos

Adjuntos, programación y seguimiento.
Las cabeceras y cómo esperar antes de reintentar.
Todos los eventos y su payload.
Todas las bibliotecas oficiales.

¿Te ha resultado útil esta página?

Gracias por tu opinión.

Gracias. Leemos todos los mensajes.