Short answer
RaftLabs built a customer rewards platform for a full-fibre broadband provider in Northern Ireland and Cumbria, reusing the Energia Rewards feature set. The backend moved from Energia's AWS setup to Vercel, Neon, and Drizzle to fit a fixed monthly hosting fee. Customers sign in from the provider's portal with a 10-second, single-use signed token, and a revised contract carries one opaque customer reference and no personal data. The platform is built and on staging but hasn't launched.
Engagement
The engagement
- Client
- A full-fibre broadband provider in Northern Ireland and Cumbria (name withheld)
- Delivered through
- The loyalty agency behind Energia Rewards
- Scope
- Customer rewards web app inside the provider's account portal, admin console, API, and portal sign-in integration
- Team
- One full-stack developer, a project manager, and QA
- Timeline
- Scoping from May 2026; staging live on 18 June 2026; agency and brand feedback rounds from July to September 2026
- Status
- Built and on staging; not yet launched
The situation
The provider wanted what Energia Rewards already gives Energia's customers: partner offers, coupons, and competitions, reached from the account portal customers already use. The brief was feature parity under a new brand. Nothing new.
Two constraints made it more than a rebrand. The agency had to run the program within a fixed monthly hosting fee, and Energia's AWS backend didn't fit that. And the provider's security team didn't want any personal data leaving their systems. Our first sign-in design put the customer's email and name inside a signed token in the redirect URL. We missed it. Their security team caught it.
So we kept Energia's feature set, moved the backend to Vercel and Neon, and rewrote the sign-in contract around one opaque customer reference. The platform is built and on staging. It hasn't launched, so this page covers the decisions behind it. Results come after launch.
Before and after
What carried over from Energia, and what had to change
- The first plan reused Energia's backend: Hasura, AWS Amplify functions, Postgres on AWS, and Cognito
- An import API would have created an account for every portal customer in advance
- Participation reporting would have run through a separate Metabase service
- The first sign-in token carried the customer's email and name in the redirect URL
- A Hono API on Vercel Functions, Neon serverless Postgres, and Drizzle ORM, with no AWS services
- Accounts start on a customer's first click-through, so the platform only holds people who used it
- Participation KPIs live inside the admin console
- A revised sign-in contract carries one opaque reference, signed and encrypted, for the provider's security team to approve
The decisions that shaped the build
Energia's backend didn't fit a fixed monthly fee
The agency's first written concern was cost: it needed to run and monitor the program within a fixed monthly fee. Energia runs on Hasura, AWS Amplify functions, Postgres on AWS, Cognito, and Metabase, a setup sized for a program with roughly 500,000 registered users. The first plan here reused that backend.
On 15 June, ten days into the codebase, we dropped the reuse plan. The backend was recreated on Vercel: a Hono API on Vercel Functions, Neon serverless Postgres, Drizzle ORM, Better Auth for admin sign-in, Vercel Blob for media, and Vercel Cron for an hourly lifecycle job. Energia's 184 schema migrations became one Drizzle schema. No production data existed yet, so the move was schema work only.
Removing Hasura also removed its permission layer. Access checks moved into application code: customer queries scope to the signed-in customer, and admin routes check roles. That was the biggest correctness risk in the switch, and the API's integration tests cover it. The API region sits in London, next to the database.
The first sign-in design put personal data in the URL
Customers reach rewards from the provider's portal and shouldn't sign in twice. The first design had the portal redirect them with a signed token holding their customer ID, email, and name. The token was HMAC-signed, expired after 10 seconds, and worked once.
On a 25 June call, the provider's security team pushed back. Under UK telecoms security rules, their position was that no personal data should leave their systems, and that a visible redirect URL was a concern even without it. Our naive version didn't meet that bar.
Five days later we sent a revised spec. It carries one opaque customer reference and nothing else, signs it with HMAC-SHA256, then encrypts it as a JSON Web Encryption token. It offers three ways for the token to travel: an encrypted token in the URL with an immediate bounce, a POST that keeps it out of the address bar, or a back-channel exchange where the browser only ever carries a spent one-time code. The provider's security team picks the one that fits their standard. It's data minimisation applied to a login.
Accounts start on a customer's first click-through
Energia keeps records for every eligible customer through scheduled joiner and leaver files. The first plan here included an API to pre-load every portal customer the same way.
The agency agreed to drop it on 7 May. The platform creates an account the first time a customer arrives through sign-in and refreshes their last-access time on every visit. It only stores and hosts people who actually used it, and the first-time-active and active-user KPIs count real visits instead of imported rows.
Access stays with the provider. The portal shows the rewards link once a customer's installation is complete and hides it when they leave or are suspended. Sessions last 24 hours, so a removed customer loses access within a day at most.
Reporting moved into the admin console
The agency asked whether KPIs would come through Metabase, as they do on Energia. For a program this size, a separate reporting service meant one more thing to host, size, and maintain inside the fixed fee.
We built the KPIs into the admin console instead: active users, engaged users, first-time active users, reward views and redemptions, competition views and entries, and campaign views and redemptions. The numbers sit next to the rewards they describe, and there's no second system to keep running.
A full component library for the people running the program daily
Operators use the admin to create rewards, campaigns, competitions, and homepage content, then publish and schedule them. Every change needs a confirmation step, and the target is WCAG 2.1 AA.
On earlier projects, building admin screens from minimal shadcn/ui primitives meant hand-building accessibility, browser support, and theming for each control, and AI coding agents burned time on the same gaps. For this admin we chose Mantine, a full component library that already handles those. The customer app keeps Tailwind and shadcn/ui under the provider's brand.
System flow
How a customer gets from the portal to a reward
A simplified view based on the project repository and the SSO specification. It shows the path a provider's security and loyalty teams need to understand. Supporting services are left out.
Entry
Provider's customer portal
Shows the rewards link to eligible customers and signs a short-lived token for each click. The provider controls who sees the link.
Sign-in
SSO route and session
Checks the signature, the 10-second expiry, and a single-use nonce, creates the account on the first visit, and sets a 24-hour session cookie.
API
Hono on Vercel Functions
Handles redemptions, competition entries, coupon allocation, and voucher PDFs, with access checks in application code and an hourly lifecycle job.
Data and operations
Neon, Drizzle, and the admin console
Postgres holds rewards, campaigns, competitions, coupons, and engagement events. Operators manage them and read KPIs in the admin.
the build
What customers and operators get
The platform splits into a customer web app, an admin console, and an API, each deployed as its own Vercel project.
Offers, coupons, and competitions under the provider's brand
Customers browse partner rewards and claim coupons delivered as a text code, a redirect link, or a branded PDF voucher. Coupons can be unique per customer or shared. Competitions can include multiple-choice questions, sponsors can run video content, and a family page filters rewards by Northern Ireland county.
An admin that moves rewards from draft to live to ended
Operators author rewards, campaigns, competitions, brands, categories, and homepage content, then publish or schedule them. Business rules run on the server, and every change asks for confirmation. An hourly job expires finished campaigns, settles closed competitions, and clears spent sign-in nonces. An audit log records what changed.
Participation KPIs without a separate reporting tool
The admin shows active, engaged, and first-time active users alongside views and redemptions for rewards, competitions, and campaigns. Engagement events feed those figures directly, so the numbers operators read come from the same data the customer app writes.
Proof
What exists today, and what this page can't claim yet
The platform hasn't launched, so there are no usage, engagement, or cost results. These are the delivered and recorded facts.
| Result | What changed | Period or context | Evidence and limitation |
|---|---|---|---|
| Delivery speed | Staging live 13 days after the codebase was created | 5 to 18 June 2026 | Project-recorded: repository history |
| Backend | Energia's 184 schema migrations recreated as one Drizzle schema on Neon | Stack change on 15 June 2026 | Project-recorded: re-pivot plan and repository |
| Sign-in | 10-second, single-use signed tokens with replay protection and secret rotation | Current staging build | Project-recorded; the revised no-personal-data contract awaits the provider's security sign-off |
| Hosting model | Backend runs on Vercel and Neon with no AWS services | Staging | Project-recorded; the hosting fee is confidential and not published |
| Outcomes | Not yet available | Before launch | KPIs are built into the admin console and will report once customers arrive |
Timeline
How the build moved
May 2026
Scoping with the agency
Scoping and a technical spec with the agency. Pre-loading every customer and a separate Metabase service came out of scope on 7 May.
5-15 Jun 2026
Started from Energia, then changed the backend
The codebase began from Energia's structure. On 15 June we dropped the AWS backend for Vercel, Neon, and Drizzle.
18-30 Jun 2026
Staging, security review, and a revised sign-in
Staging went live on 18 June and reached the agency on 26 June. After the 25 June security call, the revised sign-in spec went out on 30 June.
Jul-Sep 2026
Agency and brand feedback
Review rounds shaped the customer experience through September. Launch is pending.
The lesson
Take the sign-in design to the client's security team before you write the first token
We reused Energia's feature set, and that part held. The assumption that didn't transfer was identity. A utility rewards program and a telecom one sit under different security expectations, and our first token carried personal data the provider's rules didn't allow.
If you're adding single sign-on from a customer portal into a partner platform, take the payload, the transport, and the revocation model to the client's security team before building. One call reshaped our whole contract. It would have cost less on day one.
Where to go next
What stayed out on purpose
Scope decisions made during the engagement. None of these are part of the current build.
- Next 01
Features beyond Energia parity
The brief was the Energia Rewards feature set under a new brand. Anything new waits for a later phase.
- Next 02
Instant session revocation
Sessions expire after 24 hours for launch. A revocation webhook or a status check can close that gap before voucher campaigns, if the provider wants it.
- Next 03
An in-app entry point
Phase 1 assumes customers arrive from the web portal. An entry inside a mobile app's web view would need the redirects and session cookie reviewed first.
How it runs
- Next.js
- The customer app runs on Next.js with Tailwind and shadcn/ui under the provider's brand. The sign-in route, the session cookie, and a strict nonce-based content security policy live in the same app.
- Hono on Vercel Functions
- A small API framework on Vercel's Node runtime handles redemptions, competition entries, coupons, voucher PDFs, and the hourly lifecycle job, deployed as its own project next to the two apps.
- Neon and Drizzle
- Serverless Postgres with a typed schema replaced Hasura and hosted Postgres on AWS. Access rules moved into application code, scoped by the signed-in customer and the admin's role.
- React, Vite, and Mantine
- The admin console uses a full component library, so tables, filters, forms, and confirmation dialogs behave the same everywhere and meet an accessibility target without hand-building each control.
- Vercel
- Three Vercel projects cover the customer app, admin, and API, with Vercel Blob for media and Vercel Cron for scheduled jobs. One platform keeps hosting inside a fixed monthly fee.
Common questions
The portal signs a short-lived token when the customer clicks the rewards link. The rewards platform checks the signature, rejects tokens older than 10 seconds, and accepts each token only once. On the first visit it creates the account, then sets its own 24-hour session. In the revised design, the token carries one opaque customer reference and nothing else.
Yes, if identity is an opaque reference that only the provider can resolve to a person. The revised design keys every reward, redemption, and entry to that reference and greets customers generically. A right-to-erasure request then comes through the provider, who sends the reference so the platform can delete the activity held against it.
The provider hides the rewards link, so a former customer can't start a new session. An existing session runs out within 24 hours. A customer who later rejoins arrives under a new reference and starts fresh, with no history carried over. For immediate removal, a revocation webhook or status check can be added before voucher campaigns.
Ten seconds covers a redirect, and the window is configurable if testing shows slow devices need more. Both sides need clocks synced with NTP, and the verifier allows about two seconds of clock skew. The shared secret can also rotate: the platform accepts the previous secret during an overlap window, so rotation doesn't need a coordinated cutover.
Energia's AWS backend was sized for a much larger program and didn't fit the new program's fixed monthly hosting fee. Moving to Vercel, Neon, and Drizzle kept the Energia feature set while cutting the number of services to run. Energia's 184 schema migrations became one Drizzle schema, and there was no production data to migrate yet.
Here, staging went live 13 days after the codebase was created, with scoping in May beforehand. Most of the calendar since has gone to sign-in security review and brand feedback, and the platform hasn't launched yet. Plan the timeline around integration and approvals. See our loyalty program development service and pricing guide.
Two things. Take the sign-in design to the client's security team before writing the first token. And put the customer experience ahead of the admin. The admin got attention first, and the agency's review rounds surfaced customer-side UX details we should have caught earlier.
Related work


