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

Sinch Bundle Laravel Package

fresh/sinch-bundle

Symfony bundle that wires the Sinch SMS and messaging API into your PHP application, providing ready-made services and configuration for sending texts, managing sender IDs, and handling delivery callbacks without boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first. fresh/sinch-bundle is archived (18 stars, 0 dependents). Release 0.4.0 is the first since 2018-01-02 and does exactly one thing: it moves from Symfony 2.x/3.x to Symfony 4. That is a hard break, not a feature. It means the bundle is now usable on Symfony 4.4 (itself EOL since November 2023), and still unusable on Symfony 5/6/7. There is no upgrade path for existing users — a Symfony 3 app cannot take 0.4.0 without moving the whole app to Symfony 4, and the config location, env-var syntax, bundle registration, and container visibility all change with it. For greenfield work the bundle still saves you roughly ten minutes of DI wiring over calling Sinch's REST API with symfony/http-client. Adopt it only to maintain a legacy Symfony 4 app already on it.

Minimal steps (Symfony 4.4 app):

composer require fresh/sinch-bundle:^0.4
// config/bundles.php — added by the recipe-less install, do it by hand
return [
    // ...
    Fresh\SinchBundle\FreshSinchBundle::class => ['all' => true],
];
# config/packages/fresh_sinch.yaml — confirm alias and option names against
# DependencyInjection/Configuration.php; the README is the only other spec.
fresh_sinch:
    key:    '%env(SINCH_KEY)%'
    secret: '%env(SINCH_SECRET)%'
# .env — never commit real values; Symfony 4 resolves %env()% at compile time
SINCH_KEY=your-key
SINCH_SECRET=your-secret
// Controller or service. Symfony 4 makes services private by default, so
// $this->get() on a container id will fail unless the bundle declares the
// service public. Check Resources/config/services.yml first; if it is
// private, inject the service id via arguments: in your own services.yaml
// (see Gotchas) instead of pulling it from the container.
$sms = $this->get('fresh_sinch.sms');
$sms->send('+14155551234', 'Your code is 482913');

Where to look first, in this order:

  1. Resources/config/services.yml — 0.4.0 almost certainly touched this file for the Symfony 4 move. It is where you learn the service id, the class, the constructor arguments, and, critically, whether the service is public.
  2. README.md — the config keys and service id, if the maintainer updated it for Symfony 4 (verify against the code; old READMEs survive releases like this).
  3. DependencyInjection/Configuration.php — the real config tree. If the README and the code disagree, the code wins.
  4. composer.json — the new symfony/* constraints. This is the whole point of the release, and it tells you exactly which Symfony versions are in play.
  5. DependencyInjection/FreshSinchExtension.php — check for any %env()% handling or parameter resolution changes the Symfony 4 move introduced.

First use case to validate: send one transactional SMS to your own phone from a Symfony console command (not a controller). It proves credentials, the from number, phone formatting, the network path, and — new in 0.4.0 — that the container wiring and env-var resolution actually work on Symfony 4, without risking a broken signup flow.

Implementation Patterns

Wrap it, never call it directly. The service is still the raw, untyped SDK client. Put a one-method interface in front of it on day one — this matters more than ever now, because 0.4.0 proves the package is willing to make breaking framework jumps with no migration path:

interface SmsSender
{
    /** @throws SmsFailedException */
    public function send(string $toE164, string $body): void;
}

final class SinchSmsSender implements SmsSender
{
    public function __construct(private /* the bundle service */ $client, private LoggerInterface $logger) {}

    public function send(string $toE164, string $body): void
    {
        try {
            $this->client->send($toE164, $body);
        } catch (\Throwable $e) {
            $this->logger->error('Sinch SMS failed', ['to' => $toE164, 'e' => $e]);
            throw new SmsFailedException('SMS delivery failed', 0, $e);
        }
    }
}

This is the single highest-value move: it makes the bundle swappable (replace the implementation with symfony/http-client and never touch calling code) and lets you inject a fake in tests.

Wire it through services.yaml, not the container. Symfony 4 defaults services to private, so this is now the normal path rather than an old-Symfony workaround:

# config/services.yaml
services:
    # Alias the bundle's service id to your interface so autowiring works.
    app.sinch.client:
        alias: fresh_sinch.sms

    App\Sms\SinchSmsSender:
        arguments:
            $client: '@app.sinch.client'
            $logger: '@logger'

    App\Sms\SmsSender:
        alias: App\Sms\SinchSmsSender

Now every caller type-hints SmsSender and the bundle id appears in exactly one file in your codebase.

Always send off the request thread. SMS is an external HTTP call to a third party with variable latency and frequent 5xx. Symfony 4.4 has Messenger; use it:

// Dispatch a message; a worker does the actual send.
$bus->dispatch(new SendVerificationSms($user->getId(), $code));
# config/packages/messenger.yaml
framework:
    messenger:
        routing:
            App\Message\SendVerificationSms: async

