Platform
Docs
Solutions
ContactLog In

Create Your Free Developer Account

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

puppygram-header
TUTORIAL

Introducing Puppygram, powered by Courier Inbox, Next.js and Inngest

Carter Rabasa

June 13, 2023

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.

otto-puppygram

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:

  • Creating a fresh Next.js web application
  • Configuring Courier to send Inbox notifications
  • Fetching random pictures of cute dogs using the random.dog API
  • Using Inngest to send a new dog notification to our Inbox every minute
  • Creating the Puppygram iOS experience using the Courier iOS SDK

There are a few prerequisites for completing this tutorial:

When we're done, we'll have an app that looks like this:

Puppygram screen shot

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.

Creating a Next.js web application

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:

1
npx 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.

Get Courier API Credentials

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:

1
COURIER_AUTH_TOKEN=pk_XXX

Configure Your Courier Inbox Provider

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.

Creating a Public Inbox

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:

1
NEXT_PUBLIC_COURIER_USER=puppygram

Get Inngest Credentials

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:

1
INNGEST_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:

1
INNGEST_SIGNING_KEY=signkey-prod-yyy

Let’s Start Coding!

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:

  • Wake up once a minute
  • Select a random picture of a dog
  • Send an inbox notification with the URL of the picture of the random dog

Using Inngest to Schedule Jobs

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:

1
npm install inngest

Create a new directory called inngest in your project root. Create a file called client.js in this new directory:

1
import { Inngest } from "inngest";
2
3
// Create a client to send and receive events
4
export 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:

1
import { serve } from "inngest/next";
2
import { inngest } from "../../../inngest/client";
3
4
export 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:

1
export const sendNotification = inngest.createFunction(
2
{ name: "Puppygram Send Notification" },
3
{ cron: "* * * * *" },
4
async () => {
5
console.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:

1
export const { GET, POST, PUT } = serve(inngest, [
2
sendNotification
3
])

In your terminal, go ahead and start your web application:

1
npm run dev

Open up another terminal and run:

1
npx 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:

1
Hello Puppygram!

Getting Pictures of Random Dogs

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 IDs

Since 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:

1
import doggos from '../../../data/doggos.json' assert { type: 'json' }

Update the function to select a random image from the array and print it out:

1
export const sendNotification = inngest.createFunction(
2
{ name: "Puppygram Send Notification" },
3
{ cron: "* * * * *" },
4
async () => {
5
// get random dog photo
6
const randomIndex = Math.floor(Math.random() * doggos.length)
7
const image = doggos[randomIndex]
8
console.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!

Sending Random Dog Pics 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:

1
npm i @trycourier/courier

Next, import the module and initialize the API client at the top of route.js:

1
import { CourierClient } from '@trycourier/courier'
2
3
const courier = CourierClient()

Finally, in the body of our sendNotification function, make the API call to Courier:

1
export const sendNotification = inngest.createFunction(
2
{ name: "Puppygram Send Notification" },
3
{ cron: "* * * * *" },
4
async () => {
5
// get random dog photo
6
const randomIndex = Math.floor(Math.random() * doggos.length)
7
const image = doggos[randomIndex]
8
// send a notification with the URL to the random image
9
await courier.send({
10
message: {
11
to: {
12
user_id: process.env.NEXT_PUBLIC_COURIER_USER
13
},
14
content: {
15
title: "",
16
body: "Meet my fluffy and playful partner in crime, always ready for some mischief! 😍🐾 #AdorableTroublemaker"
17
},
18
data: {
19
image_url: `https://random.dog/${image}`
20
},
21
routing: {
22
method: "single",
23
channels: [
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

Creating an 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 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".

Install the Courier iOS SDK

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:

1
https://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.

Building the View

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:

1
import Courier_iOS

Underneath the ContentView declaration, create a messages instance variable to hold the Inbox messages that we receive from Courier:

1
struct ContentView: View {
2
@State private var messages: [InboxMessage] = []
3
var body: some View {

Immediately following the .padding() call, add an onAppear event handler with the following code.

1
.padding()
2
.onAppear{
3
Task {
4
try await
5
// Sign-in to Courier
6
Courier.shared.signIn(accessToken:"<JWT TOKEN>",clientKey:"<COURIER_CLIENT_KEY>",userId:"<NEXT_PUBLIC_COURIER_USER>")
7
// Add an Inbox listener
8
Courier.shared.addInboxListener(
9
onInitialLoad: { },
10
onError: { error in },
11
onMessagesChanged: { messages, unreadMessageCount, totalMessageCount, canPaginate in
12
// update the messages array when new messages come in
13
self.messages = messages
14
}
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.

Authenticating the iOS SDK

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>:

1
curl --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!

Displaying the Messages

Now that we're authenticated, the last step for our app is to display the messages. Delete the code below:

1
Image(systemName: "globe")
2
.imageScale(.large)
3
.foregroundColor(.accentColor)
4
Text("Hello, world!")

Replace it with this:

1
List {
2
ForEach(messages, id: \.self) { message in
3
AsyncImage(url: URL(string: message.imageUrl)){ image in
4
image.resizable()
5
} placeholder: {
6
ProgressView()
7
}
8
.frame(width: 300, height: 300)
9
.clipShape(RoundedRectangle(cornerRadius: 25))
10
VStack {
11
Text(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:

1
extension InboxMessage {
2
var imageUrl: String {
3
get {
4
return (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:

puppygram-prototype

Wrapping Things Up

Ok, we covered a LOT of territory building an Instagram clone for cute dogs. We learned how to:

  • Create a fresh Next.js web application
  • Configure Courier to send Inbox (in-app) notifications
  • Fetch random pictures of cute dogs using the random.dog API
  • Use Inngest to send a new dog notification to our Inbox every minute
  • Create a fresh iOS app
  • Use the Courier iOS SDK to custom render notifications

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!

Create Your Free Developer Account

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

More from Tutorial

How to set up automatic push notifications based on Segment events thumbnail
TUTORIAL

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

Sarah Barber

November 17, 2023

image14
TUTORIAL

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

Martina Caccamo

November 01, 2023

Build your first notification in minutes

Send up to 10,000 notifications every month, for free.

Get started for free

Email & push notification

Build your first notification in minutes

Send up to 10,000 notifications every month, for free.

Get started for free

Email & push notification

Platform

Users

Content

Channels

Sending

Workflows

Preferences

Inbox

Workspaces

Observability

API Status

Changelog

© 2023 Courier. All rights reserved.