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

Interfax Laravel Package

laravel-notification-channels/interfax

Laravel notification channel for sending faxes through InterFAX. Add fax delivery to Laravel notifications so your app can notify users or systems via InterFAX using a simple, consistent channel API.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require laravel-notification-channels/interfax
php artisan vendor:publish --provider="NotificationChannels\Interfax\InterfaxServiceProvider"

Check your framework version first. v8.0.0 dropped Laravel 11 and added Laravel 13. That means:

Your app Constraint
Laravel 13 ^8.0
Laravel 12 ^8.0 or ^7.0 — test before assuming
Laravel 11 ^7.0 only (stay put until you upgrade the app)

The v8 release notes only touch the CI matrix and the framework constraint — no channel or message API changes — so the upgrade itself is mechanical. Verify after install that InterfaxChannel::send() still looks the same as the version you were on.

Set credentials in .env (Interfax uses HTTP Basic auth with your account username/password, not an API key):

INTERFAX_USERNAME=your-account
INTERFAX_PASSWORD=your-password

2. Read these three files first

Because this is a thin community package with no dependents, treat the source as the documentation. Open, in order:

  • src/InterfaxChannel.php — the send() method tells you exactly what HTTP call is made and what the channel returns.
  • src/InterfaxMessage.php — the fluent API (to(), content(), file(), etc.) and which Interfax query parameters are actually exposed.
  • config/interfax.php — endpoint/host, sandbox flag, timeout, and any credential key names (they may not match the env names above).

3. Your first notification

The canonical first use case is a report delivery to a partner who only accepts faxes — e.g. faxing a signed PDF to a clinic, a purchase order to a supplier, or a nightly reconciliation sheet to a legacy back office:

class FaxDailyReport extends Notification implements ShouldQueue
{
    public function __construct(private string $faxNumber) {}

    public function via($notifiable): array
    {
        return [InterfaxChannel::class];
    }

    public function toInterfax($notifiable): InterfaxMessage
    {
        return (new InterfaxMessage)
            ->to($this->faxNumber)
            ->content($pdfBytes, 'application/pdf');
    }
}

Trigger it with Notification::route(InterfaxChannel::class, $number)->notify(...) if you don't want a Notifiable model at all — that's the fastest way to prove credentials and the endpoint work end-to-end.

Implementation Patterns

On-demand routing vs. notifiable models. Two shapes are idiomatic:

// A) Throwaway target — no model changes
Notification::route(InterfaxChannel::class, '+15551234567')->notify(new FaxInvoice($invoice));

// B) Model owns its default fax number
class Clinic extends Model
{
    public function routeNotificationForInterfax($notification): ?string
    {
        return $this->fax_number;
    }
}

Prefer (A) for transactional sends where the number comes from the request or a config value; prefer (B) when every notification to that entity should hit the same fax line. If neither exists, the channel has no destination and the request will fail at Interfax — always set to() explicitly as a fallback.

Generate the document, then hand it over as bytes. The most common real workflow is: render → fax → log the Interfax message ID.

$pdf = Pdf::loadView('reports.daily', $data)->output();   // dompdf / snappy
$response = $clinic->notify(new FaxDailyReport($pdf));

Keep rendering inside the job, not inside the notification, so queued retries regenerate rather than replay a stale buffer. If your PDF is already on disk, pass a path/stream instead of loading the whole file into a string — faxes with many pages can be tens of MB.

Queue everything, always. The channel makes a blocking HTTP call to Interfax. Add ShouldQueue (and consider a dedicated queue) so a slow or down Interfax endpoint can't stall a web request. This is also the only place Laravel's retry/backoff machinery can help you, since a failed fax inside a synchronous request is simply an exception.

Status polling is your responsibility, not the channel's. Interfax accepts the fax and returns a message/resource ID; delivery is asynchronous (typically a queued → in-progress → success/failed lifecycle). A successful notify() means "accepted", nothing more. The pattern that works day-to-day:

// store the returned ID at send time
$id = $clinic->notify(new FaxDailyReport($pdf));

// later, in a scheduled command
Schedule::command('faxes:reconcile')->everyFiveMinutes();

