Most Popular
Courier makes it easy to send SMS, email, push and in-app notifications with a single API call. We have a REST API, client SDKs and Docs that you'll love 💜
Sign-up
Today we launched Courier Inbox, a set of APIs and UI components for building a modern application inbox. Inbox components are available for both web, iOS and Android applications and are totally customizable so that devs can build a notification inbox that feels like a native part of the rest of the application.
To test this proposition, we decided to use Courier Inbox to build something lightyears away from a product inbox. I thought long and hard about what to build and, perhaps sensing my creative block, my dog Otto came up and asked for this afternoon walk.
And just like that, Puppygram was born! Puppgram is an Instagram clone that is built for iOS and is powered by Courier Inbox, Next.js and Inngest. In this blog post we’re going to cover:
There are a few prerequisites for completing this tutorial:
When we're done, we'll have an app that looks like this:
You can find the full source code for the Puppygram Server and Puppygram iOS app on Github and a live demo of this app hosted on Vercel.
In order to build a Next.js app, you’ll need to have Node.js installed. My preference these days is to use NVM (Node Version Manager) to install Node.js. It makes it easy to install multiple versions of Node.js and switch between them in your projects.
Once you’ve installed Node.js, open up a terminal and run the following command to install Next.js:
1npx create-next-app@latest
You’ll be prompted to answer several questions, but it’s fine to stick to the defaults. Once this process is complete, a new directory will be created and loaded with all of the default files for this app.
Change into this new directory and create a .env.local
file to store secrets for Courier and Inngest. We’ll populate this file while we’re building and testing on localhost, and you’ll just need to remember to copy these environment variables to whatever platform or infra you deploy your app to.
Log-in to your Courier account and click on the gear icon and then API Keys. When you create a Courier account, we automatically create two Workspaces for you, one for testing and one for production. Each workspace has its own set of data and API keys.
For simplicity, we’re going to stick to the “production” workspace. Copy the “published” production API Key and paste into into .env.local
using the following key:
1COURIER_AUTH_TOKEN=pk_XXX
Click on “Channels” in the left nav. Channels represent the different mediums that a user can receive a notification on. Courier supports all the most popular channels, including SMS, email, mobile push and others. For each channel, you’ll see a list of Providers. For instance, Courier supports multiple SMS providers, including Twilio, MessageBird and Vonage just to name a few.
In our case, scroll down and select the “Courier Inbox” provider. When the page opens, scroll to the bottom and click “install”. You are now ready to send notifications to the “inbox” channel using the “Courier Inbox” provider.
Courier Inbox is primarily designed to display notifications to individual users, but in our case the Inbox and its content will be viewed by anyone using the app. Still, we need to create a “user” to send these notifications to, so click on “Users” in the left navigation and create a new user.
The only required field is user_id
, so just go ahead and enter puppygram
and click “Save”. In your .env.local
, create the following variable:
1NEXT_PUBLIC_COURIER_USER=puppygram
Inngest is a workflow-as-code service that makes it easy to build reliable serverless workflows in your current codebase, without any new infrastructure. We’re going to use Inngest to create a cron job that sends puppy pics to our app once every minute.
Log-in to your Inngest account and click on “Manage” in the top nav. Click on “Event Keys” in the subnav.
Event Keys are used to bundle similar events. This helps when navigating the Inngest dashboard, debugging, etc. The recommendation is that you create a new Event Key for every combination of environment and application. In our case, we’re just going to use the “Default ingest key”, but you can easily create new Event Keys to use. Copy the value for this key and paste it into your app’s .env.local
file:
1INNGEST_EVENT_KEY=xxx
Next, click on “Signing Key”. The Signing Key is used by the Inngest SDK to securely communicate with your application. Copy the value for this key and paste it into your app’s .env.local
file:
1INNGEST_SIGNING_KEY=signkey-prod-yyy
Ok, now that we have our services and configuration out of the way, let’s dive into the code. We’re going to start by building the server-side web application that is responsible for sending pictures of puppies to our iOS app. Our web application will be designed to:
With just a few lines of code, we are going to wire up our web application to Inngest so that the service can call into our application once a minute to trigger a new notification to our Courier Inbox. We’re going to breeze through their Quickstart, which you can review in more detail later.
In the root of your project, run the following command to install the Inngest SDK:
1npm install inngest
Create a new directory called inngest
in your project root. Create a file called client.js
in this new directory:
1import { Inngest } from "inngest";23// Create a client to send and receive events4export const inngest = new Inngest({ name: "Puppygram Next.js" });
Now create a route handler to handle the /api/inngest
route. Create a directory in app
called api
and a directory in api
called inngest
. Create a file called route.js
:
1import { serve } from "inngest/next";2import { inngest } from "../../../inngest/client";34export const { GET, POST, PUT } = serve(inngest, []);
Finally, let’s create an Inngest function that prints “Hello Puppygram” every minute. Edit route.js
and paste this function just below the imports:
1export const sendNotification = inngest.createFunction(2{ name: "Puppygram Send Notification" },3{ cron: "* * * * *" },4async () => {5console.log(“Hello Puppygram!”)6}7)
Now, update the serve
call at the bottom of the file to include this new Inngest function that has been created:
1export const { GET, POST, PUT } = serve(inngest, [2sendNotification3])
In your terminal, go ahead and start your web application:
1npm run dev
Open up another terminal and run:
1npx inngest-cli@latest dev
This will execute a localhost version of the Inngest service. This will connect to your web application (running on port 3000) and begin executing your cron job. Go back to the terminal you launched your web app in, and every minute you should see this print out:
1Hello Puppygram!
Thanks to our friends at the Random Dog API, we have a service that we can use to get pictures of very cute, very random dogs. The API supports 3 different endpoints:
/woof
- return the ID of a random dog/woof.json
- return a JSON payload of a random dog/doggos
- return JSON array of all dog IDsSince our service is going to wake up once a minute to send a notification with a picture of a random dog, we didn’t want to burden this free service with all those API calls. So instead we invoked the /doggos
endpoint and copied that information into a file in our project.
Create a directory at the project root called data
and a file in it called doggos.json
. Paste the following into that file:
1[2"00186969-c51d-462b-948b-30a7e1735908.jpg",3"00b417af-0b5f-42d7-9ad0-6aab6c3db491.jpg",4"027eef85-ccc1-4a66-8967-5d74f34c8bb4.jpg",5"02f1d7d0-9ff7-44af-8066-dd9247ebe74d.jpg",6"03024628-188b-408e-a853-d97c9f04f903.jpg",7"0356c15a-8874-4af3-a02a-ed0ae8d62b55.jpg"8]
Now, let’s update our sendNotification
function in app/api/inngest/routes.js
to pick a random image and print it out to the console. Add the following import to the top of the file:
1import doggos from '../../../data/doggos.json' assert { type: 'json' }
Update the function to select a random image from the array and print it out:
1export const sendNotification = inngest.createFunction(2{ name: "Puppygram Send Notification" },3{ cron: "* * * * *" },4async () => {5// get random dog photo6const randomIndex = Math.floor(Math.random() * doggos.length)7const image = doggos[randomIndex]8console.log(image)9}10)
Reset your Next.js dev server, and you should see random image filenames being printed out once a minute. Now it’s time to send those to Courier Inbox!
The final part of the server-side component of Puppygram is the code to send the random dog pic to Courier Inbox. Luckily, this is exceedingly easy.
First, let’s add the Courier Node.js SDK to our project:
1npm i @trycourier/courier
Next, import the module and initialize the API client at the top of route.js
:
1import { CourierClient } from '@trycourier/courier'23const courier = CourierClient()
Finally, in the body of our sendNotification
function, make the API call to Courier:
1export const sendNotification = inngest.createFunction(2{ name: "Puppygram Send Notification" },3{ cron: "* * * * *" },4async () => {5// get random dog photo6const randomIndex = Math.floor(Math.random() * doggos.length)7const image = doggos[randomIndex]8// send a notification with the URL to the random image9await courier.send({10message: {11to: {12user_id: process.env.NEXT_PUBLIC_COURIER_USER13},14content: {15title: "",16body: "Meet my fluffy and playful partner in crime, always ready for some mischief! 😍🐾 #AdorableTroublemaker"17},18data: {19image_url: `https://random.dog/${image}`20},21routing: {22method: "single",23channels: [24"inbox"25]26}27}28})29}30)
You’re done! Now, all you need to do is deploy this web app (there are many options for deploying a Next.js application) and to deploy your serverless function to Inngest. Make sure your web application is fully deployed and live before you deploy the function to Inngest.
Now, let’s turn our attention to building the iOS app
In order to build an iOS app, you’ll need to install Xcode. I’ll wait…
< 2 hours later >
Hey, welcome back! Ok, now that you’ve downloaded several gigabytes of IDE, let's start building an iOS app!
Create a new Project, select "iOS" and "App" and click "Next".
Give it the name "Puppygram", make sure "SwiftUI" is selected and click "Next".
Create a new folder for your project and then click "Create".
Now that you've created your project, let's install the Courier iOS SDK.
Select the "Puppygram" project in the top left of your application nav and click "Package Dependencies" on the right. Click the "+" sign at the bottom and you'll be prompted to add a dependency. Paste this link in:
1https://github.com/trycourier/courier-ios
Click "Add Package" to add this to your project. That's it! You now have the power of Courier at your fingertips.
For the purpose of this blog post, we're going to build a single screen app that has a list view that updates in real-time to display the notifications we are sending the app.
In your project, open up the ContentView
file. At the top of the file, import the Courier iOS SDK:
1import Courier_iOS
Underneath the ContentView declaration, create a messages
instance variable to hold the Inbox messages that we receive from Courier:
1struct ContentView: View {2@State private var messages: [InboxMessage] = []3var body: some View {
Immediately following the .padding()
call, add an onAppear
event handler with the following code.
1.padding()2.onAppear{3Task {4try await5// Sign-in to Courier6Courier.shared.signIn(accessToken:"<JWT TOKEN>",clientKey:"<COURIER_CLIENT_KEY>",userId:"<NEXT_PUBLIC_COURIER_USER>")7// Add an Inbox listener8Courier.shared.addInboxListener(9onInitialLoad: { },10onError: { error in },11onMessagesChanged: { messages, unreadMessageCount, totalMessageCount, canPaginate in12// update the messages array when new messages come in13self.messages = messages14}15)16}17}
This code signs the user into Courier and wires up an event handler to update the messages
instance variable when new messages come in. In order to get the Courier.shared.signIn
call working properly, you need to replace those 3 values. Let's go one by one.
Replace <NEXT_PUBLIC_COURIER_USER>
with the to
value you are using in your web app when you send a notification using Courier. In our case, the value is puppygram
.
Replace<COURIER_CLIENT_KEY>
with the value of the Courier Public Key for your Courier Inbox Provider. This key is completely safe to use on the client, in both web and mobile apps.
Finally, we need to set the accessToken
property. In order to do this safely, it is recommended that developers generate JWTs for their users that specify scope and expiration information. For the purposes of this tutorial, we're going to use the Courier API to generate a JWT on the command line. Open up a terminal, and run the following command substituting your values for <NEXT_PUBLIC_COURIER_USER>
and <COURIER_AUTH_TOKEN>
:
1curl --request POST \2--url https://api.courier.com/auth/issue-token \3--header 'Accept: application/json' \4--header 'Authorization: Bearer <COURIER_AUTH_TOKEN>' \5--header 'Content-Type: application/json' \6--data '7{8"scope": "user_id:<NEXT_PUBLIC_COURIER_USER> read:messages",9"expires_in": "2 days"10}11'
You'll get back some JSON that looks like this:
1{"token":"eyzzz"}
Copy the value of of the token
property and paste it into the authToken
parameter for Courier.shared.signIn
. You're are totally authenticated!
Now that we're authenticated, the last step for our app is to display the messages. Delete the code below:
1Image(systemName: "globe")2.imageScale(.large)3.foregroundColor(.accentColor)4Text("Hello, world!")
Replace it with this:
1List {2ForEach(messages, id: \.self) { message in3AsyncImage(url: URL(string: message.imageUrl)){ image in4image.resizable()5} placeholder: {6ProgressView()7}8.frame(width: 300, height: 300)9.clipShape(RoundedRectangle(cornerRadius: 25))10VStack {11Text(message.subtitle ?? "")12}13}14}
This is the simplest way to define a List
and the elements inside of it. For now, we're just going to just display the image (passed-in via the custom data
field of the REST API call) and the image description which we are passing in the body
field of the REST API call.
Important Note: In the iOS SDK, the subtitle
property of the message
object maps to the value of body
in the REST API call.
Now, if you try to build this it will fail. This is because we need to tell the iOS SDK that we're passing-in a custom field in the REST API call. You can do this by extending the InboxMessage class. Just paste this code at the top of your file, just below the imports:
1extension InboxMessage {2var imageUrl: String {3get {4return (data?["image_url"] as? String) ?? ""5}6}7}
If all goes well, you can press "play" and you should see something like this running in the simulator:
Ok, we covered a LOT of territory building an Instagram clone for cute dogs. We learned how to:
With a little UI polish, you can render the data and end up with an app that looks and feels like this:
Whether you're building Kittygram, Bunnygram or a modern application inbox, Courier Inbox, provides a flexible set of APIs and UI components to help you build exactly what you need. And don't forget, while we focused on iOS, Inbox UI components are available for both web and Android applications too.
Full source code for the Puppygram Server and Puppygram iOS app on Github and a live demo of this app hosted on Vercel. Enjoy!
Courier makes it easy to send SMS, email, push and in-app notifications with a single API call. We have a REST API, client SDKs and Docs that you'll love 💜
Sign-up
How to Set Up Automatic Push Notifications Based on Segment Events
Push notifications have carved their own niche as a powerful tool for continuous user engagement. Regardless of whether an app is actively in use, they deliver your messages straight to your user's device. Two key players that can combine to enhance your push notification strategy are Segment and Courier. In this tutorial, we show you how to set up Courier to listen to your Segment events and then send push notifications to an Android device based on data from these events.
Sarah Barber
November 17, 2023
How to Send Firebase Notifications to iOS Devices Using Courier
This tutorial explains how to send push notifications to iOS devices from your iOS application code using Firebase FCM and Courier’s iOS SDK.
Martina Caccamo
November 01, 2023
Free Tools
Comparison Guides
Send up to 10,000 notifications every month, for free.
Get started for free
Send up to 10,000 notifications every month, for free.
Get started for free
© 2024 Courier. All rights reserved.