Skip links

Building Agency Tools That Agencies Actually Use

We have built internal tools at Harbor Software since 2020. Most of them failed. Not because they were technically flawed or poorly coded, but because nobody used them after the first week. The project estimation spreadsheet that took two weeks to build was abandoned within a month because it required too many inputs for each estimate. The client portal with beautiful dashboards and real-time project timelines went unused because clients preferred receiving updates in email and did not want another login to manage. The automated reporting system gathered dust because the report format did not match what account managers actually needed to send to clients.

Article Overview

Building Agency Tools That Agencies Actually Use

5 sections · Reading flow

01
The Adoption Curve Is a Cliff, Not a Slope
02
Observe the Workflow Before You Build the Tool
03
Integration Beats Innovation Every Time
04
Maintenance Is the Hidden Cost That Kills Tools
05
The Survivors: Our Current Tool Portfolio

HARBOR SOFTWARE · Engineering Insights

Over four years of building and killing internal tools, we have learned a painful but valuable lesson: building tools that work is the easy part. Building tools that people actually adopt and use daily is the hard part, and the difficulty has almost nothing to do with technology. It has everything to do with understanding the workflow you are inserting yourself into and respecting the constraints, habits, and preferences of the people who will use the tool every day.

Here is what we have learned about building internal agency tools that survive contact with reality.

The Adoption Curve Is a Cliff, Not a Slope

Internal tools do not have gradual adoption curves where usage builds over weeks and months as people discover the benefits. They have binary outcomes: either the tool becomes part of the daily workflow within the first two weeks, or it is abandoned permanently and joins the graveyard of good intentions. There is no slow build to adoption. No “it will catch on eventually.” The reason for this harsh reality is switching cost. Every internal tool competes with an existing process, even if that existing process is crude, manual, and inefficient. The existing process has one enormous advantage: zero learning curve because people already know it. They have muscle memory for it. Your new tool has a nonzero learning curve, unproven reliability, and no muscle memory. If the tool does not deliver obvious, undeniable value within the first three uses, people revert to the known process and never give the new tool a second chance.

This means the first version of any internal tool must be embarrassingly simple. Not an MVP in the startup sense (a minimally viable product that tests a market hypothesis), but a minimally useful tool that does exactly one thing measurably better than the existing process. One thing. Not five features. Not a platform with a roadmap. One specific thing that saves time or eliminates frustration right now, today, on the first use.

Our most successful internal tool is a Slack bot that took 4 hours to build. It does exactly one thing: when someone types /estimate [hours], it calculates the project cost at three price tiers (standard rate, rush rate, and enterprise rate), adds a 15% buffer row for contingency, formats everything into a clean Slack message with proper currency formatting, and posts it in the channel. Before this bot existed, the estimation process involved opening a Google Sheet, finding the right tab for the current rate card, entering the hours in the correct cell, reading the calculated values from three different rows, and copying the formatted result back into Slack. The bot eliminated 3 minutes of friction from something that happens 8-10 times per day across the team. That is 30 minutes saved daily. Everyone adopted it immediately on the first day because it was unambiguously faster than the alternative from the very first use.

// The Slack bot core logic - the entire tool in under 50 lines
app.command('/estimate', async ({ command, ack, respond }) => {
  await ack();
  const hours = parseFloat(command.text);
  if (isNaN(hours) || hours <= 0) {
    await respond('Usage: /estimate [hours] - e.g., /estimate 40');
    return;
  }

  const rates = {
    standard: 85,
    rush: 110,
    enterprise: 150
  };

  const rows = Object.entries(rates).map(([tier, rate]) => ({
    tier: tier.charAt(0).toUpperCase() + tier.slice(1),
    rate: `$${rate}/hr`,
    total: `$${(hours * rate).toLocaleString()}`,
    with_buffer: `$${Math.round(hours * rate * 1.15).toLocaleString()} (15% buffer)`
  }));

  await respond({
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*Estimate for ${hours} hours:*`
        }
      },
      ...rows.map(row => ({
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*${row.tier}* (${row.rate}): ${row.total} | With buffer: ${row.with_buffer}`
        }
      }))
    ]
  });
});

Compare this focused tool to the client portal we spent six weeks building. The portal had user authentication with password reset flows, project timeline visualizations with Gantt charts, file sharing with version history, invoice tracking with payment status, and a messaging system with read receipts. It was technically impressive and covered every feature a client could theoretically want. But clients did not want another login to manage in addition to their email, Slack, Google Drive, and every other tool they already used. They wanted project updates in email, where they already spend their day. The portal solved a problem we imagined clients had, not a problem they actually expressed or demonstrated through their behavior.

