End-to-End n8n Tutorial: Automating an App Development Pipeline (PM → Design → QA → Dev → QA → Publish) cover image

End-to-End n8n Tutorial: Automating an App Development Pipeline (PM → Design → QA → Dev → QA → Publish)

Published 5 hours ago • 5 mins read

From project intake to design, QA, development, and publishing—this hands-on tutorial shows how to wire an opinionated, production-ready workflow in n8n with approvals, rollbacks, observability, and SLAs.

TL;DR One orchestrator workflow calls modular sub-workflows: Intake → Design → Design-QA → Dev → Dev-QA → Publish. Decisions and approvals happen via Slack + Webhook. Failures route to an Error workflow with retries & alerts.

Prerequisites

  • n8n (self-hosted or cloud), with Webhook, HTTP Request, Slack, Notion/Trello/Jira, GitHub, Google Drive/S3, Function, IF, Wait, Merge, Execute Workflow nodes.
  • Credentials: Slack Bot token, GitHub PAT, Jira/Notion API, Store APIs (Google Play Developer API / App Store Connect) or deployment webhooks for web apps.
  • Environment variables in n8n: N8N_BASE_URL, SLACK_CHANNEL_ENGINEERING, GITHUB_ORG, ARTIFACT_BUCKET.

Architecture Overview

[Webhook: Project Intake] 
   → [Function: Normalize Payload] 
   → [Execute Workflow: DESIGN] 
   → [Execute Workflow: DESIGN_QA (approval loop)] 
   → [Execute Workflow: DEVELOPMENT] 
   → [Execute Workflow: DEV_QA (test gates)] 
   → [Execute Workflow: PUBLISH]
           ↘ on_error → [Error Workflow: Alert + Ticket + Retry/Abort]
    

Keep each phase in a dedicated workflow for reusability. The orchestrator passes a context object (project id, platform, repo, build number, SLA timestamps).

Canonical Context Object

{
  "projectId": "PRJ-2417",
  "client": "Acme Co",
  "platforms": ["ios","android","web"],
  "repo": "github.com/{{GITHUB_ORG}}/acme-app",
  "branch": "release/1.4.0",
  "designSpecUrl": "https://notion.so/.../spec",
  "deadline": "2025-09-10T23:59:00Z",
  "sla": { "designHrs": 24, "qaHrs": 16, "publishHrs": 8 },
  "artifacts": { "figma": "", "builds": [], "testReport": "" },
  "approvals": { "designLead": false, "productOwner": false, "qaLead": false }
}
    

Workflow 0 — Orchestrator & Intake

  1. Webhook (POST /intake): Accept new project payload or manual start.
  2. Function: validate inputs; hydrate defaults; compute SLA timestamps.
  3. Jira/Notion Node: create master ticket/page; store context.
  4. Slack: announce kickoff with deep links and “Approve/Block” buttons (use Slack interactivity to hit an Approve Webhook in n8n).
  5. Execute WorkflowDESIGN, pass context as JSON.
// Function: Normalize Payload (snippet)
const body = items[0].json;
if (!body.projectId) throw new Error("projectId required");
const now = new Date();
return [{ json: {
  ...body,
  sla: body.sla ?? { designHrs:24, qaHrs:16, publishHrs:8 },
  createdAt: now.toISOString()
}}];

Workflow 1 — DESIGN (Spec, Assets, Handoff)

  • HTTP Request → Figma API: export redlines & assets; save to S3/Drive.
  • Set: write artifacts.figma and asset URLs into context.
  • Slack: post design preview; include Approve/Request-Changes buttons.
  • Wait for Webhook: /design/approve or /design/changes.
  • IF: if changes → loop back to design node (limit 3 cycles with a counter).
  • Merge (Pass-through): return updated context to orchestrator.
// Slack button payload (example)
{
  "text": "Design ready for PRJ-2417",
  "actions": [
    { "type":"button","text":"✅ Approve","url":"{{N8N_BASE_URL}}/webhook/design/approve?projectId=PRJ-2417" },
    { "type":"button","text":"📝 Request Changes","url":"{{N8N_BASE_URL}}/webhook/design/changes?projectId=PRJ-2417" }
  ]
}

Workflow 2 — DESIGN-QA (Pixel Checks, Accessibility)

  1. HTTP Request → Visual regression service (Percy/Chromatic) start build.
  2. IF: accessibility scan (Stark/axe API) score < threshold → open ticket + notify.
  3. Wait: block until QA lead hits /design-qa/approve.
  4. Slack: “Design-QA Passed” with report links.

