courier-nextjs-quickstart
The finished app from this guide, ready to clone and run.
Prerequisites
- , from any environment. Every one ships with the Courier Inbox provider already configured.
- A Next.js app on the App Router, and Node 20.9 or newer
- Somewhere to read the signed-in user, such as NextAuth, Clerk, or your own session cookie
Clone the finished app
Skip the steps below if you would rather read working code. This repo is this guide, already assembled.localhost:3000, then run npm run send in a second terminal. The message arrives in the open page without a refresh.
Add an inbox to your app
1
Install the packages
The React package renders the inbox. The server package mints tokens.Put your key in
.env.local, which Next loads automatically..env.local
2
Add a route handler that mints the token
Your API key signs the JWT, so this runs on the server and never in the browser.Take the user id from your session, never from the request body or a query parameter. A caller who can name any user can read that user’s inbox.The route calls , and the two
app/api/courier/token/route.ts
inbox: scopes are the minimum for a working feed. lists what to add for preferences and push.3
Render the inbox in a client component
Fetch the token, call Two details are specific to Next.js and React:
signIn, then mount CourierInbox.components/courier-inbox.tsx
"use client"is required. The component uses hooks, and the inbox renders as a custom element that only exists in the browser.- Effects run twice in development. A second
signInsigns the first user out again, so guard it with a ref.
4
Give the inbox a height
CourierInbox fills its container’s width and takes its height from the parent, so size the parent.app/page.tsx
On React 17, install
@trycourier/courier-react-17 instead. The API is identical.5
Send a message to the inbox
inbox is a channel like email or sms. Including it in routing.channels is what puts the message in the feed.scripts/send.ts
content here is the shorthand form, which takes a title and a body. For multi-element layouts and per-channel variants, use a instead and reference it by id.You do not need ssr: false
Wrapping the inbox in next/dynamic with ssr: false is a common reflex with component libraries built on custom elements. Courier’s SDK does not need it.
The element base class resolves to a stub under Node, and element registration is guarded on typeof window. Importing the package on the server is safe, so the page still prerenders and only the inbox hydrates in the browser.
"use client" is the whole requirement. Adding ssr: false costs you the prerender and buys nothing.
Keep the session alive
The SDKs do not refresh tokens. Before the current one expires, mint a new JWT and callsignIn again with it.
Match expires_in to your own session length. A short-lived token can expire while a tab stays open, which quietly empties the inbox.
Verify
1
Load the page
The inbox renders, signed in and empty.
2
Send a message
Run the send against the same
user_id your token was scoped to.3
Watch it arrive
The message appears in the open page in real time, and the unread count moves.
FAQ
Can I mint the token in a Server Component instead?
Can I mint the token in a Server Component instead?
Minting it in a Server Component works, and passing it to the client component as a prop saves a round trip. A route handler is shown here because it is also what you call again when the token expires.
Does this work with the Pages Router?
Does this work with the Pages Router?
Mint the token in
pages/api/courier/token.ts and render the inbox in any component. "use client" is an App Router directive, and the Pages Router ignores it.Is the JWT safe to expose to the browser?
Is the JWT safe to expose to the browser?
The JWT is built for the browser. It is scoped to one user and it expires. Your API key, which signs it, stays on the server.
Why is the inbox empty when the send reported success?
Why is the inbox empty when the send reported success?
The feed is filtered by tenant. Signing in with a
tenantId hides messages sent without one. See .