Senior TypeScript: Make Our AI Agent Multi-Tenant and Production-Ready

Posted 4 hours ago

Worldwide

Summary

THE PRODUCT We sell B2B prospecting. A user describes who they want to reach — "e-commerce shops in Lyon running Shopify, find the owner's email, write a first outreach email" — and an AI agent actually goes and does it: it searches real data sources, builds a table of leads, enriches it column by column, writes the email sequence, and schedules the campaign to go out from the user's own mailbox. We have this in two separate applications, and they need to become one. APP A — the commercial platform, in production NestJS, PostgreSQL, Prisma, BullMQ, deployed with Docker behind nginx. It handles everything around the product: user accounts, Google and Microsoft OAuth, Stripe subscriptions and a credit ledger, connected mailboxes, real email sending through the Gmail API and Microsoft Graph with sending windows and per-mailbox caps, inbound reply detection, and an admin back office. It works. What it does not have is any intelligence: today the user fills in a six-step form. APP B — the agent, built separately Fastify 5, Prisma 6.19, SQLite, with its own Next 16 / React 19 front end. This is the product we actually want to sell: a chat where the user writes what they want, and an agent loop with 14 tools goes and does it. Eleven live data connectors, a code-execution sandbox for enrichment logic, a spreadsheet-style table UI, a campaign builder. It works too — but it was built as a single-user prototype and it has never left a laptop. THE WORK Move App B onto App A's infrastructure, so one logged-in customer can use the agent as the product. 1. Database. App B runs on SQLite. Move it to App A's PostgreSQL, in its own schema. There is no raw SQL anywhere in it and no dialect-specific query, so this is a provider change plus a first migration — the schema has never had one. 2. Authentication. App B has none. Put its routes behind App A's existing session token, and keep its live chat streaming (SSE) working through nginx — it works in development and dies behind a default proxy configuration. 3. Deployment. The server has no Dockerfile and no health endpoint. The front end has a Dockerfile, but it was inherited from a template and cannot build — it copies a standalone output our config explicitly disables. Build both images properly, add the services to the existing compose stack, route them under /agent/ on App A's domain, and give us a status page a non-developer can read. 4. Multi-tenancy. This is the largest part and the one that matters. App B was written for exactly one user: 73 of its 77 routes are open, 42 answer anyone holding an id, 366 database calls filter on nobody, and the email sender identity is a hardcoded string. Six uniqueness constraints are global, so two customers cannot both have a document at the same path or exclude the same domain — and one settings row is keyed to the literal string "global". Spending is metered into a single platform-wide pot with no user attached, so it has to become per-customer and land on the balance that customer actually bought. It also has a tool that runs model-written code with database access, so route-level filters alone do not cover any of this. 5. The screens. Closing those routes breaks the app unless the front end changes with it: there is not a single Authorization header anywhere in it, and its sign-in page is a link that walks straight into the dashboard under the words "simulated local sign-in". Merge its 48 files into App A's Next front end — same Next 16, same React 19, same Tailwind 4, same tsconfig, so this is a move plus one shared HTTP layer, not a rewrite. Present the token, guard the routes, refresh the session, and keep the live chat stream authenticated — EventSource cannot carry a header, so that one needs a deliberate answer. Without this, nothing above can be accepted: the acceptance test is two customer accounts open side by side in two browser windows, and neither of them can log in today. 6. Make it fast, and make sure it actually works. The agent is slow: a single turn can run five to ten minutes, it re-reads documentation it already holds in context, and it makes model calls it does not need. Nothing in the codebase measures any of that today — no timing, no token counting, no per-turn record — so the first task here is building that instrument, and then cutting what it shows: repeated reads, redundant calls, work done one after another that could run at the same time. Then go through the wired capabilities, find the ones that are broken or return something other than what they claim, and fix them. We agree the targets and the list with you before you start, and at the end we compare against your own baseline, not against a feeling. THE PRODUCT We sell B2B prospecting. A user describes who they want to reach — "e-commerce shops in Lyon running Shopify, find the owner's email, write a first outreach email" — and an AI agent actually goes and does it: it searches real data sources, builds a table of leads, enriches it column by column, writes the email sequence, and schedules the campaign to go out from the user's own mailbox. We have this in two separate applications, and they need to become one. APP A — the commercial platform, in production NestJS, PostgreSQL, Prisma, BullMQ, deployed with Docker behind nginx. It handles everything around the product: user accounts, Google and Microsoft OAuth, Stripe subscriptions and a credit ledger, connected mailboxes, real email sending through the Gmail API and Microsoft Graph with sending windows and per-mailbox caps, inbound reply detection, and an admin back office. It works. What it does not have is any intelligence: today the user fills in a six-step form. APP B — the agent, built separately Fastify 5, Prisma 6.19, SQLite, with its own Next 16 / React 19 front end. This is the product we actually want to sell: a chat where the user writes what they want, and an agent loop with 14 tools goes and does it. Eleven live data connectors, a code-execution sandbox for enrichment logic, a spreadsheet-style table UI, a campaign builder. It works too — but it was built as a single-user prototype and it has never left a laptop. THE WORK Move App B onto App A's infrastructure, so one logged-in customer can use the agent as the product. 1. Database. App B runs on SQLite. Move it to App A's PostgreSQL, in its own schema. There is no raw SQL anywhere in it and no dialect-specific query, so this is a provider change plus a first migration — the schema has never had one. 2. Authentication. App B has none. Put its routes behind App A's existing session token, and keep its live chat streaming (SSE) working through nginx — it works in development and dies behind a default proxy configuration. 3. Deployment. The server has no Dockerfile and no health endpoint. The front end has a Dockerfile, but it was inherited from a template and cannot build — it copies a standalone output our config explicitly disables. Build both images properly, add the services to the existing compose stack, route them under /agent/ on App A's domain, and give us a status page a non-developer can read. 4. Multi-tenancy. This is the largest part and the one that matters. App B was written for exactly one user: 73 of its 77 routes are open, 42 answer anyone holding an id, 366 database calls filter on nobody, and the email sender identity is a hardcoded string. Six uniqueness constraints are global, so two customers cannot both have a document at the same path or exclude the same domain — and one settings row is keyed to the literal string "global". Spending is metered into a single platform-wide pot with no user attached, so it has to become per-customer and land on the balance that customer actually bought. It also has a tool that runs model-written code with database access, so route-level filters alone do not cover any of this. 5. The screens. Closing those routes breaks the app unless the front end changes with it: there is not a single Authorization header anywhere in it, and its sign-in page is a link that walks straight into the dashboard under the words "simulated local sign-in". Merge its 48 files into App A's Next front end — same Next 16, same React 19, same Tailwind 4, same tsconfig, so this is a move plus one shared HTTP layer, not a rewrite. Present the token, guard the routes, refresh the session, and keep the live chat stream authenticated — EventSource cannot carry a header, so that one needs a deliberate answer. Without this, nothing above can be accepted: the acceptance test is two customer accounts open side by side in two browser windows, and neither of them can log in today. 6. Make it fast, and make sure it actually works. The agent is slow: a single turn can run five to ten minutes, it re-reads documentation it already holds in context, and it makes model calls it does not need. Nothing in the codebase measures any of that today — no timing, no token counting, no per-turn record — so the first task here is building that instrument, and then cutting what it shows: repeated reads, redundant calls, work done one after another that could run at the same time. Then go through the wired capabilities, find the ones that are broken or return something other than what they claim, and fix them. We agree the targets and the list with you before you start, and at the end we compare against your own baseline, not against a feeling. This part needs no AI or model experience, and we are not looking for a prompt engineer. The whole model surface is a single 101-line file around `new Anthropic()` — no chains, no vector store, no embeddings. It is profiling, caching, concurrency and bug-fixing on a TypeScript codebase that happens to be an agent. What the agent decides and how it writes — its prompts, its skills, its playbooks — stays with us. That is product judgement, not contract work, and we are not going to ask you to guess at it. 7. Pricing. The two applications do not sell the same thing, and once the agent is the product only one of them can be right. App A charges 5 credits per prospect, in whole numbers — `CreditGrant.amount` is an integer column, so a 0.5-credit charge cannot even be written to it. App B charges per service call in fractions: 0.1 to write a message, 0.75 for a maps lookup, 3 for an email, 15 for a phone number, across 48 priced services. So sourcing becomes the thing customers pay for and sending becomes free, which is the opposite of today. What we need from you: make the ledger able to hold what the agent actually spends, retire the per-prospect charge, and make the plans and the billing screens tell the customer the truth. The twelve plans live in one config file and their Stripe price IDs are environment variables; creating the products and setting the amounts in Stripe is our job, not yours. Deciding what a lead should cost is also ours — we will hand you the grid.

  • $4,000.00

    Fixed-price
  • Expert
    Experience Level
  • Remote Job
  • Ongoing project
    Project Type

