courier-astro-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.
- An Astro app with the React integration, and Node 20.9 or newer
- Somewhere to read the signed-in user, such as Auth.js, 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:4321, 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.
node is the adapter. The token endpoint runs per request, so the site needs a server.Put your key in .env, which Astro loads in dev and build..env
2
Declare the key as a server secret
Astro reads a secret from the environment at runtime. Everything else it inlines at build time.
astro.config.mjs
access: "secret" is the line that matters. Reading import.meta.env.COURIER_API_KEY instead compiles to the key’s build-time value as a string literal, which ships your key inside the bundle you deploy.optional: true lets the endpoint return its own message for a missing key. Without it Astro fails the schema before the server starts.3
Add an endpoint that mints the token
Your API key signs the JWT, so this runs on the server and never in the browser.
src/pages/api/courier/token.ts
export const prerender = false is required. Astro prerenders by default, and a token minted at build time is the same token for every visitor.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 endpoint calls , and the two inbox: scopes are the minimum for a working feed. lists what to add for preferences and push.4
Render the inbox in a React island
Fetch the token, call Effects run twice in development. A second
signIn, then mount CourierInbox.src/components/CourierInbox.tsx
signIn signs the first user out again, so guard it with a ref.5
Hydrate it with client:only
The inbox renders as a custom element, so it has nothing to draw on the server.
src/pages/index.astro
CourierInbox fills its container’s width and takes its height from the parent, so size the parent.On React 17, install
@trycourier/courier-react-17 instead. The API is identical.6
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.Deploy
astro dev and astro build read .env. The built server does not, so set the key wherever you host it.
@astrojs/node for that host’s adapter and set COURIER_API_KEY in its dashboard. Nothing in the two files changes.
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 the page's frontmatter instead?
Can I mint the token in the page's frontmatter instead?
Minting it in frontmatter works, and passing it to the island as a prop saves a round trip. The page then needs
prerender = false too. An endpoint is shown here because it is also what you call again when the token expires.Do I need an adapter?
Do I need an adapter?
An adapter is needed for the endpoint. A fully static Astro site has nowhere to sign a JWT, and your API key cannot go in the browser to do it there.
Can the rest of my site stay static?
Can the rest of my site stay static?
prerender = false is per route, so only the token endpoint runs on demand. The page holding the inbox can prerender.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 .