sms77/api
PHP client for the SMS77 (seven.io) API, letting you send SMS, manage contacts, and integrate messaging into your applications. A lightweight SDK for transactional and bulk texting from PHP and Laravel projects.
composer require sms77/api. The package is PSR-4 autoloaded under the Sms77\Api\ namespace (the vendor name is legacy; the service is now seven.io).src/Client.php (the single entry point — every API call is a method here) and src/Params/ (typed param builders like SmsParams, VoiceParams, LookupParams). If a method isn't in Client, it isn't supported by the client yet — you'd hit the REST endpoint directly.SMS77_API_KEY (the constructor also accepts it explicitly; newer docs use SEVEN_API_KEY, so pass it explicitly to avoid ambiguity). Never commit it — put it in .env and config/services.php.$client = new \Sms77\Api\Client(config('services.seven.key'));
$client->sms('Hello from Laravel', ['to' => '491716992343', 'from' => 'MyApp']);
new it in app code:
$this->app->singleton(\Sms77\Api\Client::class, fn () =>
new \Sms77\Api\Client(config('services.seven.key'), 'laravel'));
Config + container binding. Keep key, from/sender ID, and an optional base_url in config/services.php under a seven key. Inject Client via constructor injection in jobs, mailers, and notification channels rather than resolving it inline.
A thin wrapper service. Don't scatter $client->sms(...) across the app. Wrap it in App\Services\SmsSender with a send(string $to, string $text, array $opts = []) method that normalizes responses into a DTO/array and centralizes error handling. This is the seam you'll mock in tests.
Params objects vs. arrays. The client accepts associative arrays for most calls, but the Params/* classes give you IDE autocomplete and validation. Use the params objects for anything complex (SmsParams with flash, unicode, label, ttl) and plain arrays for one-offs. They're interchangeable at the call site.
Bulk/batch sends as queued jobs. Each SMS is a network round-trip; loop over recipients inside a ShouldQueue job and throttle with Redis::throttle() or Bus::batch() so you don't hammer the API or your queue. This is the main "day-to-day" pattern: a SendSmsJob per message, fanned out from a controller or model event.
Custom Laravel notification channel. Laravel has no built-in seven.io channel. Build App\Notifications\Channels\SevenSmsChannel implementing send($notifiable, $notification), calling $notification->toSevenSms($notifiable) and passing the result to your SmsSender. Register it via via() returning the channel class. This keeps SMS alongside mail/database notifications and lets Notification::route('sms', $phone) work naturally.
Inbound webhooks. Configure the hook in the dashboard (hooks() methods exist for managing them via API), then expose a route that verifies the payload/signature and dispatches a processing job. Treat delivery receipts (DLRs) as async — don't block on them.
Fallback / reliability. Wrap sends in a small retry policy (the errors worth retrying are transport-level: timeouts, 5xx) and fall back to a second channel or log-only in non-production.
Client methods return the raw HTTP body (JSON string). You must json_decode($response, true) yourself and check for an id/status field. Wrap this once; don't do it at every call site.GuzzleHttp\Exception\*, not a domain-specific exception. Catch RequestException (has getResponse() for the error body) and ConnectException separately. Translate into your own SmsException at the wrapper boundary.SmsSender in the container or run against a local mock server via the constructor's base-URL argument. Note that Laravel's Http::fake() will not intercept this package — it uses Guzzle directly, not the Laravel HTTP client. Mock the wrapper or inject a Guzzle MockHandler.unicode=1 (or the params object equivalent) or the message is mangled/truncated. Segment counting changes too — check text length against unicode before sending to avoid surprise multi-part billing.from must be an approved/alphanumeric sender for the destination country; unregistered IDs silently fall back to a shared number on some routes. Verify per-country behavior in the dashboard, not in code.to format. Use E.164 without + (e.g. 491716992343) to match the examples; mixed formatting is the most common cause of "message accepted but never delivered."Client and call the protected request/HTTP helper, or just use Guzzle against https://gateway.seven.io/api/... in the same wrapper service — keeps the escape hatch in one place.How can I help you explore Laravel packages today?