gdinko/speedy
Laravel package for integrating the Speedy courier API. It simplifies shipping rate calculations, shipment creation, label generation, parcel tracking, and courier office or pickup management, helping PHP applications connect to Speedy services with less boilerplate.
Start with the README — for a 21-star wrapper, example snippets are usually the only authoritative docs. Then trace the service provider and config file to understand what's actually bound in the container.
Install:
composer require gdinko/speedy
php artisan vendor:publish --provider="Gdinko\Speedy\SpeedyServiceProvider"
Populate credentials in .env (username/password or API key — check config/speedy.php for exact keys). Speedy typically issues credentials for a test/demo environment, so point the config at that base URL first.
Your first use case should be a rate quote — it's read-only, exercises auth + HTTP client + response mapping without creating real courier orders. Something like:
$quote = Speedy::calculate([
'recipient' => ['city' => 'Sofia', 'postcode' => '1000'],
'weight' => 1.5,
'service' => 'PICKUP_TO_DOOR',
]);
If that returns data, your credentials and HTTP wiring are correct. Only then move on to creating shipments. Look first at:
config/speedy.php — env vars, endpoints, defaultsWrap it behind your own service. Don't scatter Speedy:: calls through controllers. Create App\Services\ShippingService that depends on the package class (or facade), and inject it. This isolates you from the wrapper if you ever swap to a direct API client or a competitor (Econt is the usual alternative in Bulgaria). Laravel's container makes this painless:
class ShippingService
{
public function __construct(private \Gdinko\Speedy\Speedy $speedy) {}
public function quoteForOrder(Order $order): array
{
return $this->speedy->calculate([
'weight' => $order->total_weight_kg,
'recipient' => $order->shipping_address->toSpeedyPayload(),
]);
}
}
Treat creation and tracking as separate workflows. Shipment creation returns an identifier/waybill that you persist on your order model. Tracking is a periodic poll — a scheduled job that loops over orders with a non-terminal status, calls tracking, and updates state. Don't try to inline tracking into a request/response cycle; courier APIs are slow and flaky.
Normalize addresses once. Speedy APIs are picky about city IDs vs. city names, postcode formats, and street/site codes. Add a mapper that converts your internal address model into the shape Speedy expects, and cache the city/site lookups (they change rarely and are usually a separate API call).
Use jobs for anything creating a shipment. Queue it with retries and a unique constraint — a partial failure can leave you uncertain whether the shipment exists. Store an idempotency key (order ID + attempt) if the API supports one; otherwise query by reference before retrying to avoid duplicates.
Log request/response pairs for anything that mutates. Courier errors are often vague; the raw payload is what their support team will ask for.
app()->make() per use.config/speedy.php or .env, run php artisan config:clear. Credential changes silently ignored after caching is the #1 "it stopped working" cause.Illuminate\Http\Client\Factory or access the underlying client. That's usually the cleanest escape hatch short of forking.composer.json, and plan for the possibility of forking — keeping the wrapper behind your ShippingService is what makes that cheap.How can I help you explore Laravel packages today?