Technical Architecture

Deep dive into the internals of Automation Hub — from event-driven workflows to multi-tenant isolation, queue management, and the Module SDK.

v1.0.0 Event-Driven Multi-Tenant Modular

Technology Stack

LayerTechnologyVersionPurpose
Backend FrameworkLaravel12.xAPI, routing, queue, ORM, middleware
Frontend FrameworkReact19.xSPA UI with hooks and concurrent features
BridgeInertia.js2.xServer-driven SPA without REST boilerplate
StylingTailwind CSSv4Utility-first CSS with design tokens
DatabaseMySQL8.xPrimary relational data store
LanguageTypeScript5.xType-safe frontend development
Canvas Editor@xyflow/reactv12Visual workflow drag-and-drop canvas
Build ToolVite6.xHot module replacement and bundling
QueueLaravel Queue (database)-Async job processing
AuthLaravel Fortify + Sanctum-Session auth, 2FA, API tokens

Event-Driven Architecture

Automation Hub follows an event-driven architecture where all workflow executions are triggered by events. The system decouples event producers from consumers through an internal Event Bus.

Event Flow Diagram
┌──────────────────────────────────────────────────────────────────────────┐
│                          EVENT SOURCES                                     │
├──────────────┬──────────────┬──────────────┬──────────────┬──────────────┤
│  Webhook     │  Schedule    │  API Call    │  Module      │  Manual      │
│  Trigger     │  (cron)      │  Trigger     │  Event       │  Trigger     │
└──────┬───────┴──────┬───────┴──────┬───────┴──────┬───────┴──────┬───────┘
       │              │              │              │              │
       └──────────────┴──────────────┴──────────────┴──────────────┘
                                     │
                                     ▼
                        ┌────────────────────────┐
                        │       EventBus          │
                        │  (App\Services\EventBus)│
                        │                        │
                        │  • Receives events     │
                        │  • Finds matching      │
                        │    workflow triggers    │
                        │  • Dispatches jobs     │
                        └───────────┬────────────┘
                                    │
                                    ▼
                        ┌────────────────────────┐
                        │   ProcessEventJob       │
                        │  (Queued, async)        │
                        │                        │
                        │  • Creates execution   │
                        │  • Invokes engine      │
                        └───────────┬────────────┘
                                    │
                                    ▼
                        ┌────────────────────────┐
                        │    WorkflowEngine       │
                        │                        │
                        │  • Topological sort    │
                        │  • Execute nodes       │
                        │  • Handle conditions   │
                        │  • Pause/resume        │
                        │  • Error handling      │
                        └────────────────────────┘

Why Event-Driven?

Event-driven architecture allows workflows to react to external stimuli in real-time without polling. It also enables loose coupling between modules — a WhatsApp message, an email bounce, or a webhook hit all flow through the same pipeline.

Multi-Tenant Model

Automation Hub uses a shared-database, shared-schema multi-tenancy approach. Every tenant-scoped table includes an organization_id column, and data isolation is enforced at the query level via a Laravel global scope.

PHP — OrganizationScope
// app/Scopes/OrganizationScope.php
class OrganizationScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if (auth()->check() && auth()->user()->current_organization_id) {
            $builder->where(
                $model->getTable() . '.organization_id',
                auth()->user()->current_organization_id
            );
        }
    }
}

// Usage in any tenant-scoped model:
class Workflow extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new OrganizationScope);
    }
}

Key characteristics of the multi-tenant model:

Module SDK Pattern

Modules are self-contained feature packages that extend Automation Hub's capabilities. Each module implements the ModuleInterface contract and registers its components through the ModuleSDK facade.