Contract-to-hire opportunity

This lets talent know that this job could become full time.
Learn more
Skills and Expertise
Mandatory skills
AI Agent Development
TypeScript
Activity on this job
  • Proposals:50+
  • Interviewing:
    0
  • Invites sent:
    0
  • Unanswered invites:
    0
About the client
Member since Mar 17, 2026
  • FRA
    Boulogne Billancourt12:50 AM
  • $12K total spent
    1 hire, 1 active

Explore similar jobs on Upwork

Shopify Website Development for Arc GISHourly‐ Posted 1 month ago
HTML
Web Development
Shopify
Shopify Templates
Create Engaging Ads for SaaS Web AppFixed-price‐ Posted 2 weeks ago
HTML5
JavaScript
Web Design
Graphic Design

How it works

  • Post a job icon
    Create your free profile
    Highlight your skills and experience, show your portfolio, and set your ideal pay rate.
  • Talent comes to you icon
    Work the way you want
    Apply for jobs, create easy-to-by projects, or access exclusive opportunities that come to you.
  • Payment simplified icon
    Get paid securely
    From contract to payment, we help you work safely and get paid securely.
Want to get started? Create a profile

About Upwork

  • Rating is 4.9 out of 5.
    4.9/5
    (Average rating of clients by professionals)
  • G2 2021
    #1 freelance platform
  • 49,000+
    Signed contract every week
  • $2.3B
    Freelancers earned on Upwork in 2020

Find the best freelance jobs

Growing your career is as easy as creating a free profile and finding work like this that fits your skills.

Trusted by

  • Microsoft Logo
  • Airbnb Logo
  • Bissell Logo
  • GoDaddy Logo