Siųsk laiškus iš savo sistemos
Viena HTTP užklausa su tekstu ir adresu, ir atspausdintas bei apmokėtas laiškas nukrenta į pašto dėžutę. Be spausdintuvo, be pašto ženklų, be kelionių iki pašto dėžutės.
Susikurk raktąVisa integracija
Tiek jos ir yra. Atsakyme gauni id, kuriuo skaitai būseną, ir tas pats id atsiranda tavo sąskaitos eilutėje.
curl -X POST https://sendletter.eu/api/v1/letters \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "Geachte heer, mevrouw,\n\nHierbij zeg ik mijn abonnement op.",
"subject": "Opzegging",
"sender": { "name": "Jan de Vries", "street": "Merelstraat", "number": "64",
"postalCode": "8916 AX", "city": "Leeuwarden", "country": "NL" },
"recipient": { "company": "Voorbeeld BV", "name": "Afdeling Klantenservice",
"street": "Hoofdstraat", "number": "1",
"postalCode": "1011 AA", "city": "Amsterdam", "country": "NL" },
"product": "standard",
"idempotencyKey": "opzegging-2026-07-28"
}'const res = await fetch("https://sendletter.eu/api/v1/letters", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDLETTER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text, sender, recipient, product: "registered",
// Optional. Bills a company you belong to instead of
// this key's own wallet; leave it out for a personal
// letter. One you may not spend is refused, not billed
// to you.
organizationId: company.id,
idempotencyKey: invoice.id }),
})
const letter = await res.json()
// letter.id is what you store; poll it or wait for the webhook.
// letter.billedToOrganizationId says which wallet actually paid.$ch = curl_init("https://sendletter.eu/api/v1/letters");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("SENDLETTER_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"text" => $body, "sender" => $sender, "recipient" => $recipient,
"idempotencyKey" => $invoiceId,
]),
]);
$letter = json_decode(curl_exec($ch), true);import os, requests
letter = requests.post(
"https://sendletter.eu/api/v1/letters",
headers={"Authorization": f"Bearer {os.environ['SENDLETTER_KEY']}"},
json={"text": body, "sender": sender, "recipient": recipient,
"idempotencyKey": invoice_id},
timeout=30,
).json()Raktai
Kiekviena užklausa neša API raktą kaip bearer prieigos raktą. Raktus susikuri savo paskyroje ir kiekvieną matai lygiai vieną kartą; mes saugome tik maišos reikšmę. Rakto panaikinimas įsigalioja iškart.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxPasitikrink prieš išsiuntimą
Raktas, prasidedantis sk_test_, laišką įkainoja ir suformuoja lygiai taip pat kaip tikrasis, tik sustoja tiesiai prieš spausdintuvą. Taip gali išbandyti visą integraciją, o į paštą neiškeliauja niekas.
Galiniai taškai
| POST | /api/v1/letters | Send a letter |
| GET | /api/v1/letters | List letters, newest first |
| GET | /api/v1/letters/{id} | One letter, with its timeline |
| POST | /api/quote | Price, and what that country can be sent(no key) |
| GET | /api/countries | Destinations and their address rules(no key) |
| GET | /api/v1/openapi.json | The machine readable spec(no key) |
Ne kiekviena šalis turi kiekvieną produktą. Paklausk /api/quote ir perskaityk availableProducts: į Nyderlandus, pavyzdžiui, registruotų laiškų nėra. Taip gali išjungti pasirinkimą, o ne leisti jam sugriūti pačiu siuntimo metu.
41 destinations. Registered mail: DE, BE, FR, ES, CH.
Statuses
- queued
- Accepted and waiting on the print partner.
- processing
- An exclusive hand-off to the print partner is in progress.
- attention_required
- The provider result is uncertain; held for reconciliation and never retried automatically.
- submitted
- Handed over to the printer.
- printed
- Printed and enveloped.
- posted
- Handed to the carrier. A response deadline runs from here.
- delivered
- Confirmed delivered. Registered mail only.
- failed
- Not sent. `statusDetail` says why, and the payment is returned.
Būsenos pranešimai į tavo sistemas
Laiškas per kelias dienas būseną pakeičia vos kelis kartus. Apklausinėjimas tokiam ritmui netinka: arba klausi kur kas per dažnai, arba sužinai kur kas per vėlai. Užregistruok adresą savo paskyroje, ir pranešime patys.
POST your-endpoint
X-SendLetter-Event: letter.posted
X-SendLetter-Signature: 9f86d081...
{ "type": "letter.posted",
"sentAt": "2026-07-28T18:30:00.000Z",
"letter": { "id": "AbC123", "status": "posted", "trackingCode": null, ... } }Kiekvienas pranešimas yra pasirašytas. Suskaičiuok HMAC-SHA256 nuo neapdoroto turinio su savo slaptuoju raktu ir palygink su antrašte X-SendLetter-Signature. Padaryk tai prieš turinio nuskaitymą: nuskaitymas ir pakartotinis serializavimas pakeičia baitus, ir parašas nebesutaps niekada.
import crypto from "node:crypto"
// The raw body, before any JSON parsing.
const expected = crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(rawBody, "utf8").digest("hex")
const sent = req.headers["x-sendletter-signature"]
if (expected.length !== sent.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sent))) {
return res.status(401).end()
}Klaidos
Kiekviena klaida yra tos pačios formos: kodas, pagal kurį šakoji logiką, ir pranešimas, kurį skaitai. Šakok pagal kodą, nes pranešimas gali pasikeisti.
{ "error": { "code": "no_route",
"message": "Registered mail has no route to NL",
"details": null },
"requestId": "req_018f38d7c9f34738adbe1f06a9b36f11" }Every response also carries X-Request-Id. Include that value when asking support about one call; use the letter id to reconcile the complete order.
| 401 | unauthorised | No key, a revoked key, or a malformed header. |
| 400 | invalid_request | The body failed validation. `details` names the fields. |
| 402 | insufficient_balance | The wallet is short. `details` carries required and available. |
| 403 | insufficient_scope / credential_spending_limit | The key lacks a scope or its per-letter ceiling is too low. Nothing is charged. |
| 429 | rate_limited / credential_spending_limit | A request, daily-spend or monthly-spend ceiling was reached. |
| 403 | organization_forbidden | The named organisation exists and this account is in it, but its role may not spend there. Nothing is charged, to it or to you. |
| 404 | organization_not_found | No organisation of that id that this account belongs to. Refused rather than billed to your own wallet. |
| 409 | no_route | That product is not sold to that country. Ask /api/quote first. |
| 413 | too_many_pages | Over the sheet limit for that destination. |
| 404 | not_found | No such letter on this account. |
OpenAPI
Visą specifikaciją pateikiame gyvai, tad ji negali aprašyti versijos, kuri neveikia. Nukreipk į ją generatorių ir turėsi klientą sava kalba.