Observe the Workflow Before You Build the Tool

Before building any internal tool, we now spend at least one full week observing the current workflow in detail. Not asking people what they want (they will describe their ideal fantasy tool that solves every problem simultaneously), but watching what they actually do minute by minute. The observation method is simple but revealing: shadow a team member for a full working day and write down every repetitive action, every context switch between applications, every moment of visible frustration or wasted time, and every workaround they have invented for inefficient processes.

When we observed our project managers over a week, we found that 40% of their working time was consumed by three specific activities that were ripe for automation:

  1. Copying task completion updates from Linear into client-facing weekly status emails (25 minutes per day per PM, involving switching between Linear and Gmail repeatedly)
  2. Calculating project budget burn rates by exporting time entries from Toggl, cross-referencing against estimates in Linear, and doing arithmetic in a spreadsheet (20 minutes per day)
  3. Chasing engineers for end-of-day handoff documents via direct messages in Slack (15 minutes per day of send, wait, follow up, send again)

Each of these became a targeted tool project, prioritized not by technical complexity or coolness factor but by a simple ROI calculation: daily time saved divided by estimated build effort:

  • Status email generator: Pulls completed tasks from Linear’s API for the past week, formats them into our standard status email template with proper client-facing language, and drafts the email ready for PM review and send. Build time: 12 hours. Time saved: 25 minutes/day per PM. ROI payback period: 3.5 working days.
  • Budget burn dashboard: Connects to Toggl’s API (time tracking) and Linear’s API (task estimates), calculates burn rate percentage for each active project, and displays a color-coded health indicator in a Slack channel every morning. Build time: 20 hours. Time saved: 20 minutes/day. ROI payback period: 7.5 working days.
  • Handoff reminder bot: At 5 PM in each engineer’s local timezone, checks whether they have posted in the #handoffs Slack channel today. If not, sends them a friendly DM reminder with a link to the channel. Build time: 3 hours. Time saved: 15 minutes/day for PMs plus improved handoff completion rate from 70% to 91%. ROI payback period: 1.5 working days.

We built the handoff reminder bot first (fastest ROI and simplest implementation), then the status email generator, then the budget dashboard. All three achieved full team adoption because they addressed pain points that people were visibly frustrated by every single day, not hypothetical problems from a brainstorming session.

Integration Beats Innovation Every Time

The tools that get adopted and survive are the ones that live where people already work. For our team, that means Slack (where we communicate all day), Google Sheets (where PMs track project data), and the browser (for everything else). Any tool that requires opening a separate application, creating a new account, logging in with a new password, and navigating to a specific feature within the application is fighting an uphill adoption battle that it will almost certainly lose.

Our standard architecture for internal tools follows a consistent, proven pattern:

// Internal tool architecture pattern - always the same
//
// Input layer: Slack command, Slack message trigger, or scheduled cron
// Processing: Node.js function (Vercel serverless or AWS Lambda)
// Data sources: Pull from existing systems via their APIs
//   - Linear API for project tasks and estimates
//   - Toggl API for time tracking entries
//   - GitHub API for repository activity
//   - WooCommerce API for client store data
// Output layer: Slack message, Google Sheets update, or email draft
//
// Critical constraints:
// - No new databases (use existing system APIs as the source of truth)
// - No new user accounts or login pages
// - No standalone web dashboards (except single-page data views linked from Slack)
// - The tool plugs into existing workflows, never replaces them

This pattern means every tool we build is a connector between systems the team already uses and trusts. The budget dashboard does not have its own web interface with a login page. It posts a formatted daily summary to a dedicated Slack channel at 9 AM every morning. PMs see it during their morning Slack scan without navigating anywhere new. The status email generator does not have a web form where you select a project and a date range. It responds to a Slack command (/status-email project-name) and outputs a formatted email body that the PM copies into Gmail with one click. No new tools to learn. No new bookmarks to maintain. No new habits to form.

We also found that Google Sheets is an underappreciated tool platform. The budget tracker started as a Slack bot but evolved into a Google Sheet integration because PMs wanted to see all projects at once in a scannable table format, which Slack messages are terrible at displaying. A shared Google Sheet with color-coded rows (green for healthy, yellow for at-risk, red for over-budget) updated hourly by a Google Apps Script function pulling from the Toggl API became the most-used view in our project management workflow. PMs already had the sheet open every day. The automation just kept the numbers current without anyone having to do manual data entry.

