Reusing a proven rewards program without reusing its hosting bill

RaftLabs rebuilt the Energia Rewards feature set for a full-fibre broadband provider, moved the backend to Vercel and Neon to fit a fixed monthly hosting fee, and redesigned portal sign-in to carry no personal data.

from a new codebase to a live staging environment
13 days
lifetime of each signed, single-use sign-in token
10 s
personal data fields in the revised sign-in contract sent for security review
0

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

Before
  • 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
After
  • 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.

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

  2. 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.

  3. 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.

  4. 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.

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

  2. 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.

  3. 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.

ResultWhat changedPeriod or contextEvidence and limitation
Delivery speedStaging live 13 days after the codebase was created5 to 18 June 2026Project-recorded: repository history
BackendEnergia's 184 schema migrations recreated as one Drizzle schema on NeonStack change on 15 June 2026Project-recorded: re-pivot plan and repository
Sign-in10-second, single-use signed tokens with replay protection and secret rotationCurrent staging buildProject-recorded; the revised no-personal-data contract awaits the provider's security sign-off
Hosting modelBackend runs on Vercel and Neon with no AWS servicesStagingProject-recorded; the hosting fee is confidential and not published
OutcomesNot yet availableBefore launchKPIs 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.

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

  2. 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.

  3. 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.

Work with us

Recognise this problem in your business?

Tell us what's broken. We'll diagnose it and show you exactly what to fix first, before you commit to anything.

  • Scope and cost agreed before work starts. No surprises. No obligation.
  • Working prototype within 3 weeks of kickoff.
  • Pay by milestone. You see progress before each invoice.
  • 60-day post-launch warranty. Bug fixes, UI tweaks, and deployment support. No retainer.
  • All conversations are NDA-protected.