PHPackages                             postbode/postbode-api - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. postbode/postbode-api

ActiveLibrary[API Development](/categories/api)

postbode/postbode-api
=====================

Official supported client for Postbode.nu (https://postbode.nu)

2.1.2(5y ago)438.9k↓54.6%2MITPHPPHP ^7.0 | ^8.0CI failing

Since Feb 9Pushed 2w ago2 watchersCompare

[ Source](https://github.com/postbode/postbode-api)[ Packagist](https://packagist.org/packages/postbode/postbode-api)[ RSS](/packages/postbode-postbode-api/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (5)Dependencies (2)Versions (10)Used By (0)

Postbode API client for PHP
===========================

[](#postbode-api-client-for-php)

Official PHP client for version 2 of the [Postbode.nu](https://postbode.nu) API: send letters, postcards and fulfillment orders, and follow them until they land on the doormat.

Full API reference:

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- A [PSR-18 HTTP client](https://packagist.org/providers/psr/http-client-implementation) and a [PSR-17 factory](https://packagist.org/providers/psr/http-factory-implementation)

Installation
------------

[](#installation)

```
composer require postbode/postbode-api
```

If your project does not have an HTTP client yet, add one — Guzzle is the usual choice and needs no further configuration:

```
composer require guzzlehttp/guzzle
```

Already using Symfony's HttpClient, or a client of your own? Pass it in and skip the extra dependency.

Getting started
---------------

[](#getting-started)

1. Set up an account at
2. Create an API token at

```
use Postbode\PostbodeApiClient;

$postbode = new PostbodeApiClient('your-api-key');

foreach ($postbode->mailboxes->list() as $mailbox) {
    printf("%s (%s): € %.2f available\n", $mailbox->name, $mailbox->customerCode, $mailbox->balance->available);
}
```

To use a specific HTTP client rather than whichever one is discovered:

```
$postbode = new PostbodeApiClient('your-api-key', new GuzzleHttp\Client(['timeout' => 30]));
```

Sending a letter
----------------

[](#sending-a-letter)

Build the request, hand it to the API, and you get a fully typed item back.

```
use Postbode\Enum\PostalPlex;
use Postbode\Enum\PostalPrinting;
use Postbode\Enum\ShippingType;
use Postbode\Request\PostalRequest;

$request = PostalRequest::make('PSBD', $envelopeUuid)
    ->addDocumentFromFile(__DIR__ . '/invoice.pdf')
    ->shipping(ShippingType::NL_FAST)
    ->printing(PostalPrinting::COLOR)
    ->plex(PostalPlex::DUPLEX)
    ->customerReference('INV-1234')
    ->metadata(['invoice_id' => 1234])
    ->send();

$postal = $postbode->postals->create($request);

echo $postal->reference;                 // PSBD-000123
echo $postal->status->name;              // Sent
echo $postal->financial->price->amountInclVat;
```

The client base64-encodes your PDFs, so never do that yourself. `addDocumentFromFile()` reads them off disk, `addDocumentFromContents()` takes bytes you already have in memory.

Every builder method returns a new instance, so a partly configured request is safe to keep as a template:

```
$template = PostalRequest::make('PSBD', $envelopeUuid)->shipping(ShippingType::NL_SLOW);

foreach ($invoices as $invoice) {
    $postbode->postals->create(
        $template->addDocumentFromFile($invoice->path)->customerReference($invoice->number)->send(),
    );
}
```

### Creating a concept first

[](#creating-a-concept-first)

Leave out `->send()`, or pass `->send(false)`, to create the item without shipping it. Check it, then release it:

```
$postal = $postbode->postals->create($request->send(false));

file_put_contents('preview.pdf', $postbode->postals->document($postal->uuid));

$postbode->postals->send($postal->uuid);   // or ->cancel($postal->uuid)
```

### Checking the price up front

[](#checking-the-price-up-front)

```
$calculation = $postbode->postals->calculate(
    envelope: $envelopeUuid,
    pages: 3,
    mailbox: 'PSBD',
    shipping: ShippingType::NL_FAST,
);

echo $calculation->totalInVat;

foreach ($calculation->elements as $element) {
    printf("%-30s € %.2f\n", $element->description, $element->price);
}
```

Nothing is created and nothing is charged by asking.

Following an item
-----------------

[](#following-an-item)

```
use Postbode\Enum\PostalStatus;

$postal = $postbode->postals->get($uuid);

if ($postal->status->isDelivered()) {
    echo 'Delivered';
}

if ($postal->tracking->isAvailable()) {
    echo $postal->tracking->url;
}

foreach ($postbode->postals->list('PSBD', limit: 25, status: PostalStatus::IN_TRANSIT) as $item) {
    echo $item->reference, ': ', $item->status->name, PHP_EOL;
}
```

Your recipients can look an item up themselves with just its reference and their own postal code:

```
$tracked = $postbode->tracking->track($reference, 'NL', '1234AB');
echo $tracked->status;
```

Endpoints
---------

[](#endpoints)

Every endpoint hangs off the client as a property.

PropertyMethods`$postbode->mailboxes``list()`, `get()`, `create()``$postbode->postals``list()`, `get()`, `create()`, `delete()`, `calculate()`, `send()`, `cancel()`, `performAction()`, `proof()`, `document()`, `logs()`, `findByV1Id()``$postbode->envelopes``listForMailbox()`, `get()`, `create()`, `delete()`, `pdf()`, `windowPreview()``$postbode->products``listForMailbox()`, `get()``$postbode->tags``listForMailbox()`, `get()`, `create()``$postbode->paperTypes``list()``$postbode->address``validate()``$postbode->fulfillment``create()``$postbode->tracking``track()`Every endpoint that takes a request builder also accepts a plain array, if you would rather build the payload yourself:

```
$postbode->postals->create([
    'mailbox' => 'PSBD',
    'envelope' => $envelopeUuid,
    'documents' => [['filename' => 'invoice.pdf', 'content' => base64_encode($pdf)]],
]);
```

Enums
-----

[](#enums)

Statuses, shipping methods and printing options are backed enums, each with a `label()` describing it in the same words the API documentation uses.

```
use Postbode\Enum\PostalStatus;
use Postbode\Enum\ShippingType;

PostalStatus::DELIVERED->value;           // 150
PostalStatus::DELIVERED->label();         // 'Delivered'
PostalStatus::DELIVERED->isFinal();       // true
ShippingType::NL_REGISTERED->isTracked(); // true
```

Available: `PostalStatus`, `PostalType`, `PostalAction`, `ShippingType`, `PostalPrinting`, `PostalPlex`, `EnvelopeStatus`, `FulfillmentOrderStatus`, `TransactionType`, `TagColor`.

Statuses on a resource keep both the typed enum and the raw value, so a status code introduced after this release still decodes:

```
$postal->status->code;     // PostalStatus|null — null if the API added a code we do not know
$postal->status->rawCode;  // int — always what the API actually sent
$postal->status->name;     // string — the API's own description
```

Error handling
--------------

[](#error-handling)

Failed calls throw; they never return a status code. Everything derives from `PostbodeException`, so one catch covers the lot.

```
use Postbode\Exception\AuthenticationException;
use Postbode\Exception\NotFoundException;
use Postbode\Exception\PostbodeException;
use Postbode\Exception\TransportException;
use Postbode\Exception\ValidationException;

try {
    $postbode->postals->create($request);
} catch (ValidationException $e) {
    foreach ($e->getErrors() as $field => $messages) {
        echo $field, ': ', implode(' ', $messages), PHP_EOL;
    }
} catch (AuthenticationException $e) {
    // 401 or 403 — bad key, or no access to this mailbox
} catch (NotFoundException $e) {
    // 404 — the envelope, mailbox or item does not exist
} catch (TransportException $e) {
    // the API could not be reached at all
} catch (PostbodeException $e) {
    // anything else, including 400 and 5xx
    echo $e->getMessage();
}
```

Upgrading from 1.x and 2.x
--------------------------

[](#upgrading-from-1x-and-2x)

Version 3 is a rewrite against the v2 API and shares no method names with earlier releases. See [UPGRADING.md](UPGRADING.md) for the full mapping, including how to translate the letter IDs you already stored into v2 UUIDs.

Examples
--------

[](#examples)

Runnable scripts live in [examples/](examples). Set `POSTBODE_API_KEY` and run one:

```
POSTBODE_API_KEY=your-key php examples/list-mailboxes.php
```

Contributing
------------

[](#contributing)

```
composer install
composer test      # phpunit, no network access required
composer format    # php-cs-fixer
```

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance63

Regular maintenance activity

Popularity34

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 61.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~87 days

Recently: every ~157 days

Total

9

Last Release

2049d ago

Major Versions

1.0.5 → 2.0.02020-06-28

PHP version history (3 changes)1.0.0PHP &gt;=7.0

2.1.0PHP ~7.0

2.1.2PHP ^7.0 | ^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/67b74c6f8cf3c9512fc139dd35ffa036a923ec9d42bcde167951877ee3c7ff33?d=identicon)[markhameetman](/maintainers/markhameetman)

---

Top Contributors

[![markhameetman](https://avatars.githubusercontent.com/u/364344?v=4)](https://github.com/markhameetman "markhameetman (13 commits)")[![DanielGSoftware](https://avatars.githubusercontent.com/u/49187378?v=4)](https://github.com/DanielGSoftware "DanielGSoftware (7 commits)")[![lvdhoorn](https://avatars.githubusercontent.com/u/22305189?v=4)](https://github.com/lvdhoorn "lvdhoorn (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/postbode-postbode-api/health.svg)

```
[![Health](https://phpackages.com/badges/postbode-postbode-api/health.svg)](https://phpackages.com/packages/postbode-postbode-api)
```

###  Alternatives

[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3661.3M49](/packages/tencentcloud-tencentcloud-sdk-php)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

353.6k](/packages/eslazarev-wildberries-sdk)[neuron-core/neuron-ai

The PHP Agentic Framework.

2.0k832.6k54](/packages/neuron-core-neuron-ai)[files.com/files-php-sdk

Files.com PHP SDK

2482.9k](/packages/filescom-files-php-sdk)[volcengine/volcengine-php-sdk

119.5k](/packages/volcengine-volcengine-php-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
