javihgil/nexmo-bundle
A Symfony bundle that integrates Nexmo (now Vonage) SMS services into your application, handling client configuration and service wiring so you can send text messages through a simple, dependency-injection-friendly interface.
Read this first: it is not a Laravel package. javihgil/nexmo-bundle is a Symfony2 bundle — the install path is AppKernel::registerBundles(), config lives in app/config/config.yml under a javihgil_nexmo: key, and the client is exposed as a container service. There is no ServiceProvider, no Facade, no package discovery, and no config/ publish tag. You cannot composer require it into a Laravel app and get a working binding.
Compounding that: the repo is archived, last tagged 2016-03-01, 7 stars, 0 dependents. Its composer.json will pin symfony/symfony or symfony/framework-bundle to a 2.x range and likely a nexmo/client 1.x line — both of which conflict with any modern Laravel (symfony/* ^6/^7) and PHP 8.x. Expect Composer to refuse the install outright.
So the realistic "getting started" is triage, in this order:
composer.json — confirm the framework and PHP constraints. This tells you immediately whether a fork is viable or whether you are only reading it for reference.DependencyInjection/*Extension.php — the config tree. This is the only place the package defines its knobs (almost certainly api_key / api_secret, possibly from). Copy these key names into your own config/services.php array.Resources/config/services.yml — the service id and constructor wiring. This is the 10 lines you are actually going to port.send() / sendText() method that news up Nexmo\Client and forwards arguments.First use case: treat the bundle as a reference implementation, not a dependency. Read the service definition and constructor, then write the equivalent in App\Providers\NexmoServiceProvider:
$this->app->singleton(\Nexmo\Client::class, fn ($app) => new \Nexmo\Client(
new \Nexmo\Client\Credentials\Basic(
config('services.nexmo.key'),
config('services.nexmo.secret')
)
));
That is the entire value the bundle was providing. If you genuinely need to run the bundle as-is, it only makes sense inside a legacy Symfony 2.8 app, in which case register it in AppKernel and inject the service by id.
Port, don't depend. The highest-value pattern is a one-afternoon extraction: fork the repo, delete DependencyInjection/, JavihgilNexmoBundle.php, and Resources/config/, keep only the client wrapper, and re-expose it through a Laravel service provider. You keep the method signatures your team already knows and drop 90% of the surface area.
Bind to an interface, not the SDK. Wrap the Nexmo client behind SmsSender / SmsSenderInterface. The bundle era used key+secret auth; Vonage now expects application_id + private_key (JWT) for Voice and Messages API. An interface lets you swap the auth strategy without touching call sites — which you will have to do.
Always queue the send. SMS is external I/O with multi-second tail latency and provider-side throttling. The bundle has no queue awareness at all; it calls through synchronously. In Laravel:
class SendSms implements ShouldQueue
{
public function __construct(public string $to, public string $text) {}
public function handle(SmsSender $sms): void
{
$sms->send($this->to, $this->text);
}
}
Check the response body, not the HTTP status. Nexmo's SMS API returns 200 OK even when the message is rejected; the real verdict is messages[0].status ("0" = accepted). Any wrapper ported from a 2016 bundle almost certainly does not do this. Normalize it in your adapter:
$response = $client->sms()->send($message);
$msg = $response->current();
if ($msg->getStatus() !== 0) {
throw new SmsFailedException($msg->getStatus() . ': ' . $msg->getErrorText());
}
Webhook handling belongs in Laravel routing. Delivery receipts (status, messageId, msisdn) and inbound SMS arrive as GET/POST to a route you define. There is nothing in the bundle for this. Use a dedicated controller, verify the sig parameter (HMAC-SHA256 over the sorted params with your secret), and persist by messageId — that key is what ties a receipt back to whatever row you wrote when you queued the job.
Config shape. Mirror the bundle's parameter names so behaviour is familiar, but source them from env:
// config/services.php
'nexmo' => [
'key' => env('NEXMO_KEY'),
'secret' => env('NEXMO_SECRET'),
'from' => env('NEXMO_FROM', 'MyApp'),
],
Test seam. Register a NullSmsSender or a LogSmsSender in the test environment, or $this->mock(SmsSender::class). Never let a feature test hit the network — the bundle offers no fake, and outbound SMS in CI costs real money.
nexmo/client is permanently yours. Do not ship this to production.Nexmo\Client → Vonage\Client), and docs all moved. The old nexmo/client package is abandoned in favour of vonage/client. Any code you port should target the current SDK, not the one the bundle pins.%javihgil_nexmo.api_key%, service ids with dots, and tag-based injection have no Laravel equivalent. Do not attempt to "run" the bundle's services.yml through a shim; rewrite the ten lines by hand.from is often constrained per country — alphanumeric IDs are rejected in the US and Canada, where you need a long code or short code. Validate against destination country rather than assuming the configured from works everywhere.+44... (no spaces, no 00 prefix) yourself; malformed numbers return a per-message error status, not an exception.config:cache footgun. If you port the bundle's habit of reading credentials at container build time via getParameter(), make sure nothing calls env() outside config/ files — cached config returns null for direct env() calls in application code.Configuration.php tree as the documented contract for keys and defaults, and map it onto a Laravel config array. That is the one piece of the bundle's design worth preserving: a single validated place where credentials and defaults live.message-id and network) alongside your own correlation id. When someone reports "the SMS never arrived," the first question is always whether it was rejected at submit or accepted-but-undelivered, and only the response body distinguishes them.How can I help you explore Laravel packages today?