DEV Community

Joshua Matthews
Joshua Matthews

Posted on

Automate the Boring Stuff: Process Automation Patterns That Scale

Every business has repetitive tasks eating up hours. Data entry, report generation, notification workflows, file processing - boring work that humans shouldn't be doing.

Here's how to automate it properly.

Identify the Right Candidates

Not everything should be automated. Good automation candidates are:

  • Repetitive: Done more than once a week
  • Rule-based: Clear logic, not requiring human judgment
  • Time-consuming: Takes more than 5 minutes each time
  • Error-prone: Humans make mistakes doing it

Bad candidates: creative tasks, one-time jobs, anything requiring nuanced decisions.

The Event-Driven Architecture

Most automations follow this pattern:

Trigger → Process → Action → Notification
Enter fullscreen mode Exit fullscreen mode

Triggers: New file uploaded, form submitted, scheduled time, API webhook
Process: Transform data, apply business logic, make decisions
Action: Send email, update database, call API, generate document
Notification: Alert humans when needed

Building a Simple Automation Pipeline

// automation-pipeline.js
const Queue = require('bull');
const Redis = require('ioredis');

// Create a queue for each automation type
const invoiceQueue = new Queue('invoice-processing', {
  redis: { host: 'localhost', port: 6379 }
});

// Define the processor
invoiceQueue.process(async (job) => {
  const { invoiceId } = job.data;

  try {
    // 1. Fetch the invoice
    const invoice = await db.invoices.findById(invoiceId);

    // 2. Generate PDF
    const pdf = await generateInvoicePDF(invoice);

    // 3. Send to customer
    await sendEmail({
      to: invoice.customerEmail,
      subject: `Invoice #${invoice.number}`,
      attachments: [{ filename: 'invoice.pdf', content: pdf }]
    });

    // 4. Update status
    await db.invoices.update(invoiceId, { status: 'sent', sentAt: new Date() });

    return { success: true, invoiceId };
  } catch (error) {
    // Log error, will be retried automatically
    console.error(`Failed to process invoice ${invoiceId}:`, error);
    throw error;
  }
});

// Add job when invoice is created
async function onInvoiceCreated(invoice) {
  await invoiceQueue.add(
    { invoiceId: invoice.id },
    {
      attempts: 3,
      backoff: { type: 'exponential', delay: 1000 }
    }
  );
}
Enter fullscreen mode Exit fullscreen mode

Scheduled Automations with Cron

// scheduled-tasks.js
const cron = require('node-cron');

// Daily report at 8 AM
cron.schedule('0 8 * * *', async () => {
  console.log('Running daily report...');

  const yesterday = getYesterdayDate();
  const stats = await db.orders.getStats(yesterday);

  await sendSlackMessage({
    channel: '#daily-reports',
    text: formatDailyReport(stats)
  });
});

// Weekly backup every Sunday at 2 AM
cron.schedule('0 2 * * 0', async () => {
  console.log('Running weekly backup...');
  await backupDatabase();
});

