Book a 30-min call
cd ../blogs
$ cat posts/from-four-tools-to-one-dashboard-solar-proposals.mdx

From Four Tools to One Dashboard: Engineering a Solar Proposal Platform

April 7, 2026 · updated August 8, 2026 · ImmovableTech Team

  • Full-Stack
  • Real-Time Systems

The pain: four tools, one sales rep

The sales team at a leading solar roofing company was creating proposals using four disconnected tools: a CRM for lead management, a separate proposal builder for system sizing and pricing, a third-party calling platform for customer follow-ups, and a document generator for contracts. Every proposal required switching between all four, copying data manually, and hoping nothing fell through the cracks.

The result: proposals took 45 minutes on average, data entry errors were common, and reps spent more time wrestling with tools than talking to customers. The company was scaling fast — doubling their sales team in six months — and the fragmented workflow could not scale with them.

Discovery: mapping the real workflow

We spent the first week shadowing three sales reps. Rather than asking “what tools do you use?”, we asked “walk me through your last five proposals.” The difference matters: people describe ideal workflows when you ask about tools, but reveal actual pain points when you ask about specific recent work.

Key findings from the observation sessions:

  1. The biggest time sink was not proposal creation — it was context switching. Reps spent 12 minutes per proposal just navigating between tools and re-entering data that already existed elsewhere.
  2. Calling was the surprise bottleneck. Reps made 3-4 calls per proposal (initial contact, follow-up, technical clarification, close). Switching to the calling platform, finding the contact, and logging the call back into the CRM took 8 minutes per call cycle.
  3. Pricing errors caused the most rework. When system sizing changed (which happened in 40% of proposals), reps had to manually update pricing in both the proposal builder and the contract generator. Mismatches caused 15% of proposals to require revision after client review.

Technical architecture

The integration layer

Rather than building a monolithic application that replaces all four tools, we built an orchestration layer that sits on top of existing systems. Each tool exposes an API (or can be accessed via one):

  • CRM (REST API): Lead data, contact history, pipeline stage
  • Proposal Builder (REST API): System sizing, equipment catalogue, pricing engine
  • Calling Platform (Twilio): Click-to-call, call recording, automatic logging
  • Document Generator (API): Contract templates, e-signature triggers

Our dashboard queries all four APIs and presents a unified view. When a rep updates a field, the change propagates to the appropriate backend systems.

The orchestration is the part worth explaining, because our first version got it wrong. We started with the obvious thing: a Node service that received a change and called the four downstream APIs in sequence inside the request. That works until one of them is slow, and one of them is always slow. A proposal save that touched CRM, the pricing engine and the document generator would hang for eight seconds because the document generator was cold, and reps learned to distrust the save button.

We moved the multi-API fan-out to AWS Step Functions Express with EventBridge Pipes. A save writes locally and emits an event; the state machine handles the fan-out, the retries and the partial-failure cases, and the rep gets their confirmation back immediately. Lambda SnapStart plus provisioned concurrency keeps the synchronous pieces — the ones the rep is actually waiting on — off the cold-start path.

The unglamorous benefit is that partial failures became visible. In the old in-request version, if the third of four calls failed, you got a 500 and no idea what had already been written. With an explicit state machine, a failed fan-out is a stuck execution you can inspect, replay and fix, and we found two silent data-drift bugs in the first month purely because failures stopped disappearing.

The one-screen design

The core UX principle was progressive disclosure: show only what the rep needs at each stage of the proposal lifecycle.

Stage 1 — Lead Review: The rep sees the customer’s information, property details, and any previous interactions. One click imports everything from the CRM — no re-typing.

Stage 2 — System Design: The proposal builder’s sizing engine runs inside our dashboard. The rep selects panel configurations and sees pricing update live. If they change the system size, pricing and the contract draft update simultaneously.

Stage 3 — Customer Communication: A call button is embedded next to the customer’s contact info. Clicking it initiates a Twilio call, records it, and logs the summary back to the CRM automatically. The rep never leaves the dashboard.

Stage 4 — Proposal & Contract: One button generates the final proposal PDF and the contract. E-signature is triggered from the same screen. The rep can track signature status without switching to the document tool.