PHP — ModuleInterface Contract
interface ModuleInterface
{
    public function identifier(): string;       // e.g., 'email', 'whatsapp'
    public function name(): string;             // Human-readable name
    public function version(): string;          // Semver
    public function description(): string;
    public function register(ModuleSDK $sdk): void;
    public function boot(): void;
    public function getConfigSchema(): array;   // JSON Schema for settings
    public function validateConfig(array $config): bool;
}
PHP — Module Registration Example
class EmailModule implements ModuleInterface
{
    public function register(ModuleSDK $sdk): void
    {
        // Register node executors (action types)
        $sdk->registerExecutor('action.email.send', EmailSendExecutor::class);

        // Register event listeners
        $sdk->registerListener('user.registered', WelcomeEmailListener::class);

        // Register permissions
        $sdk->registerPermissions([
            'email.send',
            'email.configure',
            'email.view_logs',
        ]);

        // Register routes
        $sdk->registerRoutes(__DIR__ . '/../routes/email.php');

        // Register configuration schema
        $sdk->registerConfigSchema($this->getConfigSchema());
    }
}

The SDK registration pattern ensures:

Workflow Engine Internals

The WorkflowEngine is the heart of Automation Hub. It takes a workflow definition (a directed acyclic graph of nodes and edges) and executes it deterministically.

Execution Lifecycle

Execution Lifecycle
1. EVENT RECEIVED
   └─▶ EventBus matches event type to workflow triggers

2. EXECUTION CREATED
   └─▶ workflow_executions record: status = 'running'

3. TOPOLOGICAL SORT
   └─▶ Nodes ordered by dependency (edges define order)
   └─▶ Kahn's algorithm ensures no node runs before its inputs

4. NODE EXECUTION (iterative)
   ├─▶ Check condition (if conditional node)
   ├─▶ Resolve variables ({{step_id.output.field}})
   ├─▶ Call executor (module-provided or core)
   ├─▶ Store output in execution_steps
   └─▶ Determine next nodes (follow edges)

5. PAUSE/RESUME (if delay or wait node)
   ├─▶ Status → 'paused'
   ├─▶ ResumeWorkflowJob dispatched with delay
   └─▶ On resume: continue from paused step

6. COMPLETION
   ├─▶ Status → 'completed' or 'failed'
   └─▶ Duration and metadata recorded

Topological Sort

The engine uses Kahn's algorithm for topological sorting. This guarantees that nodes are executed in the correct order, respecting all edge dependencies. The algorithm:

  1. Builds an adjacency list and in-degree count from the workflow edges
  2. Starts with nodes that have zero in-degree (trigger nodes)
  3. Processes nodes in BFS order, decrementing in-degree of downstream nodes
  4. Detects cycles (if any nodes remain unprocessed, the graph is invalid)

Error Handling

When a node execution fails:

Pause and Resume

Delay nodes and human-approval nodes can pause execution. When paused:

RBAC System

The Role-Based Access Control system provides granular permission management at the organization level.

RBAC Entity Relationships
┌──────────────┐       ┌──────────────┐       ┌──────────────────┐
│    Users     │──M:N──│Organizations │──1:N──│      Roles       │
└──────────────┘       └──────────────┘       └───────┬──────────┘
                                                      │
                              pivot: organization_user │  M:N
                              (user_id, org_id,        │
                               role_id)                ▼
                                              ┌──────────────────┐
                                              │   Permissions    │
                                              │                  │
                                              │ • workflows.*    │
                                              │ • modules.*      │
                                              │ • settings.*     │
                                              │ • users.*        │
                                              │ • api_keys.*     │
                                              └──────────────────┘
Default RoleDescriptionKey Permissions
ownerFull control over the organizationAll permissions (wildcard)
adminManage users, modules, and workflowsAll except billing and org deletion
editorCreate and edit workflowsworkflows.*, modules.view, executions.view
viewerRead-only access*.view only

Permissions follow a resource.action naming convention (e.g., workflows.create, workflows.delete, modules.configure). Modules can register additional permissions via the SDK.

API Authentication

The REST API uses bearer token authentication with SHA-256 hashed keys stored in the database. Keys are never stored in plain text.

API Key Architecture
┌─────────────────────────────────────────────────────────────────┐
│                      API Key Lifecycle                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. User creates key in UI                                       │
│     └─▶ Plain key shown ONCE: ah_live_abc123...                  │
│     └─▶ SHA-256 hash stored: hash('sha256', $plainKey)           │
│                                                                  │
│  2. Client sends request                                         │
│     └─▶ Header: Authorization: Bearer ah_live_abc123...          │
│                                                                  │
│  3. Server validates                                             │
│     └─▶ Hash incoming key                                        │
│     └─▶ Look up hash in api_keys table                           │
│     └─▶ Check: is_active, expires_at, organization_id            │
│     └─▶ Check: key scopes vs. requested endpoint                 │
│                                                                  │
│  4. Rate limiting applied                                        │
│     └─▶ Per-key limit (e.g., 60 req/min)                         │
│     └─▶ Headers: X-RateLimit-Remaining, X-RateLimit-Reset        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Each API key has:

Queue System

Automation Hub uses Laravel's queue system with the database driver for reliable, persistent job processing without external dependencies like Redis.

Job ClassQueuePurposeRetry Policy
ProcessEventJobdefaultProcess incoming events and trigger workflows3 attempts, 30s backoff
DispatchWebhookJobwebhooksSend outbound webhook HTTP requests3 attempts, exponential backoff
ResumeWorkflowJobdefaultResume paused workflow executions after delay1 attempt (delay-based)
bash — Running Queue Workers
# Process all queues (recommended for production)
php artisan queue:work --queue=default,webhooks --tries=3 --backoff=30

# Process only webhooks (separate worker for isolation)
php artisan queue:work --queue=webhooks --tries=3 --backoff=10,30,60

# Monitor failed jobs
php artisan queue:failed

Queue Worker Required

Workflow executions will not process without a running queue worker. In production, use a process manager like Supervisor to keep the worker running. See the Getting Started guide for Supervisor configuration.

Project Folder Structure

Directory Tree
automation-hub/
├── app/
│   ├── Http/
│   │   ├── Controllers/
│   │   │   ├── WorkflowController.php
│   │   │   ├── ExecutionController.php
│   │   │   ├── ModuleController.php
│   │   │   ├── OrganizationController.php
│   │   │   └── Api/
│   │   │       └── V1/
│   │   │           ├── WorkflowApiController.php
│   │   │           ├── EventApiController.php
│   │   │           └── WebhookApiController.php
│   │   ├── Middleware/
│   │   │   ├── EnsureOrganizationAccess.php
│   │   │   ├── ApiKeyAuthentication.php
│   │   │   └── RateLimitByApiKey.php
│   │   └── Requests/
│   ├── Models/
│   │   ├── User.php
│   │   ├── Organization.php
│   │   ├── Workflow.php
│   │   ├── WorkflowExecution.php
│   │   ├── ExecutionStep.php
│   │   ├── Event.php
│   │   ├── Module.php
│   │   ├── ApiKey.php
│   │   └── Role.php
│   ├── Modules/
│   │   ├── Email/
│   │   │   ├── EmailModule.php
│   │   │   ├── Executors/EmailSendExecutor.php
│   │   │   └── Config/email-schema.json
│   │   ├── WhatsApp/
│   │   │   ├── WhatsAppModule.php
│   │   │   ├── Executors/
│   │   │   ├── Models/WhatsAppConversationModel.php
│   │   │   └── Config/whatsapp-schema.json
│   │   ├── AI/
│   │   │   ├── AIModule.php
│   │   │   ├── Executors/AIPromptExecutor.php
│   │   │   └── Config/ai-schema.json
│   │   ├── Notifications/
│   │   │   └── NotificationsModule.php
│   │   └── Courier/
│   │       ├── CourierModule.php
│   │       └── Executors/
│   ├── Scopes/
│   │   └── OrganizationScope.php
│   ├── Services/
│   │   ├── EventBus.php
│   │   ├── WorkflowEngine.php
│   │   └── ModuleSDK.php
│   └── Jobs/
│       ├── ProcessEventJob.php
│       ├── DispatchWebhookJob.php
│       └── ResumeWorkflowJob.php
├── resources/
│   └── js/
│       ├── Pages/
│       │   ├── Dashboard.tsx
│       │   ├── Workflows/
│       │   │   ├── Index.tsx
│       │   │   ├── Editor.tsx
│       │   │   └── Canvas/
│       │   │       ├── WorkflowCanvas.tsx
│       │   │       ├── nodes/
│       │   │       └── edges/
│       │   ├── Executions/
│       │   ├── Modules/
│       │   ├── Settings/
│       │   └── Organizations/
│       ├── Components/
│       │   ├── ui/          (shadcn/ui components)
│       │   ├── layout/
│       │   └── shared/
│       ├── hooks/
│       ├── lib/
│       └── i18n/
│           ├── en.json
│           ├── es.json
│           ├── fr.json
│           └── ar.json
├── database/
│   ├── migrations/
│   │   ├── create_organizations_table.php
│   │   ├── create_workflows_table.php
│   │   ├── create_workflow_executions_table.php
│   │   ├── create_execution_steps_table.php
│   │   ├── create_events_table.php
│   │   ├── create_modules_table.php
│   │   ├── create_api_keys_table.php
│   │   ├── create_roles_table.php
│   │   └── create_permissions_table.php
│   ├── seeders/
│   │   ├── TemplateSeeder.php
│   │   └── RolePermissionSeeder.php
│   └── factories/
├── routes/
│   ├── web.php
│   ├── api.php
│   └── channels.php
├── tests/
│   ├── Feature/
│   └── Unit/
└── config/
    └── automation-hub.php

