Στείλε επιστολές από το δικό σου σύστημα
Ένα αίτημα HTTP με κείμενο και μια διεύθυνση, και μια τυπωμένη επιστολή με πληρωμένα τέλη φτάνει σε ένα γραμματοκιβώτιο. Χωρίς εκτυπωτή, χωρίς γραμματόσημα, χωρίς διαδρομή ως το ταχυδρομείο.
Φτιάξε κλειδίΟλόκληρη η ενσωμάτωση
Αυτό είναι όλο. Η απάντηση φέρνει ένα αναγνωριστικό με το οποίο διαβάζεις την κατάσταση, και το ίδιο αναγνωριστικό εμφανίζεται στη γραμμή του τιμολογίου σου.
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()Κλειδιά
Κάθε αίτημα μεταφέρει ένα κλειδί API ως bearer token. Τα κλειδιά τα φτιάχνεις στον λογαριασμό σου και βλέπεις το καθένα ακριβώς μία φορά· εμείς κρατάμε μόνο ένα hash. Η ανάκληση ενός κλειδιού ισχύει αμέσως.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxΔοκίμασέ το πριν το ταχυδρομήσεις
Ένα κλειδί που αρχίζει με sk_test_ υπολογίζει την τιμή και συνθέτει την επιστολή ακριβώς όπως ένα κανονικό, και μετά σταματά λίγο πριν από τον εκτυπωτή. Έτσι δοκιμάζεις ολόκληρη την ενσωμάτωση χωρίς να φύγει τίποτα.
Τερματικά σημεία
| 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) |
Δεν πουλάει κάθε προορισμός κάθε προϊόν. Ρώτα το /api/quote και διάβασε το availableProducts: προς τις Κάτω Χώρες, για παράδειγμα, δεν υπάρχει συστημένη. Έτσι απενεργοποιείς μια επιλογή αντί να την αφήσεις να αποτύχει τη στιγμή της αποστολής.
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.
Ειδοποιήσεις κατάστασης προς τα συστήματά σου
Μια επιστολή αλλάζει κατάσταση λίγες φορές μέσα σε αρκετές ημέρες. Η τακτική ερώτηση δεν ταιριάζει σε αυτόν τον ρυθμό: ή ρωτάς πάρα πολύ συχνά ή το μαθαίνεις πολύ αργά. Δήλωσε ένα τερματικό σημείο στον λογαριασμό σου και σου το λέμε εμείς.
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, ... } }Κάθε ειδοποίηση είναι υπογεγραμμένη. Υπολόγισε HMAC-SHA256 πάνω στο ακατέργαστο σώμα με το μυστικό σου και σύγκρινέ το με την κεφαλίδα X-SendLetter-Signature. Κάνε το πριν διαβάσεις το σώμα: η ανάλυση και η εκ νέου σειριοποίηση αλλάζουν τα bytes, και τότε η υπογραφή δεν ταιριάζει ποτέ ξανά.
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()
}Σφάλματα
Κάθε σφάλμα έχει το ίδιο σχήμα: έναν κωδικό για να διακλαδώσεις τη λογική σου και ένα μήνυμα για να το διαβάσεις. Διακλάδωσε στον κωδικό, γιατί το μήνυμα μπορεί να αλλάξει.
{ "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
Την πλήρη προδιαγραφή τη σερβίρουμε ζωντανά, οπότε δεν γίνεται να περιγράφει έκδοση που δεν τρέχει. Στρέψε πάνω της έναν generator και έχεις έτοιμο πελάτη στη δική σου γλώσσα.