// Check for expiring subscriptions every hour
cron.schedule('0 * * * *', async () => {
  const expiringIn24h = await db.subscriptions.findExpiring(24);

  for (const sub of expiringIn24h) {
    if (!sub.reminderSent) {
      await sendExpiryReminder(sub);
      await db.subscriptions.markReminderSent(sub.id);
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

File Processing Automation

// file-processor.js
const chokidar = require('chokidar');
const path = require('path');

// Watch a folder for new files
const watcher = chokidar.watch('/uploads/incoming', {
  ignored: /(^|[\/\\])\../, // Ignore dotfiles
  persistent: true
});

watcher.on('add', async (filePath) => {
  console.log(`New file detected: ${filePath}`);

  const ext = path.extname(filePath).toLowerCase();

  switch (ext) {
    case '.csv':
      await processCSV(filePath);
      break;
    case '.pdf':
      await processPDF(filePath);
      break;
    case '.jpg':
    case '.png':
      await processImage(filePath);
      break;
    default:
      console.log(`Unknown file type: ${ext}`);
  }

  // Move to processed folder
  await moveFile(filePath, '/uploads/processed/');
});

async function processCSV(filePath) {
  const records = await parseCSV(filePath);

  for (const record of records) {
    await db.imports.create({
      ...record,
      source: filePath,
      importedAt: new Date()
    });
  }

  await notify(`Imported ${records.length} records from ${path.basename(filePath)}`);
}
Enter fullscreen mode Exit fullscreen mode

Webhook-Triggered Automations

// webhooks.js
const express = require('express');
const crypto = require('crypto');

const app = express();

// Verify webhook signatures
function verifyWebhook(req, secret) {
  const signature = req.headers['x-webhook-signature'];
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(req.body))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Stripe payment webhook
app.post('/webhooks/stripe', express.json(), async (req, res) => {
  if (!verifyWebhook(req, process.env.STRIPE_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;

  switch (event.type) {
    case 'payment_intent.succeeded':
      await handlePaymentSuccess(event.data.object);
      break;
    case 'customer.subscription.deleted':
      await handleSubscriptionCancelled(event.data.object);
      break;
  }

  res.json({ received: true });
});

async function handlePaymentSuccess(payment) {
  // Create order record
  const order = await db.orders.create({
    paymentId: payment.id,
    amount: payment.amount,
    status: 'paid'
  });

  // Send confirmation email
  await sendOrderConfirmation(order);

  // Update inventory
  await updateInventory(order.items);

  // Notify team
  await sendSlackNotification(`New order: £${payment.amount / 100}`);
}
Enter fullscreen mode Exit fullscreen mode

Error Handling and Monitoring

Automations fail. Plan for it:

// error-handling.js
class AutomationError extends Error {
  constructor(message, context) {
    super(message);
    this.context = context;
    this.timestamp = new Date();
  }
}

async function runWithRetry(fn, options = {}) {
  const { maxAttempts = 3, delay = 1000, backoff = 2 } = options;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) {
        // Final failure - alert humans
        await alertOnFailure(error, { attempt, maxAttempts });
        throw error;
      }

      const waitTime = delay * Math.pow(backoff, attempt - 1);
      console.log(`Attempt ${attempt} failed, retrying in ${waitTime}ms...`);
      await sleep(waitTime);
    }
  }
}

async function alertOnFailure(error, context) {
  // Log to monitoring system
  await logger.error('Automation failed', {
    error: error.message,
    stack: error.stack,
    context
  });

  // Send alert
  await sendSlackMessage({
    channel: '#automation-alerts',
    text: `🚨 Automation failed: ${error.message}\nContext: ${JSON.stringify(context)}`
  });
}
Enter fullscreen mode Exit fullscreen mode

The Monitoring Dashboard

Track your automations:

// metrics.js
const metrics = {
  jobs_processed: new Counter('jobs_processed_total'),
  jobs_failed: new Counter('jobs_failed_total'),
  job_duration: new Histogram('job_duration_seconds')
};

// Wrap job processing
async function processWithMetrics(jobType, fn) {
  const timer = metrics.job_duration.startTimer({ job_type: jobType });

  try {
    const result = await fn();
    metrics.jobs_processed.inc({ job_type: jobType, status: 'success' });
    return result;
  } catch (error) {
    metrics.jobs_processed.inc({ job_type: jobType, status: 'failure' });
    metrics.jobs_failed.inc({ job_type: jobType });
    throw error;
  } finally {
    timer();
  }
}
Enter fullscreen mode Exit fullscreen mode

When to Use Off-the-Shelf Tools

Don't reinvent the wheel. For common automations:

  • Zapier/Make: Quick integrations between SaaS tools
  • n8n: Self-hosted workflow automation
  • Temporal: Complex, long-running workflows
  • AWS Step Functions: Serverless orchestration

Build custom only when off-the-shelf tools don't fit or costs become prohibitive at scale.

The Automation Checklist

Before deploying any automation:

  • [ ] Error handling and retry logic in place
  • [ ] Monitoring and alerting configured
  • [ ] Logs capture enough context to debug issues
  • [ ] Manual override available for emergencies
  • [ ] Documentation for how it works and how to maintain it
  • [ ] Test cases for edge conditions

Start Small

Pick one painful, repetitive task. Automate it. Measure the time saved.

Then do the next one.

Over time, those hours add up to days, then weeks of reclaimed productivity.


At LogicLeap, we build automation systems that run while you sleep. Data pipelines, process automation, and integrations that just work.

Top comments (0)