Remote Work Tools

n8n is an open-source workflow automation tool that self-hosts. Unlike Zapier or Make, you run it on your own server, pay nothing per workflow execution, and keep your data in your own infrastructure. For remote teams handling sensitive client data or running high-volume automations, n8n eliminates per-task pricing and data residency concerns.

This guide covers: self-hosted n8n setup, five practical remote team workflows, and error handling to make automations reliable.

Install n8n with Docker

# Production install with persistent storage and tunnel support
docker run -d \
  --name n8n \
  --restart unless-stopped \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  -e N8N_BASIC_AUTH_ACTIVE=true \
  -e N8N_BASIC_AUTH_USER=admin \
  -e N8N_BASIC_AUTH_PASSWORD=changeme-secure-password \
  -e N8N_HOST=automation.yourdomain.com \
  -e N8N_PORT=5678 \
  -e N8N_PROTOCOL=https \
  -e WEBHOOK_URL=https://automation.yourdomain.com/ \
  -e GENERIC_TIMEZONE=America/New_York \
  n8nio/n8n:latest

# Access at https://automation.yourdomain.com
# Default editor runs on port 5678
# Docker Compose with PostgreSQL for larger teams
# n8n with SQLite is fine for single users; use Postgres for teams
version: "3.9"

services:
  n8n:
    image: n8nio/n8n:latest
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      - WEBHOOK_URL=https://automation.yourdomain.com/
      - GENERIC_TIMEZONE=UTC
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  n8n_data: {}
  postgres_data: {}

Workflow 1: GitHub PR to Slack Notification with Context

The default GitHub → Slack integration only sends a link. This workflow sends a formatted message with reviewer names, labels, and a direct link to the diff.

{
  "name": "GitHub PR to Slack",
  "nodes": [
    {
      "type": "n8n-nodes-base.webhook",
      "name": "GitHub Webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "github-pr",
        "responseMode": "onReceived"
      }
    },
    {
      "type": "n8n-nodes-base.if",
      "name": "Filter PR events",
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.body.action }}",
              "operation": "equal",
              "value2": "opened"
            }
          ]
        }
      }
    },
    {
      "type": "n8n-nodes-base.slack",
      "name": "Send to Slack",
      "parameters": {
        "channel": "#engineering",
        "text": "New PR: {{ $json.body.pull_request.title }}",
        "attachments": [
          {
            "color": "#36a64f",
            "fields": [
              {
                "title": "Author",
                "value": "{{ $json.body.pull_request.user.login }}",
                "short": true
              },
              {
                "title": "Changes",
                "value": "+{{ $json.body.pull_request.additions }} / -{{ $json.body.pull_request.deletions }}",
                "short": true
              }
            ],
            "actions": [
              {
                "type": "button",
                "text": "Review PR",
                "url": "{{ $json.body.pull_request.html_url }}"
              }
            ]
          }
        ]
      }
    }
  ]
}

Workflow 2: Daily Standup Reminder with Auto-Summary

Trigger: Schedule — 9:00 AM Mon-Fri
Step 1: GitHub node — fetch commits from last 24 hours per developer
Step 2: Code node — format into standup summary
Step 3: Slack node — post to #standup channel
// Code node: format standup summary
const commits = items.flatMap(item => item.json.commits || []);

const byAuthor = commits.reduce((acc, commit) => {
  const author = commit.author?.login || commit.commit.author.name;
  if (!acc[author]) acc[author] = [];
  acc[author].push(commit.commit.message.split('\n')[0]);
  return acc;
}, {});

const message = Object.entries(byAuthor)
  .map(([author, msgs]) => {
    return `*${author}*:\n${msgs.map(m => `• ${m}`).join('\n')}`;
  })
  .join('\n\n');

return [{ json: { message: message || 'No commits in the last 24 hours.' } }];

Workflow 3: New Notion Page → Slack Alert

When anyone creates a new page in a specific Notion database (e.g., the Engineering Decisions database), notify the team immediately.

Trigger: Webhook (Notion webhook via Zapier or Notion API polling)
OR: Schedule — poll Notion API every 5 minutes