Workflow 3 — DEVELOPMENT (Branch, Build, Artifact)

  • GitHub: create branch release/x.y.z; open PR with checklist.
  • HTTP Request: trigger CI (GitHub Actions/CircleCI) with context vars.
  • Wait: poll CI status endpoint; on failure → notify & retry up to 2.
  • HTTP Request: fetch build artifacts (APK/IPA/web bundle); push to S3/Drive.
  • Set: append artifact URLs to artifacts.builds.
# Example CI dispatch (GitHub Actions)
POST https://api.github.com/repos/{{GITHUB_ORG}}/acme-app/actions/workflows/release.yml/dispatches
{
  "ref": "release/1.4.0",
  "inputs": { "platforms":"ios,android,web", "projectId":"PRJ-2417" }
}

Workflow 4 — DEV-QA (Automated & Manual Gates)

Gate Node(s) Pass Criteria
Unit & Lint HTTP Request → CI reports Coverage ≥ 80%, zero critical
E2E Tests HTTP Request → Playwright/Cypress Pass rate ≥ 95%
Beta Distribution HTTP → Firebase App Distribution/TestFlight Smoke OK on 3 devices
Manual QA Sign-off Wait for Webhook from Slack “Approve Release” QA lead approves

Failures open/attach to the release ticket and trigger auto-rollback of the CI environment if needed.

Workflow 5 — PUBLISH (Stores & Web)

  • IF platform contains androidHTTP Google Play: upload bundle, track “internal” → promote to “production”.
  • IF platform contains iosHTTP App Store Connect: upload via CI; submit for review.
  • IF platform contains webHTTP to hosting (Vercel/Netlify/CloudFront invalidation) to deploy.
  • Slack: publish summary with build numbers, changelog, links.
  • Notion/Jira: update release page with artifacts & audit trail.
// Example publish summary payload
{
  "projectId":"PRJ-2417",
  "version":"1.4.0",
  "ios":"App Store submission #1023",
  "android":"Play rollout 20% staged",
  "web":"Deployed to prod @ 14:20 UTC",
  "artifacts":["s3://artifacts/acme/1.4.0/app-release.apk","..."],
  "changelog":"Bug fixes, performance improvements"
}

Global Error Handling & Resilience

  1. Error Trigger workflow: catches any failure; posts to #eng-alerts with run URL.
  2. Function: categorize error (network, validation, auth, store review) → choose path.
  3. Retry policy: 3 attempts, exponential backoff (15s, 60s, 5m) for transient HTTP 5xx.
  4. Dead-letter: write failed payload + logs to S3 with projectId/runId.
  5. Ticket: auto-create/attach incident to release ticket with labels.

Approvals & Human-in-the-Loop

Use Slack interactive buttons that hit n8n Webhooks. Persist decisions inside the context.approvals object and enforce at each gate.

// Example gate check (Function node)
const c = items[0].json.context;
if (!c.approvals.designLead) throw new Error("Design approval missing");
return items;

SLAs, Metrics, and Reporting

  • Emit metrics per step: start/end timestamps, retries, latency. Store in a sheet/DB.
  • Daily digest workflow: aggregates cycle times by phase; posts a burn-down to Slack.
  • Auto-escalate if now > deadline - publishHrs and approvals pending.

Handling All Cases (Branches & Edge Conditions)

  • Multi-platform: Use Split In Batches to iterate platforms array; merge results.
  • Hotfix vs Release: Switch on branch pattern; hotfix skips design phases.
  • Design changes late: If a change arrives after DEV-QA, reopen DESIGN-QA sub-workflow and bump version (e.g., 1.4.1-rc1).
  • Store rejection: Route to “Rework” lane, create tasks, loop back to DEV-QA automatically.
  • Client veto: Approve button writes approvals.productOwner=false and halts downstream nodes with a clear Slack summary.

Export & Reuse

Tag workflows with team=mobile, type=qa, lane=publish. Keep a Template folder; export JSONs to Git for version control and peer review.

// Minimal Execute Workflow mapping
{
  "workflowId": "WORKFLOW_DEV_QA",
  "options": { "waitForExecution": true },
  "jsonParameters": true,
  "parameters": { "context": "={{$json}}" }
}
Pro Tip

Add a small “Runbook URL” to the context and include it in every Slack alert. When things break at 2 AM, the on-call engineer gets exactly the steps to fix or rollback.


Join my mailing list