Skip to content

Repository files navigation

Postbode API client for PHP

Official PHP client for version 2 of the Postbode.nu API: send letters, postcards and fulfillment orders, and follow them until they land on the doormat.

Full API reference: https://postbode.app/docs/api

Requirements

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

  1. Set up an account at https://postbode.nu
  2. Create an API token at https://postbode.app/gebruiker/api-tokens
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

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

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

$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

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

Every endpoint hangs off the client as a property.

Property Methods
$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

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

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

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

Examples

Runnable scripts live in examples/. Set POSTBODE_API_KEY and run one:

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

Contributing

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

License

MIT

About

Composer package for postbode.nu

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages