Transactionele e-mail versturen met Node.js, Python en cURL

Kopieerklare voorbeelden voor e-mail versturen via een REST-API vanuit Node.js, Python, PHP en de command line, met timeouts, foutafhandeling en een herbruikbare helper.

Transactionele e-mails zijn de berichten die je product verstuurt omdat een gebruiker iets heeft gedaan: zich aangemeld, een wachtwoord gereset, een factuur betaald, een uitnodiging voor een workspace gekregen. Ze moeten snel de deur uit, vanaf je eigen domein, zonder het verzoek te vertragen dat ze heeft getriggerd.

Dit artikel laat zien hoe je ze via de REST-API van Inbixio verstuurt vanuit de programmeertalen die teams het meest gebruiken, en maakt van die losse aanroep vervolgens een kleine helper die je in elke codebase kunt gebruiken.

Het verzoek op één plek

Elk voorbeeld hieronder verstuurt hetzelfde HTTP-verzoek:

  • Methode en URL: POST https://backend.inbixio.com/api/send-email/, inclusief de slash aan het eind
  • Authenticatie: een X-API-Key-header met een sleutel die het recht Send (versturen) heeft
  • Body: JSON met to_email, subject en message (HTML)

Een geslaagde aanroep geeft 200 terug met {"message": "Email queued successfully."}. Heb je nog geen sleutel aangemaakt of SMTP-server gekoppeld, begin dan met de quickstart van vijf minuten.

In alle voorbeelden wordt de sleutel gelezen uit een omgevingsvariabele met de naam INBIXIO_API_KEY. Houd hem op de server; stuur hem nooit mee naar een browser of mobiele app.

cURL

Handig om een sleutel te testen, te debuggen vanaf een server, of om aan te roepen vanuit een shellscript of cronjob:

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 verbergt de voortgangsbalk maar toont nog wel fouten, en --max-time voorkomt dat een script blijft hangen als het netwerk dat doet.

Node.js

Node.js 18 en hoger heeft fetch ingebouwd, dus je hebt geen HTTP-bibliotheek nodig:

// 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.' }
}

En zo gebruik je hem vanuit een Express-route:

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 });
});

Let op escapeHtml. Het veld message wordt als HTML weergegeven, dus alles wat een gebruiker heeft ingetypt, zoals zijn naam, moet je escapen voordat je het in de body zet.

Python

Met 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()

Met json= wordt de Content-Type-header automatisch gezet. Stel altijd een timeout in: requests wacht standaard eindeloos.

Roep de functie in Django aan nadat de databasetransactie is gecommit, zodat je nooit mailt over een rij die is teruggedraaid:

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;
}

Versturen naar meerdere mensen

Elk verzoek levert af bij één to_email. Wil je een lijst gebruikers informeren, verstuur dan één verzoek per ontvanger. Bij transactionele e-mail is dat sowieso wat je wilt: elke gebruiker krijgt een eigen bericht, niemand ziet het adres van een ander, en één fout adres houdt de rest niet tegen.

Houd het aantal gelijktijdige verzoeken bescheiden, zodat je je eigen SMTP-server niet overbelast. Een kleine pool van 5–10 parallelle verzoeken is voor de meeste apps ruim voldoende.

Verstuur niet vanuit het request-pad

De API aanroepen gaat snel, maar het blijft een netwerk-roundtrip. Zet alles wat niet tijdkritisch is in een achtergrondjob (BullMQ, Celery, Sidekiq, een cloudqueue), zodat:

  • de pagina van de gebruiker laadt zonder op e-mail te wachten,
  • een tijdelijke fout opnieuw kan worden geprobeerd zonder dat de gebruiker het merkt, en
  • een plotselinge piek, zoals een grote import die duizenden welkomstmails triggert, in een gelijkmatig tempo wordt afgewerkt.

Bij OTP-codes en wachtwoordresets zitten gebruikers actief te wachten, dus verstuur die meteen en toon een vriendelijke foutmelding als het mislukt. Fouten en limieten van de e-mail-API afhandelen legt uit welke fouten je opnieuw probeert en welke je aan de gebruiker toont.

Checklist voor livegang

  • De API-sleutel komt uit een omgevingsvariabele of secrets manager, en de sleutel heeft alleen het recht Send.
  • Elke waarde in de HTML die van een gebruiker komt, is ge-escapet.
  • Verzoeken hebben een timeout van 10–15 seconden.
  • Responses anders dan 200 worden gelogd met de response body, zodat meldingen in error en detail niet verloren gaan.
  • Je verzenddomein heeft SPF, DKIM en DMARC ingesteld, zodat de e-mails in de inbox belanden.

Verstuur het met Inbixio

Koppel je eigen SMTP-server, maak een API-sleutel met beperkte rechten aan en verstuur vanuit elke programmeertaal met één HTTPS-verzoek.

Account aanmaken Bekijk prijzen