SNS Custom Mobile Pushes: APNs and FCM Configuration#
Mobile push notifications have become essential to modern application experiences. Whether you’re sending time-sensitive alerts, engagement messages, or critical updates, getting notifications to your users’ devices reliably and at scale requires careful orchestration. AWS Simple Notification Service (SNS) abstracts away much of the complexity involved in managing multiple mobile platforms, but understanding how to configure and use it effectively is crucial for any developer building mobile applications on AWS.
In this article, we’ll explore how SNS enables you to deliver custom push notifications to iOS and Android devices through Apple Push Notification Service (APNs) and Firebase Cloud Messaging (FCM). We’ll cover the practical steps needed to set everything up, how to publish notifications to individual devices and bulk audiences, and the nuances of payload formatting and error handling that will help you build reliable notification systems.
Understanding SNS Mobile Push Architecture#
Before diving into configuration details, it’s helpful to understand how SNS fits into the broader mobile push notification ecosystem. SNS acts as a bridge between your application logic and the mobile notification services maintained by Apple and Google. Rather than your application needing to authenticate directly with APNs and FCM, you configure SNS as an intermediary that handles those connections on your behalf.
When you send a notification through SNS, here’s what happens behind the scenes: SNS receives your message, translates it into the appropriate format for the target platform, authenticates using credentials you’ve provided, and delivers it through either APNs or FCM to the end user’s device. This abstraction means you can change platforms or target multiple platforms simultaneously without rewriting your notification logic.
The flow works like this: your application registers a device with SNS, receives a platform endpoint ARN (Amazon Resource Name), and then uses that endpoint to publish notifications. SNS handles the heavy lifting of credential management, retries, and platform-specific message formatting. If you were building this yourself, you’d need to maintain secure connections to Apple and Google’s services, handle token rotation, and deal with all the platform-specific quirks. SNS handles that complexity for you.
Registering Applications with SNS#
The first step in sending mobile push notifications through SNS is registering your application as a platform application. This involves creating an SNS platform application resource for each mobile platform you want to target—one for APNs (iOS) and one for FCM (Android), for example.
To register an application for iOS with APNs, you’ll need to provide Apple credentials. Apple uses a certificate-based approach where you generate an SSL certificate in your Apple Developer account with Apple Push Notification Service enabled. You export this certificate (along with its private key) and upload it to SNS when creating your platform application. Alternatively, Apple also supports token-based authentication using a signing key, which is increasingly preferred for its improved security and simpler management.
For Android applications, you’ll need your Firebase Cloud Messaging credentials. When you create a Firebase project in the Google Cloud Console, you can generate a server API key or, more securely, download a service account JSON file. SNS requires either the server API key or the service account credentials to authenticate requests to FCM.
Here’s how you’d create a platform application for iOS using the AWS CLI:
aws sns create-platform-application \
--name MyIOSApp \
--platform APNS \
--attributes PlatformCredential=<certificate-or-token>,EventEndpointCreated=arn:aws:sns:us-east-1:123456789012:topic-for-creation-eventsAnd similarly for Android with FCM:
aws sns create-platform-application \
--name MyAndroidApp \
--platform GCM \
--attributes PlatformCredential=<server-api-key-or-service-account>Notice the platform values: APNS for iOS and GCM for Android (GCM is the legacy name; it still maps to FCM). The attributes section can also include optional settings like event endpoints for tracking when devices are created, updated, or deleted. These event notifications are genuinely useful if you’re trying to monitor the health of your notification infrastructure or keep your own database in sync with SNS state.
Creating Platform Endpoints for Individual Devices#
Once your platform application is registered, the next step is creating platform endpoints. A platform endpoint represents a specific device that can receive notifications. Each device is uniquely identified by its device token, which is generated by the mobile platform itself when your application requests notification permissions.
On iOS, your app calls the appropriate APIs to request user permission for notifications, and in return, APNs provides a device token—typically a 64-character hexadecimal string. Your app should securely transmit this token to your backend. Similarly, on Android, FCM provides a unique device token when your app initializes the Firebase Messaging SDK.
Once you have the device token, you create a platform endpoint in SNS:
aws sns create-platform-endpoint \
--platform-application-arn arn:aws:sns:us-east-1:123456789012:app/APNS/MyIOSApp \
--token device-token-from-apnsSNS returns a platform endpoint ARN that looks like this: arn:aws:sns:us-east-1:123456789012:endpoint/APNS/MyIOSApp/12345678-1234-1234-1234-123456789012. This ARN is your handle for publishing messages to that specific device.
You might be wondering: where should you create these endpoints? The answer depends on your application architecture. Some teams create endpoints as part of their user registration or device onboarding flow. Others have devices register themselves directly with SNS through unauthenticated APIs (with appropriate rate limiting and security measures). A common pattern is to have your backend create the endpoint when a user’s app first starts up and transmits its device token.
It’s worth noting that SNS allows you to attach custom attributes to endpoints using the set-endpoint-attributes command. This is handy for storing application-specific metadata like user preferences, subscription preferences, or custom segmentation data.
Publishing Notifications to Single and Bulk Endpoints#
Once you have platform endpoints, publishing notifications is straightforward. For a single device, you use the publish API with the platform endpoint ARN:
aws sns publish \
--target-arn arn:aws:sns:us-east-1:123456789012:endpoint/APNS/MyIOSApp/12345678-1234-1234-1234-123456789012 \
--message '{"default":"Hello from SNS"}'However, the message format above is overly simplistic. In reality, you’ll want to leverage SNS’s message structure feature, which allows you to specify platform-specific message content. This is done using a JSON payload where the top-level keys represent different targets: default for any platform not explicitly mentioned, APNS for Apple devices, GCM for Android devices, and so on.
Here’s a more realistic example:
{
"default": "This is the default message",
"APNS": "{\"aps\":{\"alert\":\"Important notification\",\"sound\":\"default\",\"badge\":1}}",
"GCM": "{\"data\":{\"message\":\"Important notification\"}}"
}Notice that the APNS and GCM values are themselves JSON strings (they need to be serialized as strings within the outer JSON structure). The APNS section follows Apple’s payload format, while GCM follows Google’s Data Message format.
When you publish with this message structure, you need to tell SNS to use it. In the AWS CLI, you’d add the --message-structure json flag. In the SDK, there’s typically a parameter to indicate the message structure type.
For publishing to multiple endpoints simultaneously, SNS provides topic-based publishing. You create an SNS topic and subscribe your platform endpoints to it. Then, when you publish to the topic, SNS automatically delivers the message to all subscribed endpoints. This is far more efficient than making individual publish calls for each device:
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:topic/my-notifications \
--protocol application \
--notification-endpoint arn:aws:sns:us-east-1:123456789012:endpoint/APNS/MyIOSApp/12345678-1234-1234-1234-123456789012Then publish once to the topic:
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:topic/my-notifications \
--message '{"default":"Hello all","APNS":"{\"aps\":{\"alert\":\"Hello iOS users\"}}","GCM":"{\"data\":{\"message\":\"Hello Android users\"}}"}' \
--message-structure jsonCrafting Platform-Specific Payloads#
Understanding payload structure is critical because it determines what your users actually see on their devices. APNs and FCM have different payload formats, and SNS requires you to speak both languages.
For APNs (Apple), the payload is structured around the aps (Apple Push Service) dictionary. Inside this dictionary, you specify alert text, sounds, badge numbers, and other iOS-specific behaviors:
{
"aps": {
"alert": {
"title": "New Message",
"body": "You have a new message from Sarah"
},
"sound": "default",
"badge": 5,
"content-available": 1,
"category": "MESSAGE_CATEGORY",
"mutable-content": 1
},
"custom_data": "any-custom-field"
}Let’s break down these fields: the alert field can be a simple string or an object with title and body. The sound field triggers a notification sound on the device (use “default” for the system sound). The badge field updates the application badge number shown on the home screen. The content-available field is set to 1 for silent notifications that wake your app in the background without user interaction. The category field maps to notification action categories you’ve defined in your iOS app. The mutable-content field allows your app’s notification service extension to modify the notification before it’s displayed. Any custom data fields you include are available to your app when the notification is received.
For GCM/FCM (Android), the structure is different. FCM supports two types of messages: notification messages (which FCM automatically displays) and data messages (which your app must handle). For a data message:
{
"data": {
"message": "You have a new message",
"sender": "Sarah",
"timestamp": "1234567890"
}
}And for a notification message with data:
{
"notification": {
"title": "New Message",
"body": "You have a new message from Sarah",
"sound": "default",
"click_action": "OPEN_MESSAGE"
},
"data": {
"sender": "Sarah",
"timestamp": "1234567890"
}
}The key difference is that notification messages are displayed automatically by the Android system, while data messages are delivered directly to your application code. Many developers prefer data messages because they offer more control over how notifications are displayed and handled.
A complete SNS publish call with platform-specific payloads might look like this in Python using the boto3 SDK:
import json
import boto3
sns = boto3.client('sns')
message = {
"default": "New notification for you",
"APNS": json.dumps({
"aps": {
"alert": {
"title": "New Order",
"body": "Your order #12345 has been confirmed"
},
"sound": "default",
"badge": 1
}
}),
"GCM": json.dumps({
"notification": {
"title": "New Order",
"body": "Your order #12345 has been confirmed",
"sound": "default"
},
"data": {
"order_id": "12345"
}
})
}
response = sns.publish(
TargetArn='arn:aws:sns:us-east-1:123456789012:endpoint/APNS/MyIOSApp/...',
Message=json.dumps(message),
MessageStructure='json'
)Notice how we’re serializing the platform-specific payloads as JSON strings within the outer message structure. This is the correct format for SNS.
Managing Device Token Expiration and Errors#
Device tokens don’t live forever. On both APNs and FCM, tokens can expire or become invalid if a user uninstalls your app, disables notifications, or hasn’t used the app for an extended period. When you try to send a notification to an invalid token, the push notification service will reject it and inform SNS of the failure.
SNS provides feedback mechanisms to help you keep your endpoint list clean. When you create a platform application, you can configure SNS to send failure feedback to an SNS topic. Each failed send attempt can trigger a message to that feedback topic, allowing you to process failures and invalidate endpoints.
To set this up:
aws sns set-platform-application-attributes \
--platform-application-arn arn:aws:sns:us-east-1:123456789012:app/APNS/MyIOSApp \
--attributes EventEndpointDeleted=arn:aws:sns:us-east-1:123456789012:topic-for-failuresWhen SNS receives permanent failure feedback from APNs or FCM (such as “invalid token” or “uninstalled app”), it automatically deletes the endpoint and can send a notification to your feedback topic. This allows you to stay in sync with the actual list of valid devices.
However, not all failures are permanent. Sometimes a device is temporarily offline or the network is unavailable. SNS will retry failed deliveries automatically, but if a device consistently fails to receive messages after multiple retries, it’s eventually disabled. You can manually check the status of an endpoint using the get-endpoint-attributes command to see if it’s been marked as disabled:
aws sns get-endpoint-attributes \
--endpoint-arn arn:aws:sns:us-east-1:123456789012:endpoint/APNS/MyIOSApp/...The response will include attributes like Enabled (true or false). If you discover an endpoint is disabled, you might want to either delete it from your system or attempt to get the user to re-register their device.
A best practice is to implement a cleanup routine that periodically removes disabled endpoints from your database. You should also listen to your feedback topic and handle failures programmatically, removing invalid tokens so you’re not repeatedly trying to send to dead endpoints.
Understanding Cost Implications#
SNS charges for push notifications based on the number of notifications published. As of this writing, the pricing structure includes a free tier (the first one million requests per month across all SNS operations are free), followed by per-request charges that vary slightly by region but typically range from around $0.50 per million requests for standard operations.
This means that if you’re sending push notifications to thousands of users, the costs can add up quickly. A message published to a topic with 100,000 subscribed endpoints results in 100,000 billable requests. If you’re sending notifications multiple times daily to a large user base, you could easily be looking at millions of requests per month.
This has practical implications for your notification strategy. High-frequency, low-priority notifications (like engagement nudges) should be carefully considered. You might want to implement user preferences that allow subscribers to opt out of certain notification types or reduce frequency. Batching notifications or using quiet hours (where you buffer notifications and send them during optimal times) can help reduce your overall request volume.
Additionally, keep in mind that each device token that appears in your endpoint list represents a potential billable request. Maintaining a clean list of valid endpoints without disabled or invalid tokens is both a reliability concern and a cost optimization measure.
Practical Considerations and Error Handling#
In production systems, you’ll want to handle various error scenarios gracefully. When publishing fails, SNS returns error codes that indicate what went wrong. Common errors include:
InvalidParameterException typically indicates malformed input—perhaps your JSON payload has syntax errors or your target ARN is incorrectly formatted. Always validate your inputs before sending.
NotFound errors mean the endpoint or topic doesn’t exist. This might happen if your code is using cached ARNs from an earlier session, and an endpoint has been deleted since then. Implement retry logic with exponential backoff, but also consider whether your endpoint references are stale.
InvalidPlatformToken indicates the device token format is invalid. This can happen if somehow a malformed token was stored in your database.
Throttling errors suggest you’re publishing too rapidly. SNS has rate limits, and if you’re batch-publishing to many endpoints simultaneously, you might hit them. Implement rate limiting and batching on your side.
A robust implementation includes structured error handling. Rather than simply failing a publish operation, you might want to:
Log the error with context (which endpoint, which message, which user) so you can investigate later.
Retry transient failures with exponential backoff to handle temporary network issues.
Quarantine endpoints that consistently fail, preventing repeated failed attempts.
Monitor and alert on high failure rates, which might indicate problems with your APNs or FCM credentials.
Putting It All Together: A Complete Workflow#
Let’s walk through a realistic workflow from start to finish. Imagine you’re building an order notification system for an e-commerce app.
First, you’d register your applications with SNS during your infrastructure setup, storing the platform application ARNs in your configuration or a parameter store.
When a user installs your app and grants notification permission, your mobile app requests a device token from APNs or FCM and sends it to your backend. Your backend receives this token and creates a platform endpoint in SNS, storing the returned endpoint ARN in your user database.
When an order is placed, your backend publishes a message to the appropriate platform endpoint (or to a topic if you’re notifying multiple users):
def send_order_notification(user_id, order_id, order_details):
# Retrieve the user's endpoint ARN from your database
endpoint_arn = db.get_user_endpoint_arn(user_id)
if not endpoint_arn:
# User hasn't registered a device yet
return
message = {
"default": f"Order {order_id} confirmed",
"APNS": json.dumps({
"aps": {
"alert": {
"title": "Order Confirmed",
"body": f"Order {order_id} has been confirmed"
},
"sound": "default",
"badge": 1
},
"order_id": order_id
}),
"GCM": json.dumps({
"notification": {
"title": "Order Confirmed",
"body": f"Order {order_id} has been confirmed"
},
"data": {
"order_id": order_id
}
})
}
try:
sns.publish(
TargetArn=endpoint_arn,
Message=json.dumps(message),
MessageStructure='json'
)
except sns.exceptions.NotFound:
# Endpoint doesn't exist; remove it from the database
db.delete_endpoint(user_id)
except sns.exceptions.ClientError as e:
# Log the error and potentially quarantine the endpoint
logger.error(f"Failed to send notification to {user_id}: {e}")Meanwhile, you’ve configured SNS to send failure feedback to a topic, and you have a background job that processes those failures and cleans up your endpoint database.
This workflow ensures that your users get timely notifications while you’re actively managing the health of your endpoint list.
Conclusion#
SNS simplifies the complex task of sending push notifications to iOS and Android devices by abstracting away the platform-specific details of APNs and FCM. By registering your applications, creating platform endpoints for individual devices, and publishing well-formatted messages, you can reach your users reliably at scale.
The key to building a robust notification system is understanding the complete lifecycle: registering applications with proper credentials, creating endpoints when users enable notifications, publishing platform-specific payloads, monitoring failures, and maintaining a clean endpoint list. Pay attention to cost implications, especially if you’re sending high volumes of notifications, and implement proper error handling so your notification system remains resilient in the face of transient failures.
With these fundamentals in place, you’ll be well-equipped to build notification systems that keep your users informed, engaged, and satisfied.