Have that command call the Interfax status endpoint for every outbound fax still marked pending, and update your own faxes table. Build a small local model — id, to, pages, status, error_code, sent_at — rather than trying to reconstruct history from Interfax's API later.

Multiple recipients = multiple notifications, not one message. Fax is point-to-point; loop and dispatch separately so one bad number doesn't abort the batch:

foreach ($numbers as $number) {
    Notification::route(InterfaxChannel::class, $number)->notify(new FaxNotice($pdf));
}

Test with Notification::fake() for wiring and Http::fake() for the channel itself.

Notification::fake();
$clinic->notify(new FaxDailyReport($pdf));
Notification::assertSentTo($clinic, FaxDailyReport::class);

// Exercise the real channel without hitting Interfax:
Http::fake(['rest.interfax.net/*' => Http::response('', 200)]);

Notification::fake() proves your toInterfax() builds a message; Http::fake() proves the channel serialises it correctly. You generally need both.

Wrap it behind your own service. The package tracks Laravel majors reliably but is still a tiny wrapper around one HTTP call, and any release can bump the framework floor out from under you (v8 just did). Put a one-method FaxSender interface in front of it. When a future major forces a framework upgrade you can't take yet, or when the Interfax API version changes, you swap one binding and every notification keeps working.

Upgrading majors is mostly a composer.lock event. Because these releases are usually constraint-only, the practical workflow is: bump ^7.0 → ^8.0 on a branch, run composer update laravel-notification-channels/interfax, run your Http::fake() tests, diff InterfaxChannel::send(), and ship. The risk is not in the diff — it's in apps pinned to a framework version the new release no longer supports.

Gotchas and Tips

"Sent" ≠ "delivered." The single biggest operational trap. The channel returns after Interfax accepts the job, so notify() succeeding tells you nothing about whether a fax machine answered. Without your own status reconciliation you will silently believe faxes arrive when they don't — and fax failures are common (busy line, no answer, wrong number).

Content type matters more than you'd expect. Interfax treats the request body as a file, so the MIME type you send determines how it's rendered. Send application/pdf for PDFs and text/plain (or the correct type) for plain text; a PDF declared as text will arrive as garbage pages, and vice versa. Check the signature of content() in InterfaxMessage — if it doesn't accept a second argument, look for a file()/data() variant, or set the type via the message's headers.

Credentials are HTTP Basic, and config key names are not standardised. This package predates the services.interfax convention, so the env names in its config may differ from what you'd guess. Verify against the published config rather than the README, and never put the username/password in a notification class.

Phone numbers must be E.164. +15551234567, no spaces, dashes, or extensions. Interfax is strict here and the error surfaces as a validation failure long after you've queued the job — validate at the form/request layer, not at send time.

There's no built-in retry or error mapping. Expect the channel to let Guzzle exceptions bubble. Wrap notify() in a try/catch, log the response body (Interfax returns useful error text), and map it to something your app understands. Combine with ShouldQueue's $tries/backoff() so a transient 5xx retries but a 4xx bad-number failure doesn't.

Sandbox before production. Interfax exposes a test endpoint that simulates lifecycle events. Point your config at it (look for a sandbox/host key) and drive a fake fax through your status-reconciliation code before touching a real line — otherwise your first real test is a real fax at real per-page cost.

Watch cost and page headers. Billing is per page, so a 60-page attached report is 60× the cost of a one-page cover note. If the message exposes a pageHeader option, keep it ASCII-only and short — non-ASCII headers can corrupt or be rejected.

Pin the version and read the diff on every major. Each major release here mirrors a Laravel major, and the release notes are terse — "support Laravel 13; drop Laravel 11; add PHP 8.5 to workflow matrices" tells you nothing about whether the channel's internals moved. Always commit composer.lock, pin an exact major (:^8.0, not :*), and after any bump open src/InterfaxChannel.php and confirm send() is unchanged. The extension point to remember is the service provider's container binding — if the channel resolves a client from the container, you can substitute your own configured Guzzle client (timeouts, middleware, logging) without touching the package.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core