Real-time sync and conflict resolution

The hardest engineering problem was not the API integrations — it was handling conflicts. What happens when a rep updates the system size in our dashboard while a colleague updates the same lead’s contact info in the CRM directly?

We implemented a last-write-wins strategy for independent fields (contact info, notes) and a lock-and-notify strategy for dependent fields (system size, pricing). When a rep starts editing a proposal’s system configuration, we acquire a soft lock. If another user tries to edit the same proposal, they see a notification and can either wait or force-override with an audit trail.

Why CRDTs were overkill here

Last-write-wins plus lock-and-notify worked because proposals are almost always edited by one rep at a time. Concurrent editing showed up in roughly 3% of sessions, and the soft lock resolved those cleanly.

We do reach for CRDTs when the workload genuinely warrants them — on a later multi-user editing project we used them for simultaneous document editing, where changes merge without coordination. The cost is that CRDT state is materially harder to reason about and debug than a lock, and that cost is constant whether or not you have concurrency to justify it. Two users typing in the same paragraph is a CRDT problem. One rep at a time with a rare collision is a lock problem, and picking the fancier tool for it would have bought us nothing but a harder on-call.

The calling integration

Twilio’s API handled outbound calling, but the real value was in the automation around it:

  • Click-to-call from any contact card in the dashboard
  • Automatic call logging: duration, timestamp, and a one-click “call summary” field that the rep fills in (average: 15 seconds vs. 3 minutes when they had to switch to the CRM)
  • Follow-up scheduling: after each call, a prompt suggests the next action based on the proposal stage, and schedules it with one click
  • Call history timeline: every interaction with a customer is visible in a single chronological feed

UX lessons

Do not hide the complexity — sequence it. Our first prototype showed all four stages simultaneously in a tabbed layout. Reps found it overwhelming. The final design shows one stage at a time with clear “Next” progression. Completion rate went from 68% (tabs) to 94% (sequential).

Make the happy path the only path. In the old workflow, reps could create a proposal without attaching it to a CRM lead, which caused orphaned records. Our dashboard requires a lead context before any proposal work begins. This eliminated 100% of orphaned proposals.

Celebrate completion. A small animation and a “Proposal sent!” confirmation when the e-signature is triggered sounds trivial, but reps told us it was their favourite feature. It turned a tedious administrative task into something that felt like closing a deal.

Results

  • 62% reduction in proposal creation time (from 45 minutes to under 18 minutes on average)
  • 4 platforms unified into a single dashboard with no data re-entry
  • Zero orphaned proposals after launch (down from 15% of all proposals)
  • 92% rep adoption within the first month, with positive feedback driving the remaining 8% by week six

What made this work

This project succeeded because of the discovery phase, not because of any single technical decision. Shadowing reps for a week before writing code meant we solved the right problems — context switching and calling friction, not “we need a prettier proposal builder.”

The stack was deliberately unexciting: React 19 with Next.js 15 and Server Actions on the front end, Step Functions Express and Lambda behind it, PostgreSQL for state, Twilio for calling. Server Actions earned their place for a specific reason. Every mutation in this app is a form submission that has to fan out to external systems, and the pre-Actions version of that was a client-side handler calling an API route that called a service — three places to keep in sync for one logical operation. Collapsing the mutation path meant the fan-out logic lives in exactly one place, next to the form that triggers it.

What we’d do differently

Two things.

We would put the orchestration in Step Functions from the first commit instead of arriving there after the in-request fan-out failed. We knew the downstream APIs were slow and unreliable; we still wrote the naive version first, and the rewrite cost us a fortnight plus the reps’ trust in the save button, which took longer to win back than the code took to fix.

We would also spend the discovery week instrumenting the old tools rather than only shadowing. Shadowing told us reps felt that context switching was the problem, and they were right, but we sized it from three people’s recollections. A fortnight of real usage logs from the four existing tools would have given us the per-stage timings up front, and we would have prioritised the calling integration — which turned out to be the single biggest win — ahead of the pricing sync rather than behind it.

References


This project is part of our Full-Stack Web & App Development practice. Read the full case study or talk to us about a similar challenge.