Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Prontuno Laravel Laravel Package

prontuno/prontuno-laravel

Prontuno for Laravel is a lightweight package that brings Prontuno functionality into your Laravel applications. It offers straightforward Composer installation, simple configuration, and familiar Laravel conventions, making it easy to integrate and use in new or existing projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the baseline. The package targets PHP 8.3+ and Laravel 12/13 only — check composer.json before you try it in an older app. The service provider and Prontuno facade are auto-discovered, so there is nothing to register.

composer require prontuno/prontuno-laravel
php artisan vendor:publish --tag=prontuno-config

Configure from .env first, not the config file. Everything meaningful has an env key: PRONTUNO_SENDING_KEY, PRONTUNO_MANAGEMENT_KEY, base URL, timeouts, retry counts, webhook secrets and tolerance, and PRONTUNO_APP_INFO (appended to the User-Agent). Set PRONTUNO_APP_INFO to your app name and environment immediately — it is the cheapest thing you can do to make support requests traceable. Open config/prontuno.php only to see what the env values map to.

Look at these three things in order:

  1. config/prontuno.php — the full surface area in one file.
  2. The README examples for emails()->send() and the webhook middleware — they are the two paths most apps use.
  3. The exception classes (ApiException, ConnectionException, ConfigurationException, PaginationException) — they define your error-handling contract.

First use case: the mail transport. It is a one-line change and exercises the whole client, so it is the fastest way to validate keys, DNS/domain setup, and connectivity.

MAIL_MAILER=prontuno
PRONTUNO_SENDING_KEY=...

Then send something disposable from php artisan tinker:

Mail::raw('hello from prontuno', fn ($m) => $m->to('[email protected]')->subject('Smoke test'));

If that arrives, your sending key and domain are correct. Do this before wiring anything into application code, and before you start on webhooks.

Implementation Patterns

Use the mail transport for anything Mailable-shaped; use the client for everything else. MAIL_MAILER=prontuno covers Mailables, notifications, and Mail::raw(), and maps from/to/cc/bcc/reply-to/subject/text/HTML/custom headers/attachments including inline ones. Do not hand-roll Prontuno::emails()->send() inside a Mailable — you lose the framework's queueing, Mail::fake(), and the transport's Message-ID idempotency.

Multiple sending domains = multiple mailers. Rather than swapping the key at call sites, add a mailer in config/mail.php with its own key, then select it per mailable with ->mailer('billing'). This keeps the choice declarative and testable.

Treat X-Prontuno-Email-ID as your correlation ID. Accepted message IDs land on the sent message as a header. Capture it in a MessageSent listener and persist it against your own record — that is the join key for later emails()->retrieve() calls and for support/debugging.

Reserve direct emails()->send() for programmatic sends. When you do use it, always pass an idempotency key. Retries for POSTs only happen when a key is present, so omitting one converts an ambiguous network failure into a potential double-send. For volume, sendBatch() and map ApiException field-level errors (they are per item) back to your input offsets.

Domain onboarding as an explicit workflow. Create the domain, store the returned ID, then retrieve() (or list()) to poll verification. Because there is no webhook event listed for domain status in this release, plan on a scheduled job rather than an event-driven flow.

Suppressions: mirror, don't replace. Pull the suppression list into your own table on a schedule and check it before sending. This makes your send path resilient to a Prontuno outage and gives you one place to apply the policy.

Webhooks: middleware for the common case, verifier for the custom case.

Route::middleware('prontuno.webhook')->group(function () {
    Route::post('/webhooks/prontuno/billing', BillingWebhookController::class);
});

Name extra endpoints with a suffix (prontuno.webhook:billing) to select a distinct secret. Switch on the WebhookEvent type constants rather than string literals. If you need the raw request or custom routing, WebhookVerifier::fromRequest() inside your own controller is equivalent.

Deduplicate webhook events on your side. The signature proves authenticity; it does not prove once-only delivery. Keep a unique index on the event ID and return 200 on duplicates — that is also what makes a 300-second tolerance safe.

Multi-tenant: create scoped clients, don't mutate the facade. usingApiKey() and withSendingKey() both return a new client. Resolve per-tenant clients through a small factory or container binding keyed on tenant ID, and resolve it inside jobs rather than serialising a client into a queued payload.

Pagination: list() for UI, lazy() for jobs. list() gives you one page with a cursor when you are rendering or stepping through; lazy() is what you want in a sync command. Set a page limit on long-running jobs so a runaway collection cannot outlive the worker.

Retries are already there for reads — budget for them. GET/DELETE retry on 408/429/5xx and connection failures, honour Retry-After up to 60s, and apply the timeout per attempt. Size queue worker and job timeouts as attempts × timeout + backoff, not as a single request.

Testing: Http::fake() works because the client sits on Laravel's HTTP client. Fake the full URLs (derive them from your configured base URL) and assert on the outbound payloads. Cover three cases explicitly: a 429 followed by success, a ConfigurationException for a missing key, and a webhook request with a valid/expired signature. If you need a real signature in tests, compute the HMAC over the raw body with your test secret.

Start with MAIL_MAILER=prontuno in one non-production environment. Prove deliverability and header/attachment mapping there, then promote. Add a smoke test that asserts the transport is wired (Mail::mailer() resolves) so a config regression fails CI instead of production.

Gotchas and Tips

v0.1.0 means pin it. Use an exact constraint ("prontuno/prontuno-laravel": "0.1.*") and read the changelog on every bump. Assume payload shapes and method signatures may shift before 1.0.

Two keys means two failure modes. Sending-key methods and management-key methods are distinct, and a call whose key is missing throws ConfigurationException before a request is made. That is excellent for diagnosis but means a half-configured environment fails at the call site, not at boot. Add a startup or health-check assertion that both keys are present in environments that need them.

ConfigurationException will not be retried. Do not wrap sends in a generic retry loop that swallows it — a missing key is not transient, and retrying it just buries the real problem.

ConnectionException extends Laravel's own. Any existing catch (\Illuminate\Http\Client\ConnectionException $e) in your codebase will now also catch Prontuno transport failures. Audit those blocks if you are adding this to a mature app.

Watch what you do to Message-ID. It is the idempotency key. If you set a custom Message-ID header — or send from a loop that reuses one — you can either collide two distinct messages or accidentally dedupe a legitimate send. Let the framework generate it unless you have a specific reason not to.

Signature verification needs the raw body. Any middleware that parses, rewrites, or re-encodes the request before the verifier runs can break the HMAC. Put prontuno.webhook early in the group and do not run body-mutating middleware ahead of it.

300-second tolerance is generous but not infinite. Long queue backlogs, retried deliveries with stale timestamps, and clock skew on containers all surface as 401s. NTP your hosts and keep the webhook path off slow queues.

Named webhook secrets must exist in config. prontuno.webhook:billing with no matching secret in config/prontuno.php fails verification. And after editing config, run php artisan config:cache — cached config is the single most common cause of "it works locally."

A retried DELETE that 404s is a success. The client treats "already removed" as success on the retry path, so do not add your own retry wrapper around delete() calls or you will re-introduce the failure mode it solves.

Retry-After is capped at 60 seconds. A server that asks for longer will get a shorter wait and another 429. Handle that as backpressure — slow the producer — rather than raising the retry count.

PaginationException on a lazy() walk usually means a bad cursor. The client guards against repeated cursors specifically because servers can loop. If you see it, log the page number and cursor from your own take/chunk callback before escalating — the exception means the walk stopped, not that data was lost.

ApiException carries more than a message. Log the request ID and error code on every catch; branch on retryable and the suggested action rather than on status codes you hardcode. For sendBatch(), field-level errors are keyed per item — index into your original array, don't try to match on email address.

Signed attachment download links are fresh, not permanent. emails()->retrieve() minting a new link means you should not persist the URL; persist the email ID and re-fetch when the user clicks. Cache the redirect for a short window at most.

Inline attachments are the highest-risk mapping. Send one real HTML email with a cid:-referenced image and check it in a client that blocks remote content before you trust the mapping in production.

Keep PRONTUNO_APP_INFO descriptive but non-sensitive. It goes out in the User-Agent on every request and may end up in third-party logs — app name and environment are fine, tenant IDs and tokens are not.

The framework-agnostic client is a useful escape hatch. If you hit an edge case in the Laravel wrapper, prontuno/prontuno-php is the layer underneath; reading its source is the fastest way to understand exactly what payload the Laravel client is producing when Http::fake() assertions get confusing.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo