End-to-end B2B onboarding automation built with n8n, PostgreSQL, Redis, Docker, and Google Workspace integrations.
The platform coordinates the process from an authenticated CRM Deal Won event through client-data collection, validation, manual approval, external provisioning, Google Drive and Calendar setup, team notification, and durable completion. PostgreSQL remains authoritative while n8n orchestrates people, APIs, retries, and recovery.
Project status: this is a locally tested portfolio implementation, not a claimed customer production deployment. Mock CRM input and the Mock Provisioning API are replaceable integration adapters. No commercial outcomes or production-scale metrics are claimed.
| Repository evidence | Current tracked scope |
|---|---|
| Coordinated n8n workflows | 8 |
| Exported n8n nodes | 310 |
| Ordered database migrations | 14 |
| Application tables | 8 |
| SQL test suites / assertions | 5 / 46 |
| Docker Compose services | 6 |
- Problem
- Solution
- Workflow overview
- WF01: Deal Won intake
- Architecture
- Reliability design
- Database overview
- Testing and runtime evidence
- Repository structure
- Local setup
- Environment variables
- Demo evidence
- Tech stack
- Honest project status
Manual B2B onboarding is often fragmented across CRM notes, email threads, spreadsheets, shared folders, calendars, and internal tools. This creates predictable engineering risks:
- repeated data entry and inconsistent client records;
- duplicate external actions when events or jobs are retried;
- unreliable retries after partial failures;
- limited observability across the complete onboarding lifecycle;
- manual creation of Drive folders, Calendar events, and notifications;
- unclear ownership of state when multiple workers act concurrently;
- workflow execution history becoming an accidental business datastore.
The system separates orchestration from authoritative state:
- PostgreSQL owns cases, steps, submissions, tokens, clients, external-operation state, audit events, and errors.
- n8n validates workflow contracts and coordinates human and external-system interactions.
- Redis provides queue coordination for n8n main and two workers.
- Docker Compose runs the local platform as one repeatable stack.
- Google Workspace integrations deliver email, create onboarding folders, and schedule kickoff events.
- The Mock Provisioning API provides a deterministic REST boundary for success, replay, retryable failure, and terminal failure testing.
The business flow is:
flowchart LR
CRM["Mock CRM<br/>Deal Won"] --> WF01["WF01<br/>Intake Deal Won"]
WF01 --> WF02["WF02<br/>Request Client Data"]
WF02 --> WF03["WF03<br/>Receive and Validate Data"]
WF03 -->|"validation failed"| WF02
WF03 -->|"valid"| WF04["WF04<br/>Manual Approval"]
WF04 -->|"approved"| WF05["WF05<br/>Provision Client"]
WF04 -->|"rejected"| STOP["Terminal rejection"]
WF05 --> WF06["WF06<br/>Finalize Onboarding"]
WF06 --> DONE["Completed"]
WF98["WF98<br/>Retry Dispatcher"] -. "due retry or state gap" .-> WF02
WF98 -.-> WF04
WF98 -.-> WF05
WF98 -.-> WF06
WF01 -. "technical failure" .-> WF99["WF99<br/>Central Error Handler"]
WF02 -.-> WF99
WF03 -.-> WF99
WF04 -.-> WF99
WF05 -.-> WF99
WF06 -.-> WF99
Validation failure starts a new client-data request cycle. Approval rejection is terminal. Retryable technical failures remain persisted until PostgreSQL says they are due for another attempt.
| Workflow | Responsibility | Export and contract |
|---|---|---|
| WF01 Intake Deal Won | Authenticate, validate, normalize, and persist a CRM event; resolve duplicates and source conflicts; invoke WF02 when required. | Export · Contract |
| WF02 Request Client Data | Prepare a secure request cycle, deliver a single-use form link through Gmail, and persist or reconcile delivery. | Export · Contract |
| WF03 Receive and Validate Client Data | Authorize the form token, store an immutable submission, validate business data, and link a canonical client. | Export · Contract |
| WF04 Manual Approval | Send an approval request, wait for an authorized decision, and persist approval or rejection. | Export · Contract |
| WF05 Provision Client | Call the idempotent provisioning REST API and persist the external client ID or classified failure. | Export · Contract |
| WF06 Finalize Onboarding | Create or reconcile the Drive folder, kickoff event, and team notification before completing the case. | Export · Contract |
| WF98 Retry Dispatcher | Find due retries, expired leases, and supported state gaps, then dispatch recovery to the owning workflow. | Export · Contract |
| WF99 Central Error Handler | Sanitize and persist technical failures and coordinate deduplicated operator-intervention notifications. | Export · Contract |
WF01 is the public entry boundary for the onboarding process. Its 15-node export implements:
- n8n Header Auth before business processing;
- content-type, payload, and required-field validation;
- deterministic normalization of source, deal, company, contact, and metadata fields;
- one PostgreSQL transaction through
process_wf01_intake; - source-event idempotency and source-identity conflict detection;
- creation of one case and its seven steps when the deal is new;
- conditional dispatch to the real WF02 workflow;
- explicit dispatch-error routing to WF99 without rolling back committed intake data.
| HTTP | Confirmed outcome |
|---|---|
201 |
A new event and deal create one case; WF02 is invoked. |
200 |
A duplicate event or a new event for an existing deal reuses the same case; WF02 is not duplicated. |
400 |
A syntactically valid payload violates the input contract. |
403 |
Built-in n8n Header Auth rejects invalid credentials before WF01 executes. |
409 |
A known source event is presented with a conflicting deal identity. |
415 |
The request uses an unsupported content type. |
422 |
n8n rejects malformed JSON before the first WF01 node executes. |
500 |
WF02 dispatch acceptance fails after the intake transaction; WF99 records the technical failure. |
The runtime suite uses a unique event_id and deal_id for every run, polls asynchronous state with a timeout, checks HTTP responses and PostgreSQL counts, and restores the real WF02 target plus the original active/published workflow state after fault injection.
flowchart TB
CRM["Mock CRM / external REST caller"]
CLIENT["Client form user"]
OPERATOR["Approval operator"]
subgraph STACK["Docker Compose"]
MAIN["n8n main<br/>editor, webhooks, schedules"]
REDIS["Redis<br/>queue coordination"]
W1["n8n worker 1"]
W2["n8n worker 2"]
PG["PostgreSQL<br/>b2b_onboarding + n8n"]
MOCK["Mock Provisioning API<br/>REST /v1/clients"]
MAIN --> REDIS
REDIS --> W1
REDIS --> W2
MAIN --> PG
W1 --> PG
W2 --> PG
W1 --> MOCK
W2 --> MOCK
end
CRM -->|"authenticated Deal Won webhook"| MAIN
CLIENT <-->|"single-use form"| MAIN
OPERATOR <-->|"approval request and response"| MAIN
W1 --> GMAIL["Gmail API"]
W2 --> GMAIL
W1 --> DRIVE["Google Drive API"]
W2 --> DRIVE
W1 --> CAL["Google Calendar API"]
W2 --> CAL
Business state does not depend on n8n execution retention. See the architecture document for the state machine, trust boundaries, and external-operation protocol.
| Pattern | Implementation |
|---|---|
| PostgreSQL source of truth | Workflows reread authoritative case, step, token, and operation records before state-sensitive actions. |
| Idempotency keys | Every external side effect has a deterministic key; the provisioning API also consumes Idempotency-Key. |
| Database constraints | Unique source identities, valid states, one operation per key, immutable submissions, and token lifecycle rules are enforced in SQL. |
| Leases | Atomic claims use an owner and expiry so concurrent workers cannot perform the same operation simultaneously. |
| Retry scheduling | Attempt count, maximum attempts, and next_retry_at are persisted; WF98 selects due work using PostgreSQL time. |
| Reconciliation | Gmail markers, Drive appProperties, and Calendar private extended properties are checked before ambiguous actions are repeated. |
| Duplicate prevention | Replayed CRM events, provisioning calls, email sends, folders, and events reuse persisted identities. |
| Append-only events | A trigger rejects updates and deletes from onboarding_events. |
| Central error handling | WF99 sanitizes technical context, writes error_log, and deduplicates intervention notifications. |
| Partial-failure recovery | Successful provider work is retained; a later execution resumes from the earliest incomplete or uncertain operation. |
Additional safeguards include single-use expiring form tokens, SHA-256 token hashes, temporary AES-GCM-encrypted delivery material, immutable versioned submissions, transition guards, and separation of business outcomes from technical errors.
The application schema contains eight tables defined by migration 001 and extended through migration 014.
| Table | Responsibility |
|---|---|
clients |
Canonical validated client identity and contact data. |
onboarding_cases |
One authoritative case per source deal, current state, and accepted client/submission links. |
onboarding_steps |
Seven required steps with status, attempts, wait state, decision, and completion fields. |
onboarding_form_tokens |
Hashed, expiring, single-use tokens and temporary encrypted delivery material. |
onboarding_submissions |
Immutable versioned submissions and deterministic validation results. |
onboarding_events |
Append-only idempotent business audit events. |
external_operations |
Side-effect identity, lease, attempts, retry schedule, external ID, and sanitized result. |
error_log |
Sanitized technical and integration failures. |
Every case receives these seven step records: collect_client_data, validate_client_data, manual_approval, provision_client, create_drive_folder, create_kickoff_event, and notify_team.
001: tables, constraints, triggers, token consumption, operation claims, and guarded transitions;002–010: WF02/WF03 token delivery, reconciliation, retry, and validation finalization;011: WF04 approval preparation and decision/failure finalization;012: WF05 provisioning preparation and success/failure finalization;013: WF06 Drive, Calendar, notification, recovery, and case completion functions;014: atomic WF01 intake resolution, idempotency, and source-identity validation.
Key database APIs include process_wf01_intake, claim_external_operation, complete_external_operation_success, complete_external_operation_failure, finalize_wf03_validation, the WF04/WF05/WF06 preparation and finalization functions, and complete_wf06_onboarding.
The five SQL suites run in transactions and roll back their fixtures:
for test_file in db/tests/*.sql; do
docker compose exec -T postgres sh -lc \
'psql -X -v ON_ERROR_STOP=1 -U "$APP_DB_USER" -d "$APP_DB_NAME"' \
< "$test_file"
doneCurrent verified result: 46 assertions passed across foundation, WF01, WF04, WF05, and WF06 suites.
scripts/test_wf01_runtime.sh automates scenarios A–I. The Header Auth value is supplied only at runtime:
WF01_AUTH_HEADER='replace-with-local-header-value' \
./scripts/test_wf01_runtime.shThe suite verifies:
- new Deal Won intake and WF02 delivery;
- duplicate event and existing-deal suppression;
- source-identity conflict;
- missing email and unsupported content type;
- n8n malformed-JSON
422and Header Auth403behavior; - a temporary missing-WF02 fault, WF99 completion, one
error_logrecord, and restoration of the real workflow configuration.
Running this suite with real Google credentials sends one test Gmail message; it should be used deliberately.
docker compose exec -T mock-provisioning-api npm testThe six Node.js tests cover health, idempotent replay, conflicting payloads for one key, retryable-once recovery, persistent retryable failure, and terminal rejection.
docker compose config --quiet
for workflow in n8n/workflows/*.json; do
jq empty "$workflow"
done
bash -n scripts/test_wf01_runtime.sh
git diff --checkThe current repository contains 8 workflow exports, 310 nodes, and 137 Code nodes. Code-node syntax is also compiled with Node.js during local verification.
The following evidence was produced locally with Docker, n8n queue mode, PostgreSQL, Redis, the mock API, and configured Google integrations:
- WF01 scenarios A–I passed with HTTP and PostgreSQL assertions;
- a new intake created one case and seven steps, invoked WF02 once, and reached
awaiting_client_data; - duplicate deliveries did not create another case, business operation, or WF02 dispatch;
- dispatch fault injection returned
500, retained the committed case increated, and produced exactly one WF99 error record; - a recovery-focused finalization case reached
completedwith all seven steps completed and succeeded Drive, Calendar, and team-notification operations; - the successful Gmail path retained its case and correlation context; the failure-context path was checked deterministically without forcing another provider failure.
The finalization evidence demonstrates recovery from persisted state. It is not presented as a single uninterrupted customer run from WF01 through WF06.
.
├── .env.example
├── README.md
├── docker-compose.yml
├── docker-compose.override.yml
├── db/
│ ├── init/ # Database bootstrap
│ ├── migrations/ # Ordered migrations 001-014
│ └── tests/ # Five transactional SQL suites
├── docs/
│ ├── architecture.md
│ ├── project-plan.md
│ └── workflow-contracts/ # WF01-WF06, WF98, WF99
├── n8n/
│ └── workflows/ # Eight exported workflow JSON files
├── scripts/ # Runtime checks and targeted deployment helpers
└── services/
└── mock-provisioning-api/ # Node.js REST adapter and tests
- Docker Engine or Docker Desktop with Compose v2;
- Git,
curl, andjq; - Node.js 24 or a compatible modern runtime for direct static and mock-service tests;
- a Google Cloud OAuth project with Gmail, Drive, and Calendar APIs enabled only if exercising real integrations.
git clone https://github.com/Pokhyl/b2b-client-onboarding-platform.git
cd b2b-client-onboarding-platform
cp .env.example .envReplace every replace_with_... value. Also add the required local WF99 recipient used by docker-compose.override.yml:
OPERATOR_INTERVENTION_RECIPIENT_EMAIL=operator@example.comNever commit .env, OAuth tokens, n8n credential exports, form tokens, approval links, email contents, or provider resource identifiers.
docker compose up -d --build
docker compose psThe stack starts PostgreSQL, Redis, n8n main, two n8n workers, and the Mock Provisioning API.
On a freshly initialized database, apply migrations in order as the application database owner:
for migration in db/migrations/[0-9][0-9][0-9]_*.sql; do
docker compose exec -T postgres sh -lc \
'psql -X -v ON_ERROR_STOP=1 -U "$APP_DB_USER" -d "$APP_DB_NAME"' \
< "$migration"
donefor workflow in n8n/workflows/*.json; do
filename="$(basename "$workflow")"
docker compose cp "$workflow" "n8n-main:/tmp/$filename"
docker compose exec -T n8n-main \
n8n import:workflow --input="/tmp/$filename"
doneOpen http://localhost:5678, relink imported nodes to credentials created in the local n8n instance, verify webhook/form URLs, and activate only the intended workflows. Exported workflow files contain credential references, never credential values.
Required n8n credential types are PostgreSQL, shared Header Auth for protected webhooks, Gmail OAuth2, Google Drive OAuth2, and Google Calendar OAuth2.
curl --fail http://localhost:5678/healthz
curl --fail http://localhost:3001/healthz
docker compose psUse .env.example as the placeholder-only baseline.
| Group | Variables |
|---|---|
| PostgreSQL bootstrap | POSTGRES_ADMIN_USER, POSTGRES_ADMIN_PASSWORD, POSTGRES_HOST_PORT |
| Application database | APP_DB_NAME, APP_DB_USER, APP_DB_PASSWORD |
| n8n database | N8N_DB_NAME, N8N_DB_USER, N8N_DB_PASSWORD |
| Redis and queue mode | REDIS_PASSWORD, N8N_WORKER_CONCURRENCY |
| n8n runtime | N8N_ENCRYPTION_KEY, N8N_HOST, N8N_PORT, N8N_PROTOCOL, N8N_EDITOR_BASE_URL, WEBHOOK_URL, N8N_VERSION |
| WF02 request cycle | CLIENT_DATA_FORM_BASE_URL, token TTL/encryption settings, WF02 lease/attempt settings, sender/support values |
| WF04 approval | recipient, template, response timeout, lease/attempt, sender/support values |
| WF05 provisioning | PROVISIONING_API_BASE_URL, WF05_PROVISIONING_SCENARIO, WF05 lease/attempt settings |
| WF06 finalization | Drive parent/template, Calendar/time settings, attendee, and team-notification settings |
| WF99 intervention | OPERATOR_INTERVENTION_RECIPIENT_EMAIL; optional sender/template and lease/attempt settings from the Compose override |
| Mock service | MOCK_PROVISIONING_API_PORT |
All real passwords, encryption keys, OAuth material, recipients, and Google resource IDs belong only in the local .env or n8n credential store.
No portfolio screenshots are committed yet, and this README does not use fabricated images.
Useful future evidence, after redaction:
- Architecture: the Compose services alongside the architecture diagram above.
- Workflow canvas: a sanitized WF01 intake route and WF06 reconciliation route.
- Database audit trail: one test case with seven steps, deterministic operations, and append-only events.
- Fault injection: WF01 dispatch failure routed to WF99 with the case retained after commit.
- External integration: sanitized Drive, Calendar, and Gmail test artifacts with all account, email, URL, and resource identifiers removed.
The repository itself provides reproducible evidence through workflow JSON, migrations, SQL tests, the mock API tests, the WF01 runtime script, and the associated Git history.
| Area | Technology |
|---|---|
| Workflow orchestration | n8n 2.30.8, queue mode |
| Workflow logic | JavaScript in n8n Code nodes |
| Business persistence | PostgreSQL 18.4 |
| Queue coordination | Redis 8.8 |
| Local orchestration | Docker Compose |
| External adapter | Node.js 24 Mock Provisioning API |
| Integrations | REST APIs, Gmail, Google Drive, Google Calendar |
| Authentication | Header Auth, n8n credentials, Google OAuth2 |
| Verification | PostgreSQL test scripts, Node.js tests, jq, shell syntax checks |
This repository is a portfolio implementation of a production-oriented architecture. It demonstrates durable state, idempotent external operations, bounded retries, reconciliation, duplicate prevention, and recovery after partial failure in a local environment.
The Mock CRM event source and Mock Provisioning API can be replaced with a real CRM, ERP, IAM platform, or internal service API if the adapter preserves the documented authentication, idempotency, and response contracts.
The project has not been presented as a real customer deployment. It makes no claims about revenue, time savings, percentages, service-level objectives, production traffic, or commercial outcomes.