Inspiration

We're college students, so we know every dollar counts. Between tuition, rent, textbooks, and groceries, there isn't much left over. Almost every brand we already pay for has a student discount, whether it be for streaming, software, clothes, or coffee. The problem is that nobody remembers to look for them. You find out about the student deal after you've paid full price.

We didn't want another budgeting app that sits unopened on our home screens. We wanted something that lives where students already are: our text messages. So we built an assistant you can just text. "Is there a student deal at Nike?" gets you a real answer in seconds. It can also look at what you're actually spending money on and point out the discounts you're missing.

What it does

  • Text it like a friend. Each user gets a personal iMessage number. Ask about a store or product and the assistant replies with matching student offers and where to redeem them.
  • Connect your bank. Through Plaid, users link their bank so we can sync their accounts, balances, and transactions.
  • Find savings automatically. Every hour, a background job pulls new transactions, looks at the merchants you've spent money at, and matches them against a catalog of student discounts.
  • A web dashboard for budgeting and viewing transactions

How we built it

Layer Tech
Frontend Next.js, React, Tailwind CSS, react-plaid-link
Backend Node.js + Express 5
Database MySQL Prisma ORM
Messaging Photon (Spectrum SDK) for iMessage
AI Google Gemini
Banking Plaid API
Offers Student discount catalog from studentoffers.co

The message pipeline. When a text comes in, it runs through three steps:

  1. Parse: Gemini runs with structured output to pull out the store, product, and price the user is asking about.
  2. Look up: we search our offers database for matching discounts.
  3. Reply: Gemini writes a short, friendly answer using the offers we found. If an offer doesn't say where to redeem it, the assistant points you to the company's website.

Every message in and out is logged so we can improve the responses over time.

Fuzzy matching. People misspell things when they text ("spotfy," "starbux"), and bank merchant names are messy. So we match offers two ways: substring matches, plus phonetic matches using MySQL's SOUNDEX. Results are ranked exact match first, then substring matches, then sound-alikes.

Security. Logins use short-lived JWT access tokens (15 minutes) and long-lived refresh tokens. We store only a SHA-256 hash of each refresh token. Passwords are hashed with bcrypt. Plaid access tokens are encrypted at rest with AES-256-GCM, using a fresh random IV for each token.

Incremental syncing. Instead of re-downloading every transaction, we use Plaid's cursor-based /transactions/sync. Each sync pulls only what was added, changed, or removed since the last one.

Why it matters (the math)

Say a student spends x per month at merchant x, and that merchant has a student discount rate x. Their yearly savings are:

$$ \text{Annual savings} = 12 \sum_{i=1}^{n} s_i \, d_i $$

For example, if a student spends a combined 150 USD a month at places offering an average 15% student discount:

$$ 12 \times 150 \times 0.15 = 270 \text{ USD per year} $$

That's a textbook, a month of groceries, or a few tanks of gas, from money students were already spending.

Challenges we ran into

  • Turning texts into structured data. People text casually and vaguely. Getting Gemini to reliably pull out a store name took structured output schemas. We also made sure a failed parse doesn't stop the user from getting a reply.
  • Coordinating several services. Signup creates a user in our database and registers them with Photon for iMessage. If the second step fails, the first one has already happened. Handling partial failures across services was harder than we expected.
  • Plaid Sandbox timing. Sandbox transactions don't show up right after a bank is linked, so our seed script has to retry the sync before there's data to work with.
  • Background jobs. Running the hourly offers job in the same process as the server was simple to start with, but it means the job only runs while the server is up, and it would run once per server if we ever scaled to more than one.

What we learned

  • How to build a multi-step LLM pipeline (parse, retrieve, respond) instead of sending everything in one prompt.
  • How bank data APIs work in practice: link tokens, token exchange, cursors, and re-authentication.
  • How to handle secrets and tokens responsibly: hashing, encryption at rest, and short-lived credentials.
  • How to keep an Express codebase organized with a router, validator, service layering pattern.
  • How to work as a team across separate frontend, backend, and integration branches with pull requests.

What's next for B2B

  • Proactive texts: the hourly job already finds offers that match your recent purchases. Next we'll text them to you, e.g. "You spent at Spotify this week. Did you know there's a student plan?"
  • Plaid webhooks, so syncs happen when new transactions arrive instead of once an hour
  • More Offers: To let our users save even more money, we would like to integrate webscraping to find even more deals!
Share this project:

Updates

Submission history