1 · Grundlagen
Mit der API lässt sich der Postkartenversand direkt in eigene Anwendungen, Shops, Backends oder interne Prozesse integrieren. Die API übernimmt Vorschau, Einzel- und Batchversand. Gestaltung und Pflege der Templates sowie die Verwaltung erzeugter Aufträge erfolgen weiterhin in der Heymail Webanwendung.
-
Templates gestalten
In der Heymail-App werden Vorder- und Rückseite der Postkarte gestaltet. Platzhalter für variable Inhalte werden direkt im Template hinterlegt, z. B.
{{couponCode}}oder{{firstName}}. Nach dem Speichern ist das Template über seinetemplateIdin der API referenzierbar. -
API-Requests senden
Für Vorschauen steht
POST /v1/mailings/previewzur Verfügung. Für den Versand wirdPOST /v1/mailings/sendverwendet — egal ob eine einzelne Postkarte oder ein Mailing an viele Empfänger;mailItemsist immer ein Array. Jedes Item kombiniert ein Anschreiben ausrecipient(Adresse) und optionalvariableData(Template-Variablen) als Geschwister. -
Aufträge nachverfolgen
API-basierte Aufträge sind in der Heymail-App unter Mailings sichtbar. Dort lassen sich unter anderem Status, Empfänger und das verwendete Template nachvollziehen.
Requests mit einem live-Key erzeugen produktive Aufträge. Requests mit einem test-Key erzeugen keine abrechenbaren Sendungen.
2 · Quickstart
Der Einstieg läuft typischerweise in vier Schritten:
-
API-Zugang anlegen
Für Entwicklung und Tests einen Key mit Umgebung
testanlegen. Der Key wird nur einmal vollständig angezeigt und sollte direkt sicher gespeichert werden. -
Template anlegen
Das Template in der Heymail-App erstellen, speichern und die
templateIdkopieren. Diese ID wird in allen API-Requests verwendet. -
Preview generieren
Vor dem ersten Versand empfiehlt sich ein Preview-Request. Damit lässt sich prüfen, ob Template und variable Daten korrekt zusammenspielen.
curl -X POST https://api.heymail.com/v1/mailings/preview \ -H "Authorization: Bearer hey-test-..." \ -H "Content-Type: application/json" \ -d '{ "templateId": "DEINE_TEMPLATE_ID", "mailItem": { "recipient": { "lastName": "Mustermann", "street": "Testweg", "houseNumber": "5", "zip": "50667", "city": "Köln" }, "variableData": { "couponCode": "SPRING-2026" } } }' -
Testauftrag auslösen
Mit einem test-Key einen ersten Versand auslösen. Bei Erfolg liefert die API
201 Createdund eineorderId. Der Auftrag ist anschließend unter Mailings sichtbar.curl -X POST https://api.heymail.com/v1/mailings/send \ -H "Authorization: Bearer hey-test-..." \ -H "Content-Type: application/json" \ -d '{ "templateId": "DEINE_TEMPLATE_ID", "mailItems": [ { "recipient": { "salutation": "Herr", "firstName": "Max", "lastName": "Mustermann", "street": "Testweg", "houseNumber": "5", "zip": "50667", "city": "Köln", "country": "Deutschland" }, "variableData": { "couponCode": "SPRING-2026" } } ] }'
Basis-URL:
https://api.heymail.com/v1
3 · Authentifizierung
Jeder Request wird mit einem persönlichen API-Key als Bearer-Token authentifiziert.
Der Key wird im Authorization-Header übertragen:
Authorization: Bearer hey-live-abc...
| Test-Key | hey-test-xxx — für Entwicklung, CI und Integrationstests. Es wird kein produktiver Auftrag erzeugt. |
|---|---|
| Live-Key | hey-live-xxx — für produktive Requests. Erfolgreiche Requests erzeugen echte Aufträge und Sendungen. |
| Scopes (Default) | mailings:send und mailings:preview sind standardmäßig aktiv. |
| Voraussetzung | Der Account des API-Keys muss verifiziert und für die Public API freigeschaltet sein. Andernfalls antwortet die API mit 403 AUTHORIZATION. |
4 · Endpoint: Preview
POST/v1/mailings/preview
Erzeugt eine kurz gültige Preview-URL für die Postkarte mit den übergebenen Daten.
Der Request erzeugt keinen Auftrag, keinen Druck und keinen Versand. Symmetrisch zu
/send: gleiche templateId/sender-Felder, gleiche
MailItem-Struktur — nur statt mailItems[] ein einzelnes mailItem.
{
"templateId": "22222222-2222-4222-8222-222222222222",
"mailItem": {
"recipient": {
"lastName": "Mustermann",
"street": "Testweg",
"zip": "50667",
"city": "Köln"
},
"variableData": {
"couponCode": "SPRING-2026"
}
}
}
{
"previewUrl": "https://..."
}
Preview-URLs sind nur kurz gültig und sollten nicht dauerhaft gespeichert werden. Bei Bedarf kann jederzeit eine neue Vorschau angefordert werden.
5 · Endpoint: Send
POST/v1/mailings/send
Erzeugt einen Auftrag für ein Mailing an einen oder viele Empfänger.
Egal ob 1 oder 10000 Anschreiben — mailItems ist immer ein Array,
und der Request erzeugt genau einen Auftrag. Jedes Item kombiniert ein Anschreiben aus
recipient (Adresse) und optional variableData (Template-Variablen)
als Geschwister. Wenn ein einzelner Eintrag ungültig ist, wird der gesamte Request abgelehnt.
{
"templateId": "22222222-2222-4222-8222-222222222222",
"shippingDate": "2026-04-20",
"mailItems": [
{
"recipient": {
"salutation": "Herr",
"firstName": "Max",
"lastName": "Mustermann",
"street": "Testweg",
"houseNumber": "5",
"zip": "50667",
"city": "Köln",
"country": "Deutschland"
},
"variableData": {
"couponCode": "SPRING-2026"
}
}
],
"sender": {
"company": "Heymail",
"street": "Beispielweg",
"houseNumber": "1",
"zip": "50667",
"city": "Köln"
}
}
{
"templateId": "22222222-2222-4222-8222-222222222222",
"name": "Spring campaign",
"mailItems": [
{
"recipient": {
"lastName": "Mustermann",
"street": "Testweg",
"zip": "50667",
"city": "Köln"
},
"variableData": { "couponCode": "SPRING-2026" }
},
{
"recipient": {
"company": "ACME GmbH",
"street": "Business Park 1",
"zip": "10115",
"city": "Berlin"
},
"variableData": { "couponCode": "SPRING-2026" }
}
]
}
{
"orderId": "77777777-7777-4777-8777-777777777777",
"status": "ACCEPTED",
"recipientCount": 2
}
| Pflichtfelder | templateId, mailItems (Array, 1–10000 Einträge); pro Item recipient mit street, zip, city sowie lastName oder company |
|---|---|
| Optional | sender (sonst Default-Sender), shippingDate (sonst nächster gültiger Versandtag), name (interner Name des Mailings), mailItems[i].variableData (Template-Variablen pro Anschreiben) |
| Wichtig | mailItems ist auf maximal 10000 Einträge begrenzt. Ein ungültiger Eintrag macht den gesamten Request ungültig. |
6 · Sender und Default-Sender
Jeder Versandauftrag benötigt einen Absender. Dafür gibt es zwei Möglichkeiten:
- Sender pro Request mitsenden — das
sender-Objekt wird direkt aus dem Request übernommen. - Default-Sender verwenden — wenn
senderfehlt, verwendet die API automatisch den im Account hinterlegten Default-Sender. - Wenn weder
sendergesetzt noch ein Default-Sender hinterlegt ist, antwortet die API mit422 VALIDATION. - Für den Absender gelten dieselben Pflichtfelder wie für Empfänger:
street,zip,citysowie entwedercompanyoderfirstNameundlastName. - Der Default-Sender kann im Account unter
Einstellungen > Absendergepflegt werden.
Der Absender wird nicht auf Ihre Postkarte gedruckt und nur fuer die postalische Einlieferung benoetigt.
7 · Template-Variablen (variableData)
Templates können Platzhalter für dynamische Inhalte enthalten. Die zugehörigen
Werte werden pro Anschreiben unter mailItems[].variableData als Geschwister neben recipient übergeben:
"mailItems": [
{
"recipient": {
"lastName": "Mustermann",
"street": "Testweg",
"zip": "50667",
"city": "Köln"
},
"variableData": {
"couponCode": "SPRING-2026",
"postTitle": "Fruehlingsaktion"
}
}
]
- Maximal 2048 Zeichen pro Wert (Bilder werden als URL uebergeben). Die Gesamtgröße des Requests bleibt durch das Payload-Limit von 2 MB begrenzt.
- Nur Variablen, die im Template hinterlegt sind, sollten übergeben werden. Zusätzliche Keys haben aktuell keine Wirkung.
variableDataist Geschwister vonrecipientinnerhalb eines Items — nicht in dasrecipient-Objekt schachteln und nicht auf Top-Level setzen.
8 · Fehlermodell
Alle Fehlerantworten verwenden dasselbe Schema. Neben einer lesbaren
message liefert die API immer eine requestId,
damit sich Anfragen und Fehler eindeutig zuordnen lassen:
{
"type": "VALIDATION",
"message": "shippingDate is invalid.",
"requestId": "6da3e10d-3b2f-4f1a-9a7c-4b8f0d1e2c3a",
"errors": [
{
"field": "shippingDate",
"code": "RESTRICTED_DAY",
"message": "shippingDate is not available for shipping."
}
]
}
| Status | Type | Typische Ursache |
|---|---|---|
| 400 | VALIDATION | Ungültiges Request-Format, Pflichtfeld fehlt, Malformed JSON oder nicht erlaubtes Top-Level-Feld |
| 401 | AUTHENTICATION | Kein oder ungültiger API-Key, widerrufener Key |
| 403 | AUTHORIZATION | Account unverifiziert, Public API nicht freigeschaltet, fehlender Scope oder Origin geblockt (CORS) |
| 413 | PAYLOAD_TOO_LARGE | Request-Body > 2 MB |
| 415 | UNSUPPORTED_MEDIA_TYPE | Content-Type nicht application/json |
| 422 | VALIDATION | Fachliche Validierung (Sender fehlt, shippingDate, Checkout) |
| 429 | RATE_LIMIT | Rate-Limit überschritten |
| 500 | INTERNAL | Serverfehler (keine Details nach außen) |
Feld-Fehlercodes (errors[].code)
Bei Validierungsfehlern enthält errors pro betroffenem Feld einen
maschinenlesbaren code. Clients sollten diesen Code auswerten,
statt nur die message zu parsen:
UNKNOWN_FIELD— ein nicht erlaubtes Feld wurde mitgeschickt.index— verweist auf das fehlerhafte Item immailItems-Array (0-basiert). Diefield-Pfaderecipient.<feld>bzw.variableData.<key>machen sichtbar, ob die Adresse oder die Template-Variablen das Problem sind.INVALID_FORMAT— das Feld hat das falsche Format, z. B.shippingDatenicht alsYYYY-MM-DD.BEFORE_NEXT_SHIPPING_DATE—shippingDateliegt vor dem nächsten möglichen Versandtag.RESTRICTED_DAY— der gewählte Tag ist ein Wochenende oder Feiertag und damit nicht versandfähig.OUT_OF_RANGE—shippingDateliegt mehr als ein Jahr in der Zukunft.
9 · Limits
Die folgenden Limits gelten für alle Requests. Bei Überschreitung antwortet die API mit dem passenden HTTP-Status und dem dokumentierten Fehlerobjekt:
Rate-Limit /send | 5 Requests pro Minute und Key (ein Request darf bis zu 10000 Empfänger enthalten) |
|---|---|
Rate-Limit /preview | 30 Requests pro Minute und Key |
| Rate-Limit-Header | X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset |
| Max. Request-Body | 2 MB (> 2 MB → 413) |
| Max. Empfängeranzahl | 10000 Empfänger pro send-Request |
| Max. Wertlänge | 2048 Zeichen pro variableData-Value |
| Max. Adressfeld-Länge | 256 Zeichen pro Feld |
| Request-ID-Header | X-Request-ID (eingehend optional, ausgehend immer gesetzt) |
10 · curl-Beispiele
Die folgenden Beispiele können direkt als Ausgangspunkt verwendet werden.
Dazu den Test-Key und die templateId durch reale Werte aus dem
eigenen Account ersetzen:
Preview erzeugen
curl -X POST https://api.heymail.com/v1/mailings/preview \
-H "Authorization: Bearer hey-test-abc..." \
-H "Content-Type: application/json" \
-d '{
"templateId": "22222222-2222-4222-8222-222222222222",
"mailItem": {
"recipient": {
"lastName": "Mustermann",
"street": "Testweg",
"zip": "50667",
"city": "Köln"
},
"variableData": {
"couponCode": "SPRING-2026"
}
}
}'
Postkarte versenden (Test-Key)
curl -X POST https://api.heymail.com/v1/mailings/send \
-H "Authorization: Bearer hey-test-abc..." \
-H "Content-Type: application/json" \
-d '{
"templateId": "22222222-2222-4222-8222-222222222222",
"mailItems": [
{
"recipient": {
"salutation": "Herr",
"firstName": "Max",
"lastName": "Mustermann",
"street": "Testweg",
"houseNumber": "5",
"zip": "50667",
"city": "Köln",
"country": "Deutschland"
},
"variableData": {
"couponCode": "SPRING-2026"
}
}
]
}'
Mailing an viele Anschreiben versenden
curl -X POST https://api.heymail.com/v1/mailings/send \
-H "Authorization: Bearer hey-test-abc..." \
-H "Content-Type: application/json" \
-d '{
"templateId": "22222222-2222-4222-8222-222222222222",
"mailItems": [
{
"recipient": {
"lastName": "Mustermann",
"street": "Testweg",
"zip": "50667",
"city": "Köln"
},
"variableData": { "couponCode": "SPRING-2026" }
},
{
"recipient": {
"company": "ACME GmbH",
"street": "Business Park 1",
"zip": "10115",
"city": "Berlin"
},
"variableData": { "couponCode": "SPRING-2026" }
}
]
}'
Fehler debuggen über X-Request-ID
curl -i -X POST https://api.heymail.com/v1/mailings/send \
-H "Authorization: Bearer hey-test-abc..." \
-H "Content-Type: application/json" \
-H "X-Request-ID: $(uuidgen)" \
-d '{ "templateId": "..." }'