Step 1: HTTP Request — Notion API
  GET https://api.notion.com/v1/databases/DATABASE_ID/query
  Headers: Authorization: Bearer NOTION_TOKEN
  Body: {
    "filter": {
      "property": "Created time",
      "created_time": {
        "after": "{{ $now.minus(5, 'minutes').toISO() }}"
      }
    }
  }

Step 2: IF — results exist
  conditions: {{ $json.results.length > 0 }}

Step 3: Split in Batches — one notification per new page

Step 4: Slack — post to #decisions
  "New decision logged: {{ $json.properties.Name.title[0].text.content }}"
  "Author: {{ $json.created_by.name }}"
  "Link: {{ $json.url }}"

Workflow 4: Failed CI Build to Linear Issue

Automatically create a bug ticket when a CI build fails on main.

Trigger: Webhook (GitHub Actions calls this webhook on failure)
Step 1: HTTP Request — check if issue already exists in Linear
  GET https://api.linear.app/graphql
  Query: issues with title containing the workflow name

Step 2: IF — no duplicate issue

Step 3: HTTP Request — create Linear issue
  POST https://api.linear.app/graphql
  Headers: Authorization: YOUR_LINEAR_API_KEY
  Body: {
    "query": "mutation CreateIssue($input: IssueCreateInput!) { issueCreate(input: $input) { issue { id identifier } } }",
    "variables": {
      "input": {
        "title": "CI Failed: {{ $json.body.workflow }} on main",
        "description": "**Branch**: main\n**Commit**: {{ $json.body.commit_sha }}\n**Workflow**: [View run]({{ $json.body.run_url }})",
        "teamId": "YOUR_TEAM_ID",
        "priority": 2,
        "labelIds": ["BUG_LABEL_ID"]
      }
    }
  }
# In your GitHub Actions workflow, call the n8n webhook on failure
- name: Notify n8n on failure
  if: failure()
  run: |
    curl -X POST https://automation.yourdomain.com/webhook/ci-failure \
      -H "Content-Type: application/json" \
      -d '{
        "workflow": "${{ github.workflow }}",
        "commit_sha": "${{ github.sha }}",
        "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
      }'

Workflow 5: Weekly Team Metrics Digest

Trigger: Schedule — Friday 5:00 PM

Step 1: GitHub — fetch open PRs older than 48 hours
  GET /repos/:owner/:repo/pulls?state=open

Step 2: GitHub — fetch merged PRs this week
  GET /repos/:owner/:repo/pulls?state=closed&since=<monday_date>

Step 3: Linear — fetch completed issues this week
  GraphQL query with date filter

Step 4: Code node — format weekly digest message

Step 5: Slack — post to #engineering-metrics

Error Handling for Reliable Workflows

Workflows fail silently without error handling. Add error notifications:

For any workflow:
  1. Click "Add Error Workflow" in workflow settings
  2. Create a separate "Error Handler" workflow that:
     - Receives the failed workflow's name and error
     - Posts to Slack #automation-errors
     - Logs to a Notion database for tracking
// In the Error Handler workflow's Code node
const error = $input.first().json;

return [{
  json: {
    message: `Automation failed: *${error.workflow.name}*`,
    details: [
      `Error: ${error.execution.error?.message || 'Unknown'}`,
      `Node: ${error.execution.lastNodeExecuted || 'Unknown'}`,
      `Time: ${new Date(error.execution.startedAt).toISOString()}`,
      `<https://automation.yourdomain.com/workflow/${error.workflow.id}|View Workflow>`,
    ].join('\n'),
  }
}];

Manage Credentials Securely

n8n stores credentials encrypted in its database. For team environments, avoid hardcoding API keys in workflow nodes:

n8n → Settings → Credentials → Add Credential
  - GitHub API: Personal Access Token
  - Slack API: Bot Token
  - Notion API: Integration Token
  - Linear API: API Key

Then reference credentials by name in workflow nodes — never paste raw API keys into node parameters.

Built by theluckystrike — More at zovo.one