mrdanshur/awspushbundle
A Symfony bundle that integrates AWS Simple Notification Service (SNS) for sending mobile push notifications to iOS and Android devices. It wraps SNS client setup and push delivery in Symfony services and configuration, letting you dispatch device notifications from your application.
First, the reality check. Despite the "send push notifications from Laravel" framing, this package ships as a Symfony Bundle — it depends on symfony/framework-bundle, registers via bundles.php / Flex recipes, and exposes its config through a DependencyInjection/Configuration.php tree. Laravel has no bundle lifecycle: no Kernel::registerBundles(), no Extension::load(), no YAML config merge. composer require mrdanshur/awspushbundle in a Laravel app installs the code but wires up nothing. Nothing in the new 1.0.1 release changes that — it is a bugfix-only patch ("Fix GCM issue"), no new services, no framework-agnostic entry point, no Laravel provider. So the honest answer to "how would a Laravel dev use this day-to-day" is still: you wouldn't, directly. What you'd do is mine it for the SNS domain logic and reimplement the 80 lines that matter behind your own interface.
With that caveat, the first things to read:
src/ — find the class holding an Aws\Sns\SnsClient instance. That's the whole value of the package: a thin wrapper around createPlatformEndpoint() and publish(). Check the 1.0.1 diff here first — the GCM fix almost certainly lives in this class or its message-building helper.DependencyInjection/Configuration.php — this is the de-facto spec for what the bundle needs to work: AWS region, credentials, and one platform-application ARN per platform (APNS, APNS_SANDBOX, GCM, ADM).Resources/config/services.xml — the service id, in case you want to instantiate the class manually in a Laravel service provider (you'll be passing a raw config array rather than relying on the extension).The first use case you'll hit is register → map → publish: the mobile client hands you a device token at login, you call CreatePlatformEndpoint to get a persistent endpoint ARN, store it against the user, and publish to that ARN later.
In Symfony it looks roughly like this (verify key names in Configuration.php — the package is undocumented and pre-1.0 in spirit):
# config/packages/aws_push.yaml — Symfony only, sketch
aws_push:
region: '%env(AWS_REGION)%'
key: '%env(AWS_ACCESS_KEY_ID)%'
secret: '%env(AWS_SECRET_ACCESS_KEY)%'
applications:
ios: 'arn:aws:sns:eu-west-1:123:endpoint/APNS/app/...'
android: 'arn:aws:sns:eu-west-1:123:endpoint/GCM/app/...'
The Laravel equivalent you'll actually ship — plain aws/aws-sdk-php, no bundle:
// config/services.php
'sns' => [
'region' => env('AWS_DEFAULT_REGION'),
'applications' => [
'ios' => env('SNS_APP_APNS'),
'android' => env('SNS_APP_GCM'),
],
],
// App\Services\Push\SnsEndpointRegistrar
public function register(string $platform, string $token, int $userId): string
{
$result = $this->sns->createPlatformEndpoint([
'PlatformApplicationArn' => config("services.sns.applications.{$platform}"),
'Token' => $token,
'CustomUserData' => (string) $userId,
]);
return $result['EndpointArn'];
}
Persist the endpoint ARN, not the token. The token is the client's problem; the ARN is your stable handle. A simple table carries the whole pipeline:
| column | notes |
|---|---|
user_id, platform |
unique composite key |
token |
for detecting rotation |
endpoint_arn |
what you publish to |
enabled |
local mirror of SNS's Enabled attribute |
last_seen_at |
drives re-engagement sweeps |
Treat registration as idempotent upsert. CreatePlatformEndpoint is not idempotent — calling it twice with the same token returns InvalidParameter: Invalid parameter: Token Reason: Endpoint ... already exists with the same Token. The standard fix is to parse that error, extract the ARN, and store it; or pre-emptively call getEndpointAttributes() and reuse. Wrap both paths behind register() so callers never see it.
Use MessageStructure: 'json' for cross-platform payloads. One call, platform-specific shapes:
$this->sns->publish([
'TargetArn' => $endpointArn,
'MessageStructure' => 'json',
'Message' => json_encode([
'default' => 'You have a new message.',
'APNS' => json_encode(['aps' => ['alert' => $title, 'sound' => 'default']]),
'APNS_SANDBOX' => json_encode(['aps' => ['alert' => $title]]),
'GCM' => json_encode(['notification' => ['title' => $title, 'body' => $body]]),
]),
]);
The default key is mandatory and is what gets delivered if SNS can't match the endpoint's platform — keep it a human-readable string, not {}.
Fan-out belongs on a queue. SNS is one-publish-per-endpoint for direct sends, so a 10k-user broadcast is 10k HTTP calls. Put it in a ShouldQueue job, chunk the ARNs, set retryUntil, and throttle with Redis::throttle() so you don't blow through the SNS request-rate quota. For true broadcast, publish once to a topic and let subscribers (endpoints) fan out — that's the one place SNS genuinely saves you work.
Wrap it in a Notification channel. Keep SNS out of your domain code:
class PushChannel
{
public function send($notifiable, PushNotification $notification): void
{
foreach ($notifiable->pushEndpoints()->where('enabled', true)->get() as $endpoint) {
PublishPush::dispatch($endpoint->endpoint_arn, $notification->payload());
}
}
}
Now swapping in FCM v1 direct, Pinpoint, or a Lambda is a one-class change — which matters given this package's maintenance profile.
Integration tip: keep the SNS client behind interface PushTransport { public function publish(string $arn, array $payload): void; }. Your tests bind a fake; nothing in CI touches AWS.
1.0.1 is a single bugfix, not a revival. The release note is one line — "Fix GCM issue" — with no changelog detail, no test additions called out, and no framework changes. It doesn't make the package Laravel-installable and it doesn't address any of the structural caveats below. A lone patch on an otherwise dormant repo tells you someone hit a bug in production; it doesn't tell you the project is now maintained. Don't read it as a signal to adopt it as a dependency — read it as a signal to diff the change and cherry-pick the logic.
The GCM fix is probably aimed at a dead API path. If the patch touches the legacy FCM server-key flow (PlatformCredential = server key, GCM payload shape), it's fixing behaviour that Google retired. FCM v1 requires a service-account JSON credential on the platform application, and no 2023-era bundle is tracking that. Before assuming 1.0.1 solves your Android delivery, verify against current SNS docs what credential format your platform application actually holds — and be prepared to find the fix irrelevant to FCM v1.
APNS_SANDBOX vs APNS is the #1 silent failure. A debug-build iOS token published to the production APNS app returns MessageId and delivers nothing. Store the environment alongside the endpoint, or keep separate platform applications per build channel. The 1.0.1 patch doesn't touch this path.
Publish succeeding means "SNS accepted it", not "the device got it". Endpoints in Enabled: false state accept publishes happily. You get a MessageId, the user gets silence, and you find out weeks later. Enable delivery status logging on the platform application (SuccessFeedbackRoleArn / FailureFeedbackRoleArn → CloudWatch) and alert on NumberOfNotificationsFailed. Also re-enable or recreate endpoints when APNs returns Unregistered — SNS sets Enabled: false for you, but doesn't clean up.
Payload budget is tighter than you think. SNS caps the entire Message (including the MessageStructure: json envelope and the default fallback) at 256 KB but APNs is 4 KB and FCM ~4 KB. The envelope overhead eats into that. Trim before you get InvalidParameter: Message too long. If 1.0.1 changed how the GCM branch is serialised, re-measure — a "fix" that adds fields to the payload can push you over the limit on Android.
Region mismatch and credentials. AccessDenied usually means the IAM principal lacks sns:Publish / sns:CreatePlatformEndpoint on that specific ARN, not a bad key. In Laravel prefer an instance/task role over inline key/secret in config — the bundle's YAML-style key/secret keys are exactly the pattern you want to avoid, and it also means the credentials never appear in config caching.
Debugging loop that actually works: log the EndpointArn, the raw MessageId, and getEndpointAttributes() at publish time. If Attributes.Enabled is "false", stop looking at your payload — the endpoint is dead. If it's enabled and nothing arrives, it's the sandbox/production split or the platform credential, in that order. When chasing a GCM-shaped bug specifically, log the exact JSON string you put in Message — the envelope is where these regressions hide.
Extension points, in order of preference: (1) your own PushTransport interface with an SNS adapter, (2) fork the bundle's single service class (and pull in whatever 1.0.1 changed if you're on the legacy GCM path), (3) bind the bundle's service manually in a Laravel provider and accept the Symfony coupling. (1) costs an afternoon and survives this package being archived.
How can I help you explore Laravel packages today?