DEV Community

Savage Solutions
Savage Solutions

Posted on

How We Automated 90% of Our Agency's Manual Work

How We Automated 90% of Our Agency's Manual Work

When I started my agency three years ago, I thought hiring more developers was the answer to our scaling problems. We'd land a new client, manually set up their project, run the same onboarding steps, copy-paste documentation, and spend hours in Slack answering repetitive questions. By month six, we had four developers doing work that—in hindsight—should have been handled by automation.

It wasn't until I sat down and actually tracked where our time went that I realized: we were spending 40+ hours per week on completely repeatable tasks.

That's when everything changed.

The Turning Point: Auditing Our Workflow

Before automating anything, I needed to see what was actually broken. I spent a week documenting every single task:

  • Client onboarding: 4-6 hours per new client
  • Environment setup: Creating staging/production configs, database seeding
  • Deployment pipeline management: Manual testing checklists, server configurations
  • Client reporting: Extracting metrics from 5+ tools, formatting into PDFs
  • Support tickets: Answering the same FAQ questions repeatedly
  • Code reviews: Generic feedback that should have been automated linting rules

The pattern was clear: high-volume, low-complexity work that had zero business value.

What We Automated (And How)

1. Client Onboarding via Infrastructure-as-Code

Instead of manually spinning up servers and databases, we built an automated onboarding flow using Terraform and custom Node scripts.

#!/bin/bash
# onboard-client.sh - Single command to set up everything

CLIENT_NAME=$1
ENVIRONMENT=$2

# Provision infrastructure
terraform -chdir=./infrastructure apply \
  -var="client_name=${CLIENT_NAME}" \
  -var="environment=${ENVIRONMENT}"

# Seed database with templates
node scripts/seed-database.js ${CLIENT_NAME}

# Create documentation from templates
node scripts/generate-docs.js ${CLIENT_NAME}

# Set up monitoring/alerts
node scripts/setup-monitoring.js ${CLIENT_NAME}

# Create Slack channel and invite team
node scripts/setup-slack.js ${CLIENT_NAME}

echo "✅ Client ${CLIENT_NAME} fully onboarded in $(date)"
Enter fullscreen mode Exit fullscreen mode

Result: What took 4-6 hours now takes 15 minutes. One command. No manual steps.

2. Automated Deployment Pipelines

We switched from a checklist-based deployment process to a fully automated CI/CD pipeline using GitHub Actions. No more "did you remember to run migrations?"

name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - run: npm ci
      - run: npm run lint
      - run: npm run test
      - run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to production
        run: |
          npm run migrate:latest
          npm run deploy:production

      - name: Run smoke tests
        run: npm run smoke-tests

      - name: Notify Slack
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "✅ Production deployment successful"
            }
Enter fullscreen mode Exit fullscreen mode

Result: Eliminated deployment errors, reduced deploy time from 45 minutes to 8 minutes, killed our "Friday deploy anxiety."

3. Automated Client Reporting

Our clients loved reports, but they were bleeding us dry. I built a reporting automation that pulls from Google Analytics, Stripe, our internal metrics, and generates a beautiful PDF automatically.

// report-generator.js
const PDFDocument = require('pdfkit');
const fs = require('fs');

async function generateClientReport(clientId) {
  const client = await db.clients.findById(clientId);

  // Fetch metrics from multiple sources
  const [analytics, stripe, customMetrics] = await Promise.all([
    fetchGoogleAnalytics(client.analyticsId),
    fetchStripeMetrics(client.stripeId),
    fetchInternalMetrics(clientId)
  ]);

  const doc = new PDFDocument();
  const filename = `reports/${client.name}-${new Date().toISOString().split('T')[0]}.pdf`;

  doc.pipe(fs.createWriteStream(filename));

  // Build report
  doc.fontSize(20).text(`${client.name} - Monthly Report`, 100, 50);
  doc.fontSize(12).text(`Generated: ${new Date().toLocaleDateString()}`);

  // Add sections
  addAnalyticsSection(doc, analytics);
  addRevenueSection(doc, stripe);
  addPerformanceSection(doc, customMetrics);

  doc.end();

  // Email the report
  await sendEmail(client.email, {
    subject: `Your Monthly Report - ${client.name}`,
    attachments: [{ path: filename }]
  });

  console.log(`✅ Report sent to ${client.email}`);
}

// Run every month automatically via cron
schedule.scheduleJob('0 9 1 * *', async () => {
  const clients = await db.clients.findActive();
  for (const client of clients) {
    await generateClientReport(client.id);
  }
});
Enter fullscreen mode Exit fullscreen mode

Result: What was a 2-3 hour monthly task per client is now a 30-second automated job. Clients are happier (reports are consistent and on-time), we're happier (hours freed up).

4. FAQ Bot in Slack

We were answering the same questions constantly. Built a simple Slack bot that uses GPT-3.5 to answer common questions by querying our documentation first.

// slack-bot.js
const { App } = require('@slack/bolt');

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

app.message(/how do i|what is|can i/, async ({ message, say, client }) => {
  try {
    // Search our docs first
    const relevantDocs = await searchDocumentation(message.text);

    if (relevantDocs.length > 0) {
      // We found relevant docs, cite them
      await say({
        text: `Found this in our docs:`,
        blocks: relevantDocs.map(doc => ({
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*${doc.title}*\n${doc.excerpt}\n<${doc.url}|Read more>`
          }
        }))
      });
    } else {
      // Fall back to GPT for more nuanced questions
      const answer = await queryGPT(message.text);
      await say(answer);
    }
  } catch (error) {
    console.error(error);
    await say('I'm not sure about that onetagging a human! 🤖');
  }
});

app.start();
Enter fullscreen mode Exit fullscreen mode

Result: 70% of questions now get instant answers. Support team handles only complex issues.

5. Code Review Automation

We set up automated linting, formatting, and code quality checks so humans only review for logic and architecture.

// .eslintrc.js - Catches style issues before review
module.exports = {
  extends: ['eslint:recommended', 'plugin:react/recommended'],
  rules: {
    'no-console': 'warn',
    'no-unused-vars': 'error',
    'indent': ['error', 2],
    'quotes': ['error', 'single'],
    'semi': ['error', 'always'],
    'react/prop-types': 'warn'
  }
};
Enter fullscreen mode Exit fullscreen mode

Our PR template now focuses developers on the actual logic:

## What does this PR do?
[Describe the change]

## Why are we doing this?
[Explain the business value]

## Testing
[How to test this]

---
✅ Linting/formatting automated
✅ Unit tests automated
✅ Manual review focuses on: logic, architecture, edge cases
Enter fullscreen mode Exit fullscreen mode

Result: Code reviews went from 45 minutes to 15 minutes. Fewer "fix the indentation" comments.

The Numbers

After six months of automation implementation:

  • 400+ hours saved per year (10+ weeks)
  • 4x faster client onboarding
  • Zero deployment errors (down from ~2 per month)
  • 30 minutes to generate monthly reports (was 30+ hours)
  • 65% reduction in repetitive Slack messages
  • 2 developers could do work previously requiring 4

More importantly: developers were actually doing development. Engineering morale improved because nobody was stuck in the automation grind.

What We Learned

Start with auditing. You can't automate what you don't measure. Spend a week documenting everything.

Automate boring things first. Don't try to automate your complex deployment strategy if you haven't automated database seeding yet.

Use boring tools. Bash scripts, GitHub Actions, Terraform—they're not glamorous, but they work reliably. We didn't need a $500/month automation platform.

Keep humans in the loop for decisions. Automation handles repeatability. Humans handle judgment calls.

Document your automation. Your future self will thank you. An automated process that nobody understands is just technical debt.


Key Takeaways

  • Audit first: Track what's actually stealing your time before automating
  • Start small: Automate one 2-3 hour task to prove the ROI
  • Use boring technology: Bash, CI/CD, IaC—proven tools beat flashy ones
  • Free people up for creative work: The goal isn't to eliminate jobs; it's to eliminate busywork
  • **Compound

Top comments (0)