Database Schema Overview

TablePurposeKey Columns
organizationsTenant containerid, name, slug, settings (JSON), plan
usersUser accountsid, name, email, current_organization_id, two_factor_enabled
organization_userUser-org membership pivotuser_id, organization_id, role_id
rolesNamed role definitionsid, name, organization_id, is_default
permissionsGranular permission listid, name, group
role_permissionRole-permission pivotrole_id, permission_id
workflowsWorkflow definitionsid, organization_id, name, definition (JSON), is_active, trigger_type
workflow_executionsExecution instancesid, workflow_id, status, trigger_data (JSON), started_at, completed_at
execution_stepsPer-node execution resultsid, execution_id, node_id, status, input (JSON), output (JSON), error
eventsEvent logid, organization_id, type, payload (JSON), source
modulesInstalled module registryid, identifier, name, version, is_active, config (JSON encrypted)
api_keysAPI authentication keysid, organization_id, name, key_hash, scopes (JSON), rate_limit, last_used_at
audit_logsActivity audit trailid, organization_id, user_id, action, subject_type, subject_id, metadata

Security Layers

Automation Hub implements defense-in-depth with multiple security layers:

LayerImplementationDetails
CSRF ProtectionLaravel CSRF middlewareAll state-changing requests require a valid CSRF token
Rate LimitingPer-route + per-API-keyConfigurable limits; 429 response with Retry-After header
SSRF ProtectionWebhook URL validationBlock private IPs (10.x, 172.16-31.x, 192.168.x, 127.x), DNS rebinding prevention
Credential EncryptionLaravel's encrypt()Module configs (API keys, passwords) encrypted at rest with APP_KEY
Audit LoggingModel observers + middlewareAll CRUD operations, logins, permission changes logged with actor and IP
Input ValidationForm Request classesEvery endpoint validates input; workflow definitions validated against JSON schema
SQL InjectionEloquent ORM + parameterized queriesNo raw SQL; all user input parameterized
XSS PreventionReact auto-escaping + CSP headersReact escapes by default; Content-Security-Policy headers restrict inline scripts
2FA / TOTPLaravel FortifyTime-based one-time passwords with recovery codes
CRLF InjectionEmail header sanitizationStrip \r\n from email headers to prevent header injection

Security Best Practice

Always keep your APP_KEY secure and backed up. This key is used to encrypt all module credentials (SMTP passwords, API keys, etc.). If lost, you will need to re-configure all modules.