Maintenance Is the Hidden Cost That Kills Tools

Every internal tool you build is a long-term commitment to maintain it. APIs change their authentication methods. OAuth tokens expire after 90 days. Third-party services update their rate limits, deprecate endpoints, or change response formats. Slack periodically updates its Block Kit specification and deprecates old message formatting. A tool that was working perfectly in January can break silently in March because Toggl deprecated an API v8 endpoint in favor of v9 and your code is still calling the old one.

We budget 2 hours per month per active tool for ongoing maintenance. For our current portfolio of 7 active internal tools, that is 14 hours per month, roughly one full engineering day per month dedicated exclusively to keeping internal tools running smoothly. This is a real, recurring cost that must be factored into the build-versus-buy decision for every new tool.

To minimize the maintenance burden and catch issues before they affect users, we follow these practices rigorously:

  • Automated health checks for every tool. A daily cron job pings each tool’s external API dependencies and verifies authentication is still valid. If any dependency returns an error (Toggl returns 401, Linear returns 503), the health check posts a warning to our internal #tool-health Slack channel before the tool itself breaks visibly.
  • Pin API versions explicitly. When calling third-party APIs, always specify the API version in the URL or headers. api.linear.app/v2 instead of api.linear.app/latest. This prevents breaking changes from affecting your tool before you have had a chance to update your integration code.
  • Centralized configuration for all secrets and settings. All API keys, webhook URLs, rate card values, and configuration constants live in a single .env file per tool. When a key rotates or a rate changes, there is exactly one place to update and one deployment to make.
  • Kill unused tools aggressively. If a tool’s usage drops below once per week for a full month, we retire it by archiving the code and removing the scheduled triggers. Maintaining a tool that nobody uses is pure waste that creates unnecessary maintenance burden and alert noise. We have killed 5 tools over the past two years and nobody noticed any of them were gone.

The Survivors: Our Current Tool Portfolio

After four years of building, testing, iterating, and killing internal tools, here are the seven that survived natural selection and are still in active daily use:

  1. /estimate bot – Instant project cost calculation across three pricing tiers (Slack command, 4 hours to build, used 8-10 times daily)
  2. Handoff reminder – End-of-day documentation prompt sent to engineers who have not posted their handoff (Slack bot, 3 hours to build, improved handoff rate from 70% to 91%)
  3. Status email generator – Pulls task completions from Linear and formats client-ready status updates (Slack command, 12 hours to build, saves 25 min/day per PM)
  4. Budget burn tracker – Hourly project health dashboard with color-coded indicators (Google Sheets + Toggl API, 20 hours to build, saves 20 min/day)
  5. Deploy notifier – Posts deployment status with commit details to project-specific Slack channels (GitHub Actions integration, 2 hours to build, eliminated forgotten deploy notifications)
  6. New lead alert – Routes website contact form submissions to Slack with enriched company context from Clearbit (webhook + API, 1 hour to build, reduced lead response time from 4 hours to 15 minutes)
  7. Weekly metrics digest – Monday morning summary of revenue, team utilization rate, active pipeline value, and key KPIs (scheduled Slack message, 8 hours to build, replaced a manual reporting process that took 45 minutes every Monday)

Total build investment: 50 engineering hours. Total time saved per week across the team: approximately 12 hours. Annual calculation: 572 hours saved versus 50 hours of initial build time plus 168 hours of annual maintenance (14 hours/month times 12 months). Net benefit: 354 hours per year, a 2.6x return on investment. And that return compounds every year because the time savings repeat every week while the build cost is a one-time investment.

The tools that died teach equally important lessons. The client portal died because clients did not want it. The automated code review assistant died because engineers did not trust its suggestions and found them more annoying than helpful. The meeting scheduler died because Calendly already existed and did the job better than anything we could build in a reasonable timeframe. The internal knowledge base died because nobody wanted to write documentation even when the tool made it easy to do so. In every single case, the failure was not a technical problem. It was a fundamental misunderstanding of what people actually needed versus what we thought they should need.

The pattern across all surviving tools is clear: they are simple (do one thing well), they live where people already work (Slack and Google Sheets), and they solve a problem that someone was visibly frustrated by before the tool existed. Build for observed frustration, not for imagined possibility, and your internal tools will actually get used.

Leave a comment

Explore
Drag