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 Laravel Package

nexmo/laravel

Official Laravel integration for Vonage, formerly Nexmo. It provides a service provider, facade, config file, and notification channel for sending SMS messages and making voice calls via the Vonage API from any Laravel app, with simple installation and setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up (5 minutes)

composer require nexmo/laravel
php artisan vendor:publish --provider="Nexmo\Laravel\NexmoServiceProvider"

On Laravel 5.5+ the service provider and the Nexmo facade alias are auto-discovered via composer.json — no config/app.php edits. On older Laravel you must register Nexmo\Laravel\NexmoServiceProvider and alias 'Nexmo' => Nexmo\Laravel\Facade\Nexmo::class yourself.

Fill in .env (the published config/nexmo.php reads exactly these keys):

NEXMO_KEY=abc123
NEXMO_SECRET=xyz789
# only needed for webhook signature verification
NEXMO_SIGNATURE_SECRET=...
# only needed for voice / JWT-signed endpoints
NEXMO_APPLICATION_ID=...
NEXMO_PRIVATE_KEY=/absolute/path/to/private.key

Where to look first: config/nexmo.php (confirm which keys your published version actually has), then the container binding. The provider registers a singleton under both 'nexmo' and Nexmo\Client::class, so app('nexmo'), app(Nexmo\Client::class), constructor injection, and Nexmo:: all resolve to the same instance.

First use case — the smoke test in tinker:

php artisan tinker
>>> app('nexmo')->account()->getBalance();
=> ['value' => 10.28, 'autoReload' => false]

>>> Nexmo::message()->send([
...     'to'   => '14845551244',
...     'from' => 'Acme',
...     'text' => 'Hello from Laravel',
... ]);

If the balance call works, credentials are good. If SMS fails but balance works, it's almost always the from value (see Gotchas).

Implementation Patterns

Prefer constructor injection over the facade. It makes the client swappable in tests and keeps controllers thin:

use Nexmo\Client;

class SendReceipt
{
    public function __construct(private Client $nexmo) {}

    public function handle(string $to, string $body): void
    {
        $this->nexmo->message()->send([
            'to' => $to, 'from' => config('nexmo.sms_from'), 'text' => $body,
        ]);
    }
}

The facade is fine for tinker, seeders, and one-off artisan commands.

Watch the SDK major version. nexmo/laravel 1.x/2.x binds nexmo/client 2.x with the array-based API above. If your lockfile resolves vonage/client 3.x, the API is object-based and message() is gone:

use Vonage\SMS\Message\SMS;

$response = $client->sms()->send(new SMS($to, $from, $text));
$message  = $response->current();
$message->getStatus(); // 0 == accepted

Run composer show nexmo/client vonage/client before writing any SMS code — copying a snippet from a blog post written against the other major version is the #1 source of "undefined method" here. Voice ($client->voice()), number insight ($client->insights()), and account ($client->account()) are otherwise stable across both.

Wrap the client, don't scatter it. Create an SmsGateway interface with a NexmoGateway implementation bound in a service provider. That gives you: a single place to normalize the version differences, a natural place for logging/redaction, and a trivial fake for tests (implement a LogGateway).

Notifications vs. direct API — two different configs. For user-facing messages use Laravel's built-in vonage notification channel (laravel/vonage-notification-channel, toVonage() on the notifiable). Be aware it builds its own client from config/services.php under services.vonage.key / .secret / .sms_from — it does not read config/nexmo.php. Use the Nexmo facade for things the notification channel can't do (voice, verify, account, inbound processing). Duplicating credentials in two config trees is normal; forgetting to is a classic 401-at-runtime.

Verify / 2FA flow: start a verification, persist request_id (session or a verifications table with the phone number and expiry), redirect to a code form, then check. Persisting the request ID server-side — not in a hidden form field — prevents users from checking codes against someone else's verification.

Inbound SMS / delivery receipt webhooks: put the route in routes/api.php (no CSRF) or add it to VerifyCsrfToken::$except. Vonage's default inbound webhook is a GET with query params (msisdn, to, text, messageId, type), not JSON. Verify the sig parameter with the signature secret before trusting anything, then dispatch a job and return 200 immediately — Vonage retries on non-2xx.

Queue and throttle. The SMS API caps throughput per account (~30 msg/sec by default). Push bulk sends through jobs on a dedicated queue and rate-limit:

Redis::throttle('vonage-sms')->allow(25)->every(1)->then(fn () => $gateway->send(...));

Resolve the client inside handle(), not in the constructor — don't serialize a Guzzle-wrapped object into the queue payload.

Testing. Bind a fake into the container rather than hitting the network:

$this->mock(Nexmo\Client::class, function ($mock) {
    $mock->shouldReceive('message->send')->once();
});

If you want to exercise the real SDK code paths, construct a Client with a mocked PSR-18/Guzzle handler (['http_client' => $mock] in the options array) and assert on the request body.

Gotchas and Tips

  • This package is archived. Last release Feb 2022, no maintainer, Nexmo\ namespace. It still works, but expect dependency conflicts with current Laravel and PHP versions. New work should target vonage/client directly (or the Vonage-namespaced Laravel wrapper) and treat this as a migration source: Nexmo\Laravel\Facade\Nexmo → Vonage\Laravel\Facade\Vonage, Nexmo\Client → Vonage\Client, and the array SMS API → message objects. Keep the abstraction layer above so that migration is a one-file change.
  • NEXMO_PRIVATE_KEY path vs. body. The SDK accepts either a filesystem path or the PEM contents; a relative path resolves against the process working directory, which differs under queue workers, artisan config:cache, and Octane. Use base_path()/an absolute path, and ensure the file is readable by www-data. Never commit the key; .gitignore it.
  • Three secrets, three jobs. api_secret authenticates API calls, signature_secret only verifies inbound webhook signatures, and the private key only signs JWTs for voice/JWT endpoints. Mixing up signature_secret and api_secret produces valid-looking code that 401s on every send.
  • from is country-specific. Alphanumeric sender IDs ("Acme") are rejected or silently rewritten in the US, Canada, and several other markets — you need a long code, short code, or toll-free number there. A successful API call does not mean a delivered message; check status on the message object/response.
  • The SDK often doesn't throw on message failure. In the v2 array API, per-message errors come back as a status string ('1', '4', '15'…) inside the response. If you don't inspect it, failed messages look sent. Only transport-level problems throw (Exception\ServerException, Request\Exception\RequestException in v3).
  • Unicode costs money. Emoji or non-GSM characters force UCS-2 encoding, dropping the per-segment limit from 160 to 70 characters. Set the type explicitly ('type' => 'unicode' in v2; setType('unicode') on the v3 SMS object) instead of relying on detection, and remember a 150-character emoji message is 3 segments.
  • Multi-tenant credentials break the singleton. The client is resolved once per process. For per-account credentials, build a client yourself and app()->instance('nexmo', $client) — but any class that already injected Nexmo\Client keeps the old one. Safer: a factory/first-class-callable that returns a configured client per tenant.
  • config:cache and env(). Never call env('NEXMO_KEY') outside config/. It returns null after php artisan config:cache, which is exactly when things break in production.
  • Webhook idempotency. Retries and duplicate deliveries are guaranteed. Key your handler on messageId / request_id with a unique index or a Cache::add() guard before processing, or you'll send duplicate acknowledgements.
  • Extension points worth knowing: the container binding (override Nexmo\Client with a subclass that adds logging middleware), the Guzzle/HTTP client option on Client::__construct for request tracing, and Laravel events fired from your webhook controller to keep Vonage-specific code out of the domain layer.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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