Cómo enviar correos transaccionales desde Node.js, Python y cURL

Ejemplos para copiar y pegar: envía correo con una API REST desde Node.js, Python, PHP y la terminal, con timeouts, gestión de errores y un helper reutilizable.

Los correos transaccionales son los mensajes que envía tu producto porque un usuario ha hecho algo: registrarse, restablecer una contraseña, pagar una factura, recibir una invitación a un espacio de trabajo. Tienen que salir rápido, desde tu propio dominio y sin ralentizar la solicitud que los activó.

Este artículo muestra cómo enviarlos con la API REST de Inbixio desde los lenguajes que más usan los equipos y, después, convierte esa llamada puntual en un pequeño helper que puedes añadir a cualquier código.

La solicitud, en un solo lugar

Todos los ejemplos siguientes envían la misma solicitud HTTP:

  • Método y URL: POST https://backend.inbixio.com/api/send-email/, incluida la barra final
  • Autenticación: una cabecera X-API-Key con una clave que tenga el permiso Send (enviar)
  • Cuerpo: JSON con to_email, subject y message (HTML)

Una llamada correcta devuelve 200 con {"message": "Email queued successfully."}. Si todavía no has creado una clave ni conectado un servidor SMTP, empieza por la guía de inicio rápido de cinco minutos.

En todos los ejemplos, la clave se lee de una variable de entorno llamada INBIXIO_API_KEY. Mantenla en el servidor; nunca la incluyas en un navegador ni en una app móvil.

cURL

Útil para probar una clave, depurar desde un servidor o hacer llamadas desde un script de shell o una tarea cron:

curl -sS -X POST https://backend.inbixio.com/api/send-email/ \
  -H "X-API-Key: $INBIXIO_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 15 \
  -d '{
    "to_email": "ada@example.com",
    "subject": "Your invoice is ready",
    "message": "<p>Hi Ada, your invoice for October is ready.</p>"
  }'

-sS oculta la barra de progreso pero sigue mostrando los errores, y --max-time evita que un script se quede colgado si la red se cuelga.

Node.js

Node.js 18 y versiones posteriores incluyen fetch, así que no necesitas ninguna biblioteca HTTP:

// send-email.js
const INBIXIO_URL = 'https://backend.inbixio.com/api/send-email/';

export async function sendEmail({ to, subject, html }) {
  const res = await fetch(INBIXIO_URL, {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.INBIXIO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to_email: to, subject, message: html }),
    signal: AbortSignal.timeout(15_000),
  });

  const body = await res.json().catch(() => ({}));
  if (!res.ok) {
    const reason = body.error || body.detail || res.statusText;
    throw new Error(`Inbixio ${res.status}: ${reason}`);
  }
  return body; // { message: 'Email queued successfully.' }
}

Y su uso desde una ruta de Express:

app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await sendEmail({
    to: user.email,
    subject: 'Welcome aboard',
    html: `<h1>Welcome, ${escapeHtml(user.name)}!</h1><p>Your account is ready.</p>`,
  });
  res.status(201).json({ id: user.id });
});

Fíjate en escapeHtml. El campo message se renderiza como HTML, así que cualquier cosa que haya escrito un usuario, como su nombre, debe escaparse antes de incluirla en el cuerpo.

Python

Con requests:

# send_email.py
import os
import requests

INBIXIO_URL = "https://backend.inbixio.com/api/send-email/"

class EmailError(Exception):
    pass

def send_email(to: str, subject: str, html: str) -> dict:
    res = requests.post(
        INBIXIO_URL,
        headers={"X-API-Key": os.environ["INBIXIO_API_KEY"]},
        json={"to_email": to, "subject": subject, "message": html},
        timeout=15,
    )
    if not res.ok:
        try:
            body = res.json()
        except ValueError:
            body = {}
        reason = body.get("error") or body.get("detail") or res.reason
        raise EmailError(f"Inbixio {res.status_code}: {reason}")
    return res.json()

Pasar json= establece la cabecera Content-Type por ti. Define siempre un timeout: por defecto, requests espera indefinidamente.

En Django, llámalo después de que se confirme la transacción de la base de datos, para no enviar nunca un correo sobre una fila que se ha revertido:

from django.db import transaction

transaction.on_commit(lambda: send_email(user.email, "Welcome!", render_welcome(user)))

PHP

<?php
function send_email(string $to, string $subject, string $html): array {
    $ch = curl_init('https://backend.inbixio.com/api/send-email/');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
        CURLOPT_HTTPHEADER => [
            'X-API-Key: ' . getenv('INBIXIO_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'to_email' => $to,
            'subject'  => $subject,
            'message'  => $html,
        ]),
    ]);
    $body = json_decode(curl_exec($ch), true) ?? [];
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($status !== 200) {
        throw new RuntimeException("Inbixio $status: " . ($body['error'] ?? $body['detail'] ?? 'request failed'));
    }
    return $body;
}

Enviar a varias personas

Cada solicitud entrega a un único to_email. Para notificar a una lista de usuarios, envía una solicitud por destinatario. Es lo que quieres de todos modos en el correo transaccional: cada usuario recibe su propio mensaje, nadie ve la dirección de los demás y una dirección errónea no bloquea al resto.

Mantén una concurrencia moderada para no saturar tu propio servidor SMTP. Un pequeño grupo de 5–10 solicitudes en paralelo es más que suficiente para la mayoría de las aplicaciones.

No envíes desde la ruta de la solicitud

Llamar a la API es rápido, pero sigue siendo un viaje de ida y vuelta por la red. Para todo lo que no sea urgente, pon el envío en una tarea en segundo plano (BullMQ, Celery, Sidekiq, una cola en la nube) para que:

  • la página del usuario cargue sin esperar al correo,
  • un error temporal pueda reintentarse sin que el usuario lo note, y
  • un pico repentino, como una gran importación que activa miles de correos de bienvenida, se procese a un ritmo constante.

Con los códigos OTP y los restablecimientos de contraseña, los usuarios están esperando activamente, así que envíalos de inmediato y muestra un error amable si fallan. Cómo gestionar los errores y límites de la API de correo explica qué errores reintentar y cuáles mostrar.

Una lista de comprobación antes de publicar

  • La clave de API procede de una variable de entorno o de un gestor de secretos, y solo tiene el permiso Send.
  • Todos los valores aportados por el usuario en el HTML se escapan.
  • Las solicitudes tienen un tiempo de espera máximo de 10–15 segundos.
  • Las respuestas distintas de 200 se registran con el cuerpo de la respuesta, para no perder los mensajes de error y detail.
  • Tu dominio de envío tiene configurados SPF, DKIM y DMARC, para que los correos lleguen a la bandeja de entrada.

Envíalo con Inbixio

Conecta tu propio servidor SMTP, crea una clave de API con permisos limitados y envía desde cualquier lenguaje con una sola solicitud HTTPS.

Crear cuenta Ver precios