Parameterize the sender per brand/locale. Sinch routes better when the from matches the recipient's country/brand. Keep a map in config, not in code:

# config/services.yaml
parameters:
    sms.senders:
        GB: '+4475206xxxxx'
        US: '+1415555xxxx'
        default: '+1415555xxxx'

Do not put raw message text in call sites. Verification codes, order updates, and alerts have different copy, compliance constraints, and opt-out requirements. Keep templates (Twig is fine) and render before handing to the sender.

Testing. In tests, bind SmsSender to a NullSmsSender/InMemorySmsSender that records messages, and assert on that. Use Symfony's KernelTestCase container override or a test-only alias in config/services_test.yaml. Do not hit Sinch in unit tests, and do not getMockBuilder() the SDK client — the interface exists precisely so you don't have to.

Debug/QA transport. Add a dev implementation (or a kernel.environment === 'dev' branch in services.yaml) that logs the message and skips the call, or whitelists a single test number. Accidentally SMSing real users from staging is the classic failure mode of this integration.

Gotchas and Tips

  • 0.4.0 is a hard break, not an upgrade. There is no app/AppKernel.php anymore, so the old install instructions are dead. Config moves from app/config/config.yml to config/packages/fresh_sinch.yaml, bundle registration moves to config/bundles.php, and %env()% is now the only sane way to pass credentials. Budget this as a Symfony 3→4 migration, not a composer update.
  • Symfony 4.4 is EOL. The 0.4.0 release does not make the bundle modern; it makes it last-decade-current. Still no Symfony 5/6/7, no PHP 8.x testing, no security patches, no maintainer. composer why-not will still show you the constraint you cannot satisfy if your app is on anything newer.
  • The SDK dependency is the real risk, not the bundle. The bundle is thin DI glue; the transitive Sinch PHP SDK is the code that actually talks to the network, and 0.4.0 did not touch it. Audit that package and its Guzzle version before shipping — an old Guzzle is a more likely problem than the bundle itself.
  • Service visibility changed with the platform. In Symfony 4 the bundle's service is private unless explicitly declared public: true. $this->get('fresh_sinch.sms') in a controller will throw "service not found / private" rather than working the way it did on Symfony 2/3. The fix is the services.yaml alias above — do not set public: true globally in services.yaml to make $this->get() work again.
  • Clear the cache after touching credentials. Config is compiled. A changed key/secret in .env or config/packages/ will not take effect until bin/console cache:clear — a very common "the credentials are wrong" red herring. On FPM, also confirm the env var is actually exported into the PHP process, not just the shell that ran composer.
  • %env()% resolves at compile time by default. If you need per-request or runtime resolution, you must declare env(SINCH_KEY) as a runtime env processor in config/services.yaml; otherwise a cached container bakes in the value it saw at warmup. This is the number one Symfony 4 env-var gotcha and it is new relative to the Symfony 2/3 instructions this package used to ship.
  • Phone numbers must be E.164. +14155551234, not (415) 555-1234 or 0033.... Validate and normalize before calling; Sinch's error for a malformed number is not always descriptive, and you'll burn debugging time on a formatting bug.
  • Segment and encoding costs surprise people. GSM-7 vs. UCS-2 matters: a single emoji or curly quote flips the whole message to UCS-2 and can halve your character budget. A 160-char message with a smart quote is two segments. Keep alert copy ASCII, and count before you send.
  • Errors are silent-ish. The SDK throws on some paths and returns odd objects on others; delivery failures (invalid number, blocked, no route) come back asynchronously via Sinch's delivery reports, not from the send() call. Treat send() success as "accepted," never as "delivered."
  • Debugging playbook: log the outbound HTTP call (Guzzle middleware/log plugin) in dev; cross-check the message ID in the Sinch dashboard for delivery reports and error codes; verify the from number is actually provisioned on the account (an unprovisioned sender is a frequent 400). If the SDK silently no-ops on 0.4.0, first suspect a private service being resolved to a different class than you think — bin/console debug:container fresh_sinch.sms tells you immediately.
  • Secrets. Use .env/.env.local (never committed), never parameters.yml, and never log the full request with credentials. Symfony 4's secrets vault is the better option if you have it configured.
  • Extension point is still the service definition. If you need a different HTTP client, timeouts, or a proxy, override fresh_sinch.sms in your app's services.yaml with your own construction of the SDK client — don't fork the bundle, and never patch vendor/.
  • What to do instead (migration path): the drop-in replacement is an SmsSender implementation using symfony/http-client against Sinch's REST endpoint, with SINCH_KEY/SINCH_SECRET in .env. It's roughly 20 lines, has no archived dependency, supports Symfony 5/6/7, and is trivially testable. If you already introduced the SmsSender interface above, that migration is a one-file change — which is the best reason to do it first, even if you ship the bundle today.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core