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

Laravel Notification Zapmizer Laravel Package

zapmizer/laravel-notification-zapmizer

A Laravel notification channel for Zapmizer that lets you send notifications through Zapmizer's API using Laravel's built-in notification system. Configure credentials, route recipients, and deliver messages cleanly from your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config, then wire credentials into .env:

composer require zapmizer/connect
php artisan vendor:publish --tag="zapmizer-config"
ZAPMIZER_BASE_URL=https://...
ZAPMIZER_TOKEN=...

Look first at config/zapmizer.php (base URL, timeout, retry knobs) and at the two classes you will touch daily: InstanceClient (single-shot API calls such as media()) and ZapmizerConnection (higher-level flows such as awaitMedia() and syncMessages()).

First use case — fetch and inspect media:

use Zapmizer\Connect\InstanceClient;

try {
    $media = app(InstanceClient::class)->media($mediaId);
} catch (\Zapmizer\Connect\Exceptions\MediaRejectedException $e) {
    report($e);                       // $e->reason() explains the 422
} catch (\Zapmizer\Connect\Exceptions\MediaRateLimitedException $e) {
    // $e->retryAfter() — seconds, or null if the header was absent
}

If you were relying on raw HTTP status codes from media(), note that as of 0.3.1 a 422 and a 429 no longer surface as generic connection errors — they arrive as the two dedicated exception types above.

For message ingestion, locate syncMessages() next. Its contract is cursor-based: it returns messages saved after the cursor, not a general change feed. Release 0.4.1 is a documentation-only correction to the 0.4.0 wording around this method.

Implementation Patterns

Let awaitMedia() absorb rate limits, not rejections. ZapmizerConnection::awaitMedia() now treats a 429 as a transient downloading state and sleeps for the server-provided Retry-After before polling again. A 422 is not absorbed — it propagates immediately, so a bad/expired media reference fails fast instead of burning your timeout budget.

use Zapmizer\Connect\ZapmizerConnection;
use Zapmizer\Connect\Exceptions\MediaRejectedException;

try {
    $media = app(ZapmizerConnection::class)->awaitMedia($mediaId, timeout: 120);
} catch (MediaRejectedException $e) {
    // Unrecoverable: don't retry, surface reason() to the user or mark the record failed
    $this->fail($e->reason());
}

Catch children before the parent. Both new exceptions extend ZapmizerConnectException. Order your catch blocks specific → general, or a broad catch (ZapmizerConnectException $e) will swallow the rate-limit signal you wanted to act on.

Drive queue backoff from the exception. In a job, let retryAfter() decide the delay rather than a fixed constant:

public function handle(InstanceClient $client): void
{
    try {
        $client->media($this->mediaId);
    } catch (MediaRateLimitedException $e) {
        $this->release($e->retryAfter() ?? 30);
    } catch (MediaRejectedException $e) {
        $this->delete(); // permanent failure — retrying won't help
    }
}

Keep $tries modest and use retryAfter() as the floor; pair with backoff() on the job for the non-429 cases.

Separate "not ready yet" from "never going to be ready." For long-running media (video processing, large uploads), prefer awaitMedia() for the polling, and reserve direct media() calls for short-lived, user-triggered lookups where you want immediate 429 feedback in the request cycle.

Treat syncMessages() as an append-only cursor feed. It returns messages saved after the cursor. Messages that arrive during a reconnection sync are included, but an edit to a message that already existed will not be returned — Zapmizer paginates by primary key. Persist the cursor per account/connection and upsert by provider ID so reconnection syncs stay idempotent.

$cursor = $connection->zapmizer_cursor;

$messages = app(ZapmizerConnection::class)->syncMessages($cursor);

foreach ($messages as $message) {
    InboxMessage::updateOrCreate(
        ['zapmizer_id' => $message->id],
        ['payload' => $message->toArray()]
    );

    // Use the provider's cursor/primary key returned by the API.
    $cursor = max($cursor, $message->id);
}

$connection->update(['zapmizer_cursor' => $cursor]);

Do not use syncMessages() to propagate edits or deletions. For those, use webhooks, a separate reconciliation job, or a periodic full sync that does not rely solely on the saved-after-cursor feed.

Map reason() into your domain. Log the raw reason() and translate it to a friendly message at the boundary; do not leak provider phrasing to end users.

Gotchas and Tips

  • Upgrade note: 0.3.1 → 0.4.1 is non-breaking, and 0.4.1 itself is documentation-only. However, code that inspected status codes or caught ZapmizerConnectException to detect a 422 will silently change behaviour if it has not been audited since 0.3.1. Grep for media( and getStatusCode() before bumping.
  • syncMessages() is not a change-data-capture feed. The 0.4.1 docs correct the 0.4.0 wording: it returns messages saved after the cursor, including messages that arrive during a reconnection sync, but it does not return edits to existing messages. Zapmizer paginates by primary key, so an updated row will not reappear just because its content changed.
  • Persist the cursor and upsert. Reconnection syncs can include messages that arrived while you were disconnected. Without an idempotent updateOrCreate() keyed by the provider message ID, you will create duplicates. Without persisting the highest cursor returned, you will either re-read or skip messages.
  • retryAfter() can be null. If the upstream response omits Retry-After, pick your own default (30–60s) rather than releasing immediately and hammering the endpoint.
  • 422 escaping awaitMedia() is by design. Wrap the call, not the loop, and treat it as terminal — retry policies that blanket-retry all ZapmizerConnectException will loop forever on a rejected asset.
  • Don't rebuild the wait loop yourself. If you wrote a manual sleep()/usleep() poller before 0.3.1, replace it; the built-in path is now rate-limit aware and respects server timing.
  • Debugging: log the exception class alongside the media ID — "422" alone doesn't tell you whether it was a permissions problem, a deleted asset, or a bad format. For message syncs, log the cursor and the number of rows returned so you can distinguish "no new messages" from "cursor too far ahead."
  • Tests: fake the HTTP layer (Http::fake()) and assert the typed exceptions are thrown for 422/429; assert awaitMedia() issues a second request after the Retry-After window rather than asserting on wall-clock time (Carbon::setTestNow() / travel()). For syncMessages(), test cursor persistence, duplicate-free reconnection syncs, and that an edited existing message is not expected to reappear.
  • Extension point: catching the base ZapmizerConnectException is still the right place for global reporting/logging in bootstrap/app.php or your handler — just make sure the specific types are handled first in caller code.
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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support