Docs durchsuchen

Guides und API-Endpunkte durchsuchen

API-Dokumentation

Public API · docs.newsletterkit.ventureon.io · v1 · Newsletter-Middleware zu Postmark

01

Einführung

Die NewsletterKit API ist eine interne Middleware zwischen Kundenplattformen und Postmark: dein Portal schickt einfache, immer gleiche Requests — wir rendern die Mail aus MJML-Blöcken im Design des Kunden und übergeben den Versand an Postmark. Verschickt wird immer ein Template: transaktional einzeln und synchron, Newsletter als Versandjob. Jede Mail bekommt eine ID von uns; Auswertung und Sperren liegen hier, das Portal fragt ab. Empfängeradressen werden nach dem Versand gelöscht.

Base URL
https://api.newsletterkit.ventureon.io/v1
Ablauf
Blöcke → Template → Vorschau → Versand → Mail-ID
KI-optimiert. Konsistente Benennung, eine OpenAPI-Spec als Single Source of Truth, plus Docs als gebündeltes Markdown (llms-full.txt mit den fünf Abläufen, dem Einwilligungs-Modell und der Referenz) — damit Coding-KIs (Claude Code, Cursor) ein Portal anbinden, ohne nachzufragen.

Maschinenlesbar

02

Quickstart · die fünf Abläufe

Ein schmaler Wrapper, dann fünf Abläufe, die jedes Kundenportal braucht: transaktionale Mail, Newsletter aus fertigem Template, Newsletter aus dem Builder, Sperren ins CRM, Auswertungsseite. Beispiele sind auf Next.js (TypeScript, fetch) ausgelegt — der API-Key lebt ausschließlich serverseitig.

0 · API-Key hinterlegen und Verbindung prüfen

.env.local

// .env.local
NEWSLETTERKIT_API_KEY=nk_live_xxxxxxxxxxxxxxxx

lib/newsletterkit.ts

// lib/newsletterkit.ts — schmaler Server-Wrapper (nur serverseitig nutzen)
const BASE = "https://api.newsletterkit.ventureon.io/v1";

export async function newsletterkit<T>(
  path: string,
  init: RequestInit = {},
): Promise<T> {
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.NEWSLETTERKIT_API_KEY}`,
      "Content-Type": "application/json",
      ...init.headers,
    },
  });
  if (!res.ok) throw new Error(`NewsletterKit ${res.status}: ${await res.text()}`);
  return res.json() as Promise<T>;
}

Route Handler

// app/api/check/route.ts  (Next.js Route Handler)
import { newsletterkit } from "@/lib/newsletterkit";

export async function GET() {
  const health = await newsletterkit("/health");
  return Response.json(health); // { data: { status: "ok", api_version: "v1" }, … }
}

1 · Transaktionale Mail

Ein fertiges Template des Kunden, ein Empfänger, sofort. Die Antwort trägt unsere Mail-ID — sie gehört an die Bestellung oder den Nutzer, damit das Portal später den Status nachfragen kann.

  1. GET /v1/templates — Template der Art transaktional wählen, params lesen (name, label, type, required, example).
  2. POST /v1/templates/{id}/send mit to und params; Idempotency-Key gegen doppelten Versand bei Retries.
  3. Bei Bedarf GET /v1/messages/{id}: zugestellt oder gebounct, mit Grund.

Transaktionale Mail · Next.js

// Server Action / Route Handler
import { newsletterkit } from "@/lib/newsletterkit";

const { data } = await newsletterkit<{ data: { message_id: string; status: string } }>(
  "/templates/00000000-0000-4000-8000-0000000000f3/send",
  {
    method: "POST",
    headers: { "Idempotency-Key": crypto.randomUUID() },
    body: JSON.stringify({
      to: "mira@example.com",
      params: {
        titel: "Dein Zugang zum Dashboard",
        name: "Mira",
        text: "Du bist als Redaktion eingetragen.",
        link: "https://kunde.de/einladung?token=…",
        screenshot: "https://kunde.de/bilder/dashboard.jpg",
      },
    }),
  },
);
// data.status: accepted | suppressed | invalid — data.message_id am Nutzer speichern
await db.user.update({ where: { id: userId }, data: { inviteMailId: data.message_id } });

2 · Newsletter aus fertigem Template

Das Portal zeigt die Newsletter-Templates des Kunden, der Nutzer füllt die Parameter (eine KI kann sie aus Bezeichnung und Beispiel vorausfüllen), wählt Empfänger aus dem CRM und schickt ab. Verschickt wird als Job.

  1. GET /v1/templates — Art newsletter, params lesen.
  2. POST /v1/render/preview zum Prüfen — oder GET /v1/templates/{id} und die Blöcke mit den Werten rendern lassen.
  3. POST /v1/send-jobs mit template_id, params, optional scheduled_at.
  4. POST /v1/send-jobs/{id}/recipients — Kontakte aus dem CRM, gefiltert nach Einwilligung UND keiner Sperre, in Blöcken bis 5.000, je Empfänger data für {{platzhalter}}.
  5. POST /v1/send-jobs/{id}/start, danach GET /v1/send-jobs/{id} alle paar Sekunden, bis status completed ist.

Newsletter aus fertigem Template · Next.js

import { newsletterkit } from "@/lib/newsletterkit";

// 1) Job anlegen
const job = await newsletterkit<{ data: { id: string } }>("/send-jobs", {
  method: "POST",
  headers: { "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ template_id: templateId, params: { titel: "Sommerprogramm" } }),
});

// 2) Empfänger aus dem CRM: Einwilligung UND keine Sperre — nie nur eins davon
const contacts = await db.contact.findMany({
  where: { marketingConsent: true, emailSuppressed: false },
  select: { id: true, email: true, firstName: true },
});
for (let i = 0; i < contacts.length; i += 5000) {
  const chunk = contacts.slice(i, i + 5000);
  const res = await newsletterkit<{ data: { recipients: { email: string; message_id: string }[] } }>(
    `/send-jobs/${job.data.id}/recipients`,
    {
      method: "POST",
      headers: { "Idempotency-Key": `${job.data.id}:${i}` },
      body: JSON.stringify({
        recipients: chunk.map((c) => ({ email: c.email, data: { vorname: c.firstName } })),
      }),
    },
  );
  // Mail-IDs an die Kontakte schreiben — der Schlüssel für Status und Auswertung
  for (const r of res.data.recipients) await db.newsletterMail.create({ data: { contactEmail: r.email, messageId: r.message_id, jobId: job.data.id } });
}

// 3) Starten und abwarten
await newsletterkit(`/send-jobs/${job.data.id}/start`, { method: "POST" });

3 · Newsletter aus dem Builder

Der Nutzer stapelt Blöcke im Canvas des Portals und füllt sie mit Inhalt. Das Portal rendert nie selbst: Jede Vorschau und jede Kachel kommt fertig aus NewsletterKit, mit den Design-Regeln des Kunden dieses Moments. Am Ende wird der Stapel als eigenes Template gespeichert und wie ein fertiges verschickt.

  1. GET /v1/blocks — Blöcke mit Parameter-Schema und tile_url; Kacheln über GET /v1/blocks/{id}/tile in iframes mit srcdoc.
  2. Bei jeder Änderung (entprellt, ~300 ms): POST /v1/render/preview mit dem Stapel — HTML in ein iframe.
  3. Bilder: POST /v1/assets → PUT an upload.url → POST /v1/assets/{id}/complete; die url in Bild-Parameter setzen.
  4. POST /v1/templates speichert den Stapel als Template (Art newsletter; kein Footer im Stapel, der kommt aus dem Rahmen des Kunden).
  5. Weiter wie beim fertigen Template: Job anlegen, Empfänger anhängen, starten.

Newsletter aus dem Builder · Next.js

import { newsletterkit } from "@/lib/newsletterkit";

// Live-Vorschau: der Stapel des Builders, unverändert, an die API
export async function preview(blocks: { block_id: string; values: Record<string, unknown> }[]) {
  const { data } = await newsletterkit<{ data: { html: string | null; errors: string[] } }>(
    "/render/preview",
    { method: "POST", body: JSON.stringify({ blocks }) },
  );
  return data.html; // <iframe srcDoc={html} sandbox="" />
}

// Bild hochladen: drei Aufrufe hinter einer Funktion
export async function uploadAsset(file: File) {
  const created = await newsletterkit<{ data: { id: string; upload: { url: string; headers: Record<string, string> } } }>(
    "/assets",
    { method: "POST", body: JSON.stringify({ filename: file.name, content_type: file.type }) },
  );
  await fetch(created.data.upload.url, { method: "PUT", headers: created.data.upload.headers, body: file });
  const done = await newsletterkit<{ data: { url: string; width: number; height: number } }>(
    `/assets/${created.data.id}/complete`,
    { method: "POST" },
  );
  return done.data; // { url, width, height } — url in bild.url setzen
}

// Speichern als Template
const template = await newsletterkit<{ data: { id: string } }>("/templates", {
  method: "POST",
  headers: { "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({
    name: "Sommer-Newsletter",
    subject: "Hallo {{vorname}}, das Sommerprogramm ist da",
    blocks, // derselbe Stapel wie in der Vorschau
    status: "active",
  }),
});

4 · Sperren ins CRM

Die Einwilligung liegt im CRM, die Sperre bei Postmark. NewsletterKit spiegelt die Sperren; das CRM holt sie ab und leitet daraus ab, wer eine Mail bekommen darf. Ein Cron alle paar Minuten reicht — beim Versand sperrt Postmark ohnehin zuletzt.

  1. Cron: GET /v1/suppressions?since=<letzter Stand>, Seiten über paging.after; je Eintrag das Sperrfeld des Kontakts setzen (active=true) oder aufheben (active=false).
  2. Einzelfall: POST /v1/suppressions/lookup mit bis zu 500 Adressen.
  3. Nach neuem Double-Opt-in: POST /v1/suppressions/reactivate — 409 bei Spam-Beschwerde, die bleibt.

Sperren ins CRM · Next.js

// app/api/cron/suppressions/route.ts — alle 5 Minuten (Vercel Cron)
import { newsletterkit } from "@/lib/newsletterkit";

type Suppression = { email: string; active: boolean; reason?: string; changed_at: string };

export async function GET() {
  let since = (await kv.get<string>("nk:suppressions_since")) ?? "1970-01-01T00:00:00Z";
  let after: string | null = null;
  do {
    const page = await newsletterkit<{ data: Suppression[]; paging: { after: string | null; has_next: boolean } }>(
      `/suppressions?since=${since}${after ? `&after=${after}` : ""}`,
    );
    for (const s of page.data) {
      // Nur das Sperrfeld — die Einwilligung fasst der Abgleich nie an.
      await db.contact.updateMany({
        where: { email: s.email },
        data: { emailSuppressed: s.active, emailSuppressedReason: s.active ? s.reason : null },
      });
      since = s.changed_at;
    }
    after = page.paging.has_next ? page.paging.after : null;
  } while (after);
  await kv.set("nk:suppressions_since", since);
  return Response.json({ ok: true });
}

5 · Auswertungsseite

Die Auswertung liegt bei NewsletterKit, das Portal rendert nur: drei Aufrufe, fertige Zahlen. Öffnungen und Klicks laufen noch Tage nach dem Versand ein.

  1. GET /v1/send-jobs/{id} — Zähler je Ergebnis, stats (Summen) und timeline (je Stunde).
  2. GET /v1/send-jobs/{id}/messages?status=bounced — wer nicht erreicht wurde, über Mail-IDs.
  3. GET /v1/send-jobs/{id}/links — Klicks je Link.

Auswertungsseite · Next.js

import { newsletterkit } from "@/lib/newsletterkit";

export async function newsletterReport(jobId: string) {
  const [job, bounced, links] = await Promise.all([
    newsletterkit<{ data: { status: string; recipients: Record<string, number>; stats: Record<string, number>; timeline: unknown[] } }>(`/send-jobs/${jobId}`),
    newsletterkit<{ data: { message_id: string; bounce_type: string | null }[] }>(`/send-jobs/${jobId}/messages?status=bounced`),
    newsletterkit<{ data: { url: string; clicks: number; unique_clicks: number }[] }>(`/send-jobs/${jobId}/links`),
  ]);
  return { job: job.data, bounced: bounced.data, links: links.data };
}
03

Authentifizierung & Header

Jeder API-Call trägt den API-Key im Authorization-Header als Bearer-Token. Der Key bestimmt den Kunden — dahinter liegt der Postmark-Server-Token, den das Portal nie sieht. Scopes: full (lesen + schreiben/versenden) oder reporting (read-only).

Authorization-Header

Authorization: Bearer nk_live_xxxxxxxxxxxxxxxx

Server-only: Key niemals im Browser/Client-Bundle verwenden. In Next.js nur in Route Handlers, Server Actions oder Server Components. Einzige Ausnahme ohne Key: der PUT der Bilddatei an die signierte Upload-Adresse.

Standard-Header

HeaderPflichtBeschreibung
AuthorizationimmerBearer <API-Key>. Bestimmt Kunde + Plan. Niemals clientseitig im Browser verwenden.
Content-Typebei JSON-Bodyapplication/json — für alle POST/PUT-Requests mit JSON-Body.
Idempotency-Keyempfohlen (POST/PUT)Eindeutige UUID pro Erstellung/Versand. Wiederholte Requests mit gleichem Key erzeugen keine Dubletten (kein doppelter Versand).
NewsletterKit-VersionoptionalPinnt die API-Version (z. B. v1). Ohne Header gilt die Account-Default-Version.
X-Request-IdoptionalEigene Korrelations-ID; wird in Logs & Support-Anfragen zurückgegeben.

Zugang zur Doku

Menschen

SSO-Login → volle Docs

KI-Tools

llms-full.txt — Abläufe, Regeln, Referenz

OpenAPI
/docs/openapi.yaml
04

Konventionen

Gelten für alle Endpunkte: einheitlicher { data, request_id }-Envelope, Cursor-Pagination bei Listen, Rate-Limits und Idempotenz.

Pagination (Cursor)

Listen mit Cursor (Sperren, Mails eines Jobs) liefern data[] + paging.after. Den Cursor an die nächste Anfrage als after übergeben, bis has_next false ist.

Envelope

{
  "data": [ { "…": "…" } ],
  "paging": { "after": "QVFIUm...", "has_next": true }
}

// nächste Seite:
GET /v1/suppressions?since=…&after=QVFIUm...&limit=500

Rate-Limits

1.000 Anfragen je Minute und Kunde. Jede Antwort trägt X-RateLimit-*. Bei 429 die Sekundenzahl aus Retry-After abwarten (exponentielles Backoff empfohlen). Ein 429 mit quota_exceeded ist etwas anderes: eine Sicherung des Kunden (Monatslimit, Höchstzahl je Job), die ein Admin anhebt.

429-Antwort

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
Retry-After: 12

Idempotenz

Bei POST einen Idempotency-Key (UUID) mitsenden. Der erste Aufruf wird ausgeführt und seine Antwort 24 Stunden gespeichert; Wiederholungen mit gleichem Key bekommen dieselbe Antwort (Idempotent-Replayed: true) — vor allem kein doppelter Versand bei Netzwerk-Retries. Gleicher Key mit anderem Body: 409. Antwortet Postmark nicht (500 mit message_id), bleibt der Key belegt: Die Wiederholung bekommt dieselbe Antwort und sendet nicht erneut — den Ausgang zeigt GET /v1/messages/{id}.

Personalisierung

{{platzhalter}} in Betreff und festen Texten eines Templates werden je Empfänger gefüllt — aus den Job-Parametern und den data des Empfängers, der Empfänger gewinnt. Im HTML maskiert, unbekannte Platzhalter verschwinden. Gerendert wird einmal je Job, danach nur noch ersetzt.

05

Fehler

Einheitliches Fehler-Objekt mit stabilem code, lesbarer message und request_id für den Support. 4xx = dein Request, 5xx = wir/Postmark.

Fehler-Schema

{
  "error": {
    "code": "validation_error",
    "message": "Pflichtfelder fehlen: link.",
    "details": [
      { "path": "link", "message": "link ist erforderlich." }
    ]
  },
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}
StatusCodeBedeutung
400bad_requestRequest-Format ungültig (JSON-Syntax, fehlende Felder).
401unauthorizedAPI-Key fehlt, ungültig oder widerrufen.
403forbiddenKey-Scope deckt diese Operation nicht (z. B. reporting-only bei schreibenden Calls).
404not_foundRessource existiert nicht oder gehört nicht zum Kunden (falsche ID).
409conflictIdempotenz-Konflikt oder Statuskollision.
422validation_errorFelder vorhanden, aber inhaltlich ungültig (siehe details[]).
429rate_limitedZu viele Requests → Retry-After beachten.
429quota_exceededMonatslimit oder Höchstzahl je Job erreicht — eine Sicherung des Kunden, im Admin anhebbar.
5xxinternal_errorFehler bei uns oder bei Postmark. Mit Backoff erneut versuchen.
06

Zugang & Abrechnung

Es gibt keinen buchbaren Plan und kein Kontingent. Abgerechnet wird, was Postmark angenommen hat — nach einer Staffel, die in Stripe hinterlegt ist. Im Sandbox-Modus des Kunden nimmt Postmark an, stellt nichts zu und es wird nichts berechnet.

NewsletterKit meldet täglich die Zahl der angenommenen E-Mails an Stripe. Dort entsteht daraus die Rechnung — gemeinsam mit den anderen Produkten, die derselbe Kunde nutzt. Beträge, Stufen und Rechnungen stehen deshalb ausschließlich in Stripe; GET /v1/usage liefert Mengen, das Monatslimit und den Rest.

Der Zugang ist davon getrennt und ein rein administrativer Schalter. Ist er gesperrt, antworten versendende Endpunkte (/v1/templates/{id}/send, /v1/send-jobs/…/start) mit 403 forbidden, während lesende Endpunkte erreichbar bleiben. Dazu kommt die Freischaltung des Versands je Kunde: Erst wenn Postmark-Server, Broadcast-Stream, verifizierte Absender und beide Footer im Admin grün sind, geht etwas raus.

07

Webhooks

Geplant. Heute holt das Portal ab: Job-Status über GET /v1/send-jobs/{id}, Mail-Status über GET /v1/messages/{id}, Sperren über GET /v1/suppressions — idempotent, ohne Zustand bei uns. Die Zustellung ans Portal (job.completed, message.bounced, suppression.changed) kommt später als Zusatz auf dieselben Daten; die Registrierung einer URL ist schon möglich.

Signatur verifizieren (so wird es aussehen)

Header X-NewsletterKit-Signature hat die Form t=<unix>,v1=<hmac>. Der HMAC-SHA256 wird über "<t>.<roher Body>" mit deinem Webhook-Secret gebildet. Immer verifizieren, bevor du das Event verarbeitest.

Verifizierung · Next.js

// app/api/webhooks/newsletterkit/route.ts — geplant, so wird es aussehen
import crypto from "node:crypto";

export async function POST(req: Request) {
  const raw = await req.text();
  // Header-Form: t=<unix>,v1=<hmac>
  const header = req.headers.get("X-NewsletterKit-Signature") ?? "";
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", process.env.NEWSLETTERKIT_WEBHOOK_SECRET!)
    .update(`${parts.t}.${raw}`) // HMAC über "<t>.<roher Body>"
    .digest("hex");
  if (parts.v1 !== expected) return new Response("invalid signature", { status: 401 });

  const event = JSON.parse(raw); // { event: "job.completed", … }
  // … schnell 200 zurückgeben, Verarbeitung asynchron
  return new Response("ok");
}

Event-Payload

Beispiel

{
  "event": "job.completed",
  "delivery_id": "wd_2026_0001",
  "send_job_id": "f90c0326-…",
  "recipients": { "total": 12000, "accepted": 11812, "suppressed": 188 },
  "occurred_at": "2026-09-05T08:04:00Z"
}

Events werden PII-frei sein — nur IDs, Status und Aggregate, nie Empfänger-Adressen. Die Abholung bleibt der Weg der Wahl, solange ein Portal keine Echtzeit braucht.

08

Einwilligung & Sperren

Zwei Fakten, zwei Systeme: Die Werbeeinwilligung liegt im Kundensystem, die Sperre (Abmeldung, Bounce, Beschwerde) bei Postmark. NewsletterKit spiegelt die Sperren zum Abholen — und das Kundensystem leitet daraus ab, wer eine Mail bekommen darf. Das Beispiel dazu steht oben im Ablauf „Sperren ins CRM“.

  • Die Einwilligung liegt im Kundensystem. Die Werbeeinwilligung ist ein dokumentierter Akt: Double-Opt-in mit Zeitpunkt, Text und Herkunft. NewsletterKit und Postmark kennen sie nicht und fragen nie danach. Das Feld gehört dem Kundensystem, und nur das Kundensystem schreibt es.
  • Die Sperre liegt bei Postmark. Abmeldung, Hard Bounce und Spam-Beschwerde entstehen bei Postmark und werden dort durchgesetzt: Ein gesperrter Empfänger wird je Nachricht abgewiesen, egal was das Kundensystem über ihn glaubt. NewsletterKit spiegelt Sperren und gibt sie weiter; übersteuern kann sie niemand.
  • „Darf E-Mails empfangen“ ist eine Ableitung, kein Feld. Einwilligung vorhanden UND keine aktive Sperre. Im CRM sind das zwei getrennte Felder: marketing_consent schreibt nur das Kundensystem, email_suppressed schreibt nur der Abgleich mit NewsletterKit. Wer beide zu einem Boolean verschmilzt, verliert die Wiederkehr: Ein Abgemeldeter, der sich neu einträgt, hätte wieder Einwilligung, bliebe aber bei Postmark gesperrt.
  • Abgleich durch Abholen. Das Kundensystem ruft regelmäßig GET /v1/suppressions?since=<letzter Stand> und schreibt die Antwort in sein Sperrfeld — gesetzte wie aufgehobene Sperren. Ein Cron alle paar Minuten reicht: Beim Versand sperrt Postmark ohnehin zuletzt, ein verspäteter Abgleich kostet höchstens eine abgewiesene Nachricht.
  • Wiederkehr nur nach neuem Double-Opt-in. Wer sich abgemeldet hat und später neu einträgt, bleibt bei Postmark gesperrt, bis das Kundensystem POST /v1/suppressions/reactivate ruft. Das geschieht nach der Bestätigung, nie beim Setzen eines Hakens. Nach einer Spam-Beschwerde gibt es keine Reaktivierung; die Adresse bleibt dauerhaft gesperrt.
  • Der Abmeldelink kommt von Postmark. Newsletter enthalten den Abmeldelink über den Footer des Kunden; die Adresse dahinter setzt Postmark je Empfänger ein, und die Abmeldung führt auf Postmarks Abmeldeseite. Der Aufrufer übergibt keinen unsubscribe_url. Eine Abmeldung gilt für alle Newsletter dieses Kunden — Listen und Segmente kennt Postmark nicht.
  • Transaktionale Mails bleiben erreichbar. Sperren gelten je Stream. Ein abgemeldeter Empfänger bekommt weiterhin transaktionale Mails wie Double-Opt-in, Bestellbestätigung oder Passwort-Reset; gesperrt sind nur Newsletter.
  • Listen und Segmente gehören dem Kundensystem. NewsletterKit kennt keine Listen. Welche Kontakte einen Newsletter bekommen, entscheidet das Kundensystem beim Zusammenstellen der Empfänger — gefiltert nach Einwilligung und Sperre. Adressen reisen nur mit dem Versandjob und werden nach dessen Abschluss gelöscht; es bleibt der Hash und die Mail-ID.

Das Sperrfeld im CRM schreibt ausschließlich der Abgleich. Ein Haken, den jemand von Hand entfernt, ändert bei Postmark nichts: Der nächste Versand weist die Adresse trotzdem ab.

09

Versionierung

Die Version steckt im Pfad-Präfix (/v1). Innerhalb von v1 sind nur additive, nicht-brechende Änderungen erlaubt — neue Felder/Endpunkte kommen hinzu, bestehende verschwinden nicht.

v1 · stablev2 · geplant
  • Pinnen: optionaler Header NewsletterKit-Version: v1 fixiert das Verhalten unabhängig vom Account-Default.
  • Breaking Changes erscheinen nur in einer neuen Major-Version (/v2) — niemals in /v1.
  • Deprecation: abgekündigte Versionen laufen mind. 6 Monate weiter; Hinweise via Sunset-Header + Changelog.

Referenz

11 Gruppen · 31 Endpunkte

Jede Ressourcen-Gruppe hat eine eigene Seite mit allen Endpunkten, Parametern und Beispielen.

System

Verbindung & Health. Der einfachste Endpunkt — prüft API-Key und Erreichbarkeit.

1 Endpunkt

Blöcke

Liefert die für den Kunden freigeschalteten MJML-Blöcke samt Parameter-Schema. Welche Blöcke ein Template verwenden darf, kommt aus dieser Liste — referenziert wird ein Block immer über seine unveränderliche id (UUID), nicht über den slug. Blöcke selbst werden ausschließlich im Admin gepflegt.

2 Endpunkte

Design-Regeln

Liefert das vollständige, aufgelöste Design-Token-Set des Kunden (Flächen-/Text-/Akzent-/Status-Farben, Typografie, Abstände, Komponenten). Diese Tokens werden beim Render in jedes MJML-Dokument injiziert und erzwingen ein konsistentes Erscheinungsbild über alle Newsletter hinweg. Das vollständige Regelwerk (welches Token wofür) steht unter /docs/design-rules.

1 Endpunkt

Rendering

Rendert einen Block-Stapel serverseitig zu MJML + kompiliertem, responsivem HTML — die Live-Vorschau des Builders im Kundenportal, zustandslos und ohne etwas zu versenden. MJML-Templates unterstützen bedingte Abschnitte {{#param}}…{{/param}}, die nur bei nicht-leerem Wert rendern (optionale Elemente wie Logo oder AGB-Link verschwinden sonst).

1 Endpunkt

Templates

Verschickt wird immer ein Template. Es gehört dem Kunden — im Dashboard gebaut (origin admin, mit Parameter-Vertrag) oder aus dem Builder des Kundenportals gespeichert (origin customer, feste Werte, {{platzhalter}} je Empfänger). Struktur und Blöcke liegen serverseitig fest, das Aussehen kommt aus den Design-Regeln des Kunden, der Footer aus seinem Rahmen.

6 Endpunkte

Verbrauch

Zeigt die versendeten Mengen: laufender Monat, Hochrechnung, Vormonate. Keine Beträge — abgerechnet wird nach Staffel in Stripe.

1 Endpunkt

Bilder

Bilder für Newsletter: Das Portal lädt sie über NewsletterKit hoch und bekommt eine öffentliche, unveränderliche https-Adresse zurück — optimiert für Mail-Clients (höchstens 1360 px breit, JPEG oder PNG, unter 400 KB, ohne Metadaten). Zwei Schritte, weil ein Aufruf höchstens 4,5 MB annimmt: anmelden, Datei an die signierte Adresse schicken, abschließen.

5 Endpunkte

Versandjobs

Ein Newsletter geht an viele Empfänger und wird als Versandjob verschickt: anlegen (Template, Parameter, Absender, optionaler Startzeitpunkt), Empfänger in Blöcken anhängen, starten, abfragen. Ein Worker rendert einmal, schickt Batches an Postmark und hält je Empfänger das Ergebnis fest. Die Empfängerliste baut das Kundensystem aus seinem CRM — gefiltert nach Einwilligung und Sperre.

8 Endpunkte

Mails

Der Status einer einzelnen Mail. Jede Mail bekommt beim Versand eine ID von uns; das Portal schreibt sie an seine Bestellung oder seinen Kontakt und fragt hier nach — zugestellt, geöffnet, gebounct mit Grund, gesperrt. Die Adresse steht nirgends in der Antwort.

1 Endpunkt

Sperren

Der Sperrspiegel: Abmeldung, Hard Bounce und Spam-Beschwerde entstehen bei Postmark und werden dort je Nachricht durchgesetzt. Hier holt das Kundensystem die Änderungen ab, prüft Adressen und hebt Sperren nach neuem Double-Opt-in auf. Die Einwilligung selbst liegt im CRM des Kunden — „darf E-Mails empfangen“ ist die Ableitung aus beidem.

3 Endpunkte

Webhooks

Bald verfügbar

Zustellung von Ereignissen ans Kundenportal — geplant. Heute holt das Portal Job-Status (GET /v1/send-jobs/{id}), Mail-Status (GET /v1/messages/{id}) und Sperren (GET /v1/suppressions) ab; das reicht für Builder, Versand und Auswertung. Die Zustellung kommt später als Zusatz auf dieselben Daten: job.completed, message.bounced, suppression.changed. Die Registrierung einer URL ist schon möglich, die Zustellung noch nicht.

2 Endpunkte