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

Phpsms Laravel Package

toplan/phpsms

PHP SMS toolkit for Laravel and standalone apps with a unified API, multiple gateway drivers, templates, and simple configuration. Send, queue, and manage SMS messages reliably through popular providers.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require toplan/phpsms

Laravel 5.5+ auto-discovers Toplan\PhpSms\PhpSmsServiceProvider. On older versions register the provider and the Sms alias manually in config/app.php:

'providers' => [Toplan\PhpSms\PhpSmsServiceProvider::class],
'aliases'   => ['Sms' => Toplan\PhpSms\Facades\Sms::class],

Then publish the config:

php artisan vendor:publish --provider="Toplan\PhpSms\PhpSmsServiceProvider"

This gives you config/phpsms.php — the single most important file in the package.

Where to look first

  1. config/phpsms.php — the agents map (credentials, weight, retry, sign, default template) and the global scheme/timeout keys.
  2. src/Agents/ — one class per provider (Luosimao, YunPian, SubMail, Aliyun, YunTongXun, Chuanglan, a Log agent, etc.). Read the one you actually plan to use; each documents its own config keys.
  3. src/Agent.php — the abstract base: this is the contract you implement if you need a provider that isn't bundled.

First use case — send one message, log it locally

Start with the Log agent so you can build and test the whole flow without spending money or holding real credentials:

use Toplan\PhpSms\Sms;

$sms = Sms::make(['Log'])
    ->to('13800138000')
    ->content('Your verification code is 1234')
    ->send();

if ($sms->success) {
    // delivered (or, for Log, written to the log)
}

Once that works, swap ['Log'] for a real agent name and fill in its credentials in config/phpsms.php.

Implementation Patterns

Single agent, explicit

Useful when a provider is contractually mandated or you need a specific sender ID:

$sms = Sms::make(['YunPian'])
    ->to($mobile)
    ->content('...')
    ->send();

Multi-agent failover (the reason to use this package)

Pass several agents and let the package walk them in weight order, retrying each per its retry setting:

// config/phpsms.php
'agents' => [
    'YunPian'   => ['send_key' => env('YUNPIAN_KEY'),   'weight' => 10, 'retry' => 2],
    'Luosimao'  => ['send_key' => env('LUOSIMAO_KEY'),  'weight' => 5,  'retry' => 1],
],

// code
$sms = Sms::make(['YunPian', 'Luosimao'])
    ->to($mobile)
    ->content('...')
    ->send();

You can also override per-agent options at call time by passing an associative array, so one flow can use a different template than the configured default:

Sms::make([
    'YunPian'  => ['template' => config('sms.tpl.notify')],
    'Luosimao' => ['weight' => 1],
])->to($mobile)->data(['name' => 'Ann'])->send();

Template vs. free content

Chinese providers almost always require a pre-approved template. Where an agent supports it, prefer the template path over raw content:

$sms = Sms::make(['YunPian'])
    ->to($mobile)
    ->template($templateId, ['code' => $code])   // positional data fills {placeholders}
    ->send();

Keep content sending for providers/regions that allow it, and centralize your copy in config or a translation file so the {placeholder} names stay in sync with what the provider approved.

Voice verification

Verification flows often fall back to a robocall when SMS fails. The package exposes a voice entry point on agents that support it — inspect the agent class before assuming:

$sms = Sms::make(['YunTongXun'])
    ->to($mobile)
    ->voice($code)
    ->send();

Off the request path

SMS gateways are slow and flaky; never block a user-facing response on them. Push the send onto the queue and consume the result asynchronously:

// Controller
Sms::queue()->to($mobile)->content('...');   // returns a closure/job for your queue

// Or wrap in your own job for full control
dispatch(new SendSmsJob($mobile, $code));

The package's queue helper only enqueues; retry/backoff semantics are yours unless you enable per-agent retry.

Hooks for logging and metrics

Static hooks let you centralize observability without touching every call site. Register them once in a service provider:

Sms::beforeSend(function ($sms, $agent) { /* ... */ });
Sms::afterSend(function ($sms, $agent) { /* ... */ });
Sms::sendSuccess(function ($sms, $agent) { Log::info(...); });
Sms::sendFail(function ($sms, $agent) { Log::warning(...); });

These fire per agent attempt, so they're the right place to count failures per provider and alert on a degraded gateway.

Inspecting the result

send() never throws on provider failure — it records an outcome per agent:

$sms = Sms::make([...])->to($mobile)->content('...')->send();

if (! $sms->success) {
    // per-agent detail: which agent, success flag, provider message/timings
    foreach ($sms->result as $agent => $detail) { /* ... */ }
}

Treat $sms->result as the audit record: persist it against the notification so support can answer "did it go out, and through which gateway?".

Custom agent

When you need a provider that isn't bundled (or a regional gateway), extend Toplan\PhpSms\Agent, implement the send methods it declares abstract, and assign to $this->result inside each. Register it under agents in config/phpsms.php with the same key you'll pass to Sms::make(). Read the abstract class first — the exact set of methods and result shape is small but specific, and it varies by which capabilities (content, template, voice) your agent claims.

Gotchas and Tips

  • Unmaintained since 2017. Pin the version in composer.json and run your test suite on the exact PHP version you deploy. The release predates PHP 7.4/8.x, so expect deprecation noise (dynamic properties, nullable signatures) or outright breakage on modern PHP. If you must run PHP 8, budget for a vendor fork or a thin adapter rather than assuming it works.
  • Failures are silent by design. send() returns an object; it does not throw. If you skip the $sms->success check your code will happily report "sent" when every gateway rejected you. Lint for unchecked sends in review.
  • Static state on the Sms class. Hooks and other globals live on a static facade. In long-running processes (queue workers, Octane, Swoole) they accumulate across jobs. Reset between jobs/tests — the package ships a static reset helper (cleanRuntime() in recent versions); verify the name against your pinned tag.
  • Config caching. Credentials referenced via env() inside config/phpsms.php freeze after php artisan config:cache. Deploy with env set, then cache; don't cache locally and ship the file.
  • Weights and retries are not failover guarantees. weight orders attempts; it does not re-route on a provider timeout unless retry is high enough to burn through. Set an overall timeout short enough that the worst case (N agents × M retries × timeout) still fits your queue worker's job timeout.
  • Long messages get split and billed per part. Content over the provider's limit is chunked automatically — one logical send becomes multiple chargeable segments and the result reflects that. Keep copy short; verify the split behavior in src/Content.php before trusting the cost estimate.
  • Approved templates, not free text. With Chinese providers the template ID and its {placeholder} names must match exactly what was approved in the provider console; a mismatch usually surfaces as a generic provider error inside $sms->result, not as a validation error.
  • Signatures and sender IDs. Most agents require a sign/sign_name in config. It's easy to forget in staging and get a rejection that reads like an auth failure.
  • Local development. Always route to the Log agent in local/testing (AppServiceProvider: if (app()->environment('local')) { config(['phpsms.scheme' => ['Log']]); }) and never let test suites hit a paid gateway.
  • Extension point for anything missing. The Agent base class plus a config entry is the escape hatch for a new provider, IP allowlisting differences, or a proprietary internal gateway — cheaper than forking the library.
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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json