I built this demo because I wanted a small, trustworthy way to understand the OptaReach ai API boundary without guessing what happens behind the scenes. The goal was simple: fetch campaigns, choose one, send only documented prompt inputs, and inspect the generated message-template output in a way that stays transparent the whole time.
That sounds straightforward, but integration demos often drift into “magic” very quickly. They hide the request payload, blur server and client responsibilities, or quietly assume that every workspace has the same shape of data. This project takes the opposite approach. It keeps the boundary explicit, uses server-side fetching, and treats error states as part of the design rather than an afterthought.
The repository is public here: View the repository
What I set out to demonstrate
The project is a small Next.js app that focuses on one OptaReach workflow: campaign message-template generation. The intent is not to build a full campaign manager. It is to show the path from campaign discovery to generated template output with as little indirection as possible.
That workflow is visible in the page itself:
- load campaigns from OptaReach
- show a selection-oriented view
- prepare prompt inputs such as objective, tone, and context notes
- generate a template from the selected campaign
- render the returned output without hiding it behind extra app logic
I like this kind of demo because it teaches the integration shape directly. If you are evaluating the OptaReach API for campaign workflows, you can inspect the code and quickly see what the app does and does not do.
Architecture: a server-rendered page with a thin API client
The structure is deliberately compact. There are only two real application layers:
-
app/page.tsxhandles the server-rendered UI and orchestrates data loading -
lib/optareach.tswraps the API requests and error mapping
The app uses the Next.js App Router, which fits the project goal well because the initial campaign fetch happens server-side. That keeps credentials out of the browser and makes the first render reflect real API state instead of a client-side loading illusion.
A concise view of the project tree looks like this:
app/
layout.tsx
page.tsx
globals.css
lib/
optareach.ts
tests/
optareach.test.ts
.env.example
README.md
package.json
The client wrapper is intentionally narrow. In lib/optareach.ts, the app only talks to two documented endpoints:
GET /api/v1/platforms/{platform}/campaignsPOST /api/v1/platforms/{platform}/campaigns/{campaignId}/message-template/generate
That implementation choice matters. The code does not invent a wider SDK abstraction or try to normalize the API into something it is not. It just wraps the documented calls and maps failures into readable error kinds.
How the OptaReach integration works
The most useful part of this demo is that it keeps the request boundary visible. The API helper defines the request shape for message-template generation as three fields:
objectivetonecontextNotes
That strictness is a feature, not a limitation. In generateMessageTemplate, the request body is serialized exactly as provided, and the surrounding comment makes the boundary explicit:
// API boundary: only the documented request body fields are sent to OptaReach.
I think that kind of comment is valuable in an integration demo because it tells future readers what is intentional. The project is not trying to infer undocumented payloads or sneak in extra defaults. It is showing the smallest valid integration surface.
Another important choice is the error mapping. The helper converts HTTP status codes into a small set of categories:
- validation
- permission
- unavailable
- rate_limit
- upstream
- network
That gives the UI a vocabulary for meaningful feedback. A 422 validation problem is different from a 403 permission issue, and the app treats them that way instead of collapsing everything into “something went wrong.”
The page component then uses those categories to render a readable state. That’s the kind of behavior I wanted to demonstrate with Sportmicro: not just that the request succeeds, but that the integration remains understandable when it doesn’t.
Implementation flow in the page
The app/page.tsx file follows a simple flow that mirrors the project goal.
First, it tries to load campaigns for a fixed platform value:
const platform = 'multi-platform';
That fixed platform keeps the demo focused. It avoids adding a selector or routing complexity before the core workflow is proven.
Next, it calls fetchCampaigns(platform) and separates the happy path from the error path. If an error occurs, the page captures it and shows a readable status message. If no campaigns are returned, the UI shows an empty state instead of pretending the workspace is ready.
Then, if a campaign is available, the page attempts a preview generation call using the first campaign’s ID and a small request object:
- objective:
Demo preview - tone:
clear - context notes: a short note about the server-side boundary
That preview is wrapped in a try/catch block, and failures simply result in no preview being shown. That is a sensible choice for a demo because it keeps the page usable even when generated output is unavailable.
The UI itself is intentionally modest:
- a campaign selector card
- a template generation form
- a generated output preview section
- an implementation notes section
The form is presentational in this repository. The button is disabled, which makes the boundary clear: the app demonstrates the integration structure, but it does not pretend to be a fully wired production workflow in the local demo state.
Challenges and trade-offs
I did not treat this as a “solve everything” build. The main design constraints were clarity, trust, and API honesty.
A few trade-offs show up clearly in the repository:
-
Fixed platform value: keeping
multi-platformhard-coded reduces complexity, but it also means the demo stays focused on one integration shape rather than becoming a general platform switcher. - Server-side fetching: this protects credentials and makes the data boundary clean, but it also means the app is designed around server-rendered behavior instead of a fully client-driven interaction model.
- Minimal form state: the UI shows prompt inputs, but the repository does not wire a complete interactive generation flow in the local demo state. That keeps the example small and explicit, though it leaves room for a richer connected experience later.
Those constraints are reasonable for a project that wants to be inspectable. I would rather have a demo that is limited but honest than a broad example that silently blurs documented API behavior.
Local setup and what the repository supports
The repository includes enough evidence to describe local setup confidently. The README and package.json show a standard Next.js workflow:
npm install
cp .env.example .env.local
npm run dev
A production build is also supported:
npm run build
The environment setup uses OPTAREACH_API_KEY for authenticated server-side requests, and the repository makes it clear that the key is not printed or exposed to the browser.
That is enough to run the app locally and inspect its empty-state behavior, campaign-loading path, and API-client structure. I would keep expectations modest here: the repo is a demo, not a fully scripted deployment guide, so the supported setup is intentionally lightweight.
What I would improve next
If I were extending this project, I would keep the same philosophy and add only changes that preserve the documented boundary.
Some sensible next steps:
- wire the form to a real submission flow in the connected deployment
- persist the selected campaign instead of always using the first loaded campaign
- add a copy-to-clipboard action for generated template output
- render more of the returned message-template shape once the exact response structure is confirmed
- expand tests around the API client and the error mapper
I would also keep the documentation discipline intact. Any future enhancement should still be tied directly to the documented OptaReach endpoints rather than a guessed workflow.
Takeaway
The biggest lesson from this build is that a good API demo should make the integration feel obvious. You should be able to tell where the request starts, what is sent, how errors are classified, and what the UI does when data is missing.
This project does that well by staying small, server-side, and explicit. It does not try to impress with abstraction. Instead, it shows how to build a clean bridge between a Next.js app and the OptaReach campaign message-template generation flow, which is exactly what I wanted from the start.
Top comments (0)