High-scale, multi-tenant, embeddable workflow automation platform.
Loopty provides a visual DAG builder, secure node execution, and iframe-based embedding for integrating workflow automation into your SaaS product - similar to how Looker embeds dashboards.
🎵 The "Loop De Loop" Song - "You gotta do the loop de loop and pull, and your workflows are looking cool!"
- Visual Workflow Builder - n8n-style drag-and-drop DAG editor with React Flow
- 45+ Built-in Nodes - HTTP, code, AI, data transformation, CRM, communication channels, and more
- Customer Communication - Multi-channel messaging (SMS, WhatsApp, Email, Push, Voice, In-App)
- CRM Integrations - Native Salesforce, HubSpot, Pipedrive connectors
- Advanced Orchestration - Branching (IF/Switch), loops, merging, A/B testing, rate limiting
- Secure Execution - Sandboxed JS via isolated-vm, Docker containers for heavy workloads
- Multi-Tenant - Organization isolation, RBAC, encrypted credential vault
- Customer Management - Contacts, segments, event tracking, and analytics
- Developer Studio - Full-featured dashboard for managing workflows and executions
- Embeddable via Iframe - Signed embed URLs with white-label theming (like Looker)
- Real-Time Monitoring - Live execution logs via WebSocket
- Production Ready - PostgreSQL persistence, rate limiting, observability hooks
The Studio provides a developer-friendly interface for building and managing workflows:
- Workflow Editor - Visual canvas with node palette, drag-and-drop, and connection handles
- Node Configuration - Dynamic settings forms with documentation for each node type
- Execution History - View past runs with status, duration, and detailed logs
- Credential Management - Securely store API keys and secrets for use in workflows
- Real-time Updates - Live execution status and log streaming
Access Studio at /studio when running the API server.
- Node.js 20+
- pnpm 9+
- Docker and Docker Compose
- PostgreSQL 15+ (or use Docker)
- Redis 7+ (or use Docker)
# Clone the repository
git clone https://github.com/anonrose/loopty.git
cd loopty
# Install dependencies
pnpm install
# Create environment file (see Configuration section for all variables)
cat > .env << 'EOF'
DATABASE_URL=postgres://loopty:loopty@localhost:5432/loopty
REDIS_URL=redis://localhost:6379
JWT_SECRET=dev-jwt-secret-change-in-production
EMBED_SECRET=dev-embed-secret-change-in-production
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
PORT=4000
BASE_URL=http://localhost:4000
CORS_ORIGINS=*
NODE_ENV=development
EOF
# Start infrastructure (Postgres + Redis)
docker compose up -d postgres redis
# Run database migrations
pnpm db:migrate
# Seed development data
pnpm db:seed
# Seed development organization
psql $DATABASE_URL -c "INSERT INTO organizations (id, name, slug) VALUES ('00000000-0000-0000-0000-000000000001', 'Development Org', 'dev') ON CONFLICT DO NOTHING;"
# Build all packages
pnpm build
# Start development servers
pnpm devAccess the applications:
- Studio Dashboard: http://localhost:4000/studio
- API Health: http://localhost:4000/health
- Embed Preview: http://localhost:5174
For a complete environment reset (useful when troubleshooting):
# Run the start-env script (cleans and rebuilds everything)
./start-env.shThis script will:
- Stop and remove all Docker containers/volumes
- Prune Docker system
- Rebuild containers from scratch
- Run migrations and seed the database
For development with hot reloading in Docker:
# Start all services with hot reload
docker compose -f docker-compose.dev.yml up
# Services will auto-restart when you edit source files# Build and start all services
docker compose up -d
# Check health
curl http://localhost:4000/health
# {"status":"ok","version":"0.1.0"}
# View logs
docker compose logs -floopty/
├── apps/
│ ├── api/ # HTTP API + WebSocket streaming + embed serving
│ ├── embed/ # Embeddable workflow editor (served via iframe)
│ ├── studio/ # Developer dashboard for workflow management
│ └── worker/ # BullMQ job processor + node execution
├── packages/
│ ├── db/ # Drizzle ORM schema + migrations
│ ├── engine/ # Workflow execution engine
│ ├── sdk/ # Iframe embed SDK (JS + React wrapper)
│ ├── shared/ # Zod schemas + node definitions
│ └── workflow-ui/ # React Flow canvas + n8n-style node components
├── docs/ # Documentation
├── .github/ # CI/CD workflows + templates
├── docker-compose.yml # Production deployment
└── docker-compose.dev.yml # Hot-reload development
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/workflows |
List workflows |
| POST | /api/workflows |
Create workflow |
| GET | /api/workflows/:id |
Get workflow with versions |
| PATCH | /api/workflows/:id |
Update workflow |
| DELETE | /api/workflows/:id |
Delete workflow |
| POST | /api/workflows/:id/versions |
Create new version |
| POST | /api/workflows/:id/publish |
Publish a version |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/executions |
List executions |
| GET | /api/executions/:id |
Get execution details |
| POST | /api/executions/workflows/:id/run |
Run a workflow |
| POST | /api/executions/:id/resume |
Resume paused execution |
| POST | /api/executions/:id/cancel |
Cancel execution |
| POST | /api/executions/approve/:token |
Approve via token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Basic health check |
| GET | /health/db |
Database connectivity |
| GET | /health/redis |
Redis connectivity |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nodes/definitions |
Get all available node definitions with metadata |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/credentials |
List credentials (secrets never returned) |
| GET | /api/credentials/:id |
Get credential metadata |
| POST | /api/credentials |
Create credential (admin/owner only) |
| PATCH | /api/credentials/:id |
Update credential (admin/owner only) |
| DELETE | /api/credentials/:id |
Delete credential (admin/owner only) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/embed/url |
Generate signed embed URL for workflow |
| POST | /api/embed/execution-url |
Generate signed URL for execution viewer |
| GET | /api/embed/verify?token=... |
Verify embed token validity |
| GET | /embed/workflow/:id |
Serve embedded workflow editor |
| GET | /embed/execution/:id |
Serve embedded execution viewer |
Environment variables are automatically loaded from .env in the project root.
# Database (PostgreSQL connection string)
DATABASE_URL=postgres://loopty:loopty@localhost:5432/loopty
# Redis (for job queue)
REDIS_URL=redis://localhost:6379
# Authentication secrets (use strong random values in production!)
JWT_SECRET=your-secret-key-change-in-production # 32+ characters
EMBED_SECRET=your-embed-secret-change-in-production # 32+ characters
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 # 64 hex chars (32 bytes)# Server
PORT=4000 # API server port
BASE_URL=http://localhost:4000 # Public base URL for embed URLs
CORS_ORIGINS=* # Comma-separated allowed origins (or * for all)
NODE_ENV=development # development | production# Paths to built static assets (used in Docker/production)
EMBED_DIST_PATH=/app/apps/embed/dist
STUDIO_DIST_PATH=/app/apps/studio/dist
SDK_DIST_PATH=/app/packages/sdk/dist/index.umd.js# Worker settings
WORKER_CONCURRENCY=10 # Number of concurrent jobs per worker
JOB_TIMEOUT_MS=300000 # Job timeout (5 minutes default)# JavaScript sandbox limits (isolated-vm)
SANDBOX_TIMEOUT_MS=30000 # Execution timeout (30 seconds)
SANDBOX_MEMORY_MB=128 # Memory limit per execution# AI nodes (OpenAI)
OPENAI_API_KEY=sk-... # Required for AI chat, sentiment, classification nodes
# Monitoring (OpenTelemetry)
OTEL_ENABLED=true
OTEL_SERVICE_NAME=loopty
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
# Logging
LOG_LEVEL=info # debug | info | warn | error
LOG_FORMAT=json # json | pretty# Generate JWT/embed secret
openssl rand -base64 32
# Generate encryption key (32 bytes = 64 hex characters)
openssl rand -hex 32Loopty includes 45+ built-in nodes organized by category. Each node has comprehensive documentation accessible via the "Docs" tab in the Studio.
| Node | Description |
|---|---|
loopty.trigger.manual |
Start workflow manually or via API |
loopty.trigger.schedule |
Cron-based scheduled execution |
loopty.trigger.webhook |
HTTP webhook trigger with signature verification |
loopty.trigger.event |
Event-driven trigger from external systems |
loopty.trigger.form |
Form submission trigger |
| Node | Description |
|---|---|
loopty.logic.if |
Conditional branching (expression, compare, empty, regex) |
loopty.logic.switch |
Multi-way routing with rules or value matching |
loopty.logic.forEach |
Iterate over arrays with batch processing |
loopty.logic.splitBatches |
Split arrays into configurable chunk sizes |
loopty.logic.merge |
Combine multiple branches into one |
| Node | Description |
|---|---|
loopty.data.filter |
Filter arrays with expression or conditions |
loopty.data.sort |
Sort arrays by field (asc/desc) |
loopty.data.limit |
Take first/last N items from array |
loopty.data.aggregate |
Count, sum, average, min, max, group operations |
loopty.data.splitOut |
Extract single field from array items |
| Node | Description |
|---|---|
loopty.core.http |
Full HTTP client (all methods, auth, headers) |
loopty.core.code |
Execute JavaScript in isolated sandbox |
| Node | Description |
|---|---|
loopty.ai.chat |
OpenAI chat completions with function calling |
loopty.ai.sentiment |
Analyze sentiment of text (positive/negative/neutral) |
loopty.ai.classify |
Classify text into custom categories |
loopty.ai.translate |
Translate text between languages |
loopty.ai.summarize |
Summarize long text content |
loopty.ai.extractor |
AI-powered web scraping with Playwright |
| Node | Description |
|---|---|
loopty.communication.sms |
Send SMS messages via provider |
loopty.communication.whatsapp |
Send WhatsApp messages |
loopty.communication.voice |
Initiate voice calls with TTS |
loopty.communication.push |
Send push notifications |
loopty.communication.inApp |
Send in-app notifications |
| Node | Description |
|---|---|
loopty.crm.salesforce |
Salesforce CRM operations (CRUD, queries) |
loopty.crm.hubspot |
HubSpot CRM operations |
loopty.crm.pipedrive |
Pipedrive CRM operations |
loopty.crm.generic |
Generic CRM adapter for custom integrations |
| Node | Description |
|---|---|
loopty.customer.contact |
Create, update, lookup customer contacts |
loopty.customer.segment |
Evaluate and manage customer segments |
| Node | Description |
|---|---|
loopty.scheduling.delay |
Delay execution for a duration or until timestamp |
loopty.scheduling.scheduleSend |
Schedule message delivery for optimal times |
loopty.scheduling.rateLimiter |
Rate limit workflow throughput |
loopty.scheduling.abTest |
A/B test branching with traffic splits |
| Node | Description |
|---|---|
loopty.analytics.track |
Track events and properties for analytics |
| Node | Description |
|---|---|
loopty.template.render |
Render message templates with variables |
loopty.forms.validate |
Validate data against schemas |
| Node | Description |
|---|---|
loopty.approval.wait |
Pause workflow and wait for human approval |
| Node | Description |
|---|---|
loopty.utility.datetime |
Parse, format, add to dates |
loopty.utility.crypto |
Hash, encrypt, encode, generate UUIDs |
loopty.utility.htmlExtract |
Extract data from HTML with CSS selectors |
loopty.utility.noop |
Pass-through node for debugging |
| Node | Description |
|---|---|
loopty.flow.stopError |
Stop workflow with error message |
loopty.flow.tryCatch |
Wrap execution with error handling |
loopty.flow.throw |
Conditionally throw errors |
loopty.trigger.error |
Catch and handle workflow errors |
| Node | Description |
|---|---|
loopty.integration.slack |
Send Slack messages via webhook |
loopty.integration.email |
Send emails via SMTP |
loopty.integration.database |
Query PostgreSQL/MySQL databases |
Custom nodes can be added by implementing the INodeDefinition interface:
import { defineNode } from "@loopty/shared";
export const myNode = defineNode({
name: "mycompany.custom.node",
displayName: "My Custom Node",
category: "custom",
params: [
{ name: "input", type: "string", required: true }
],
async execute(input, context) {
return { status: "success", output: { result: input } };
}
});Loopty uses PostgreSQL with Drizzle ORM. Key tables include:
| Table | Description |
|---|---|
organizations |
Multi-tenant organization accounts |
users |
User accounts with email/password auth |
org_members |
Organization membership with roles (owner/admin/developer/viewer) |
api_keys |
API keys for programmatic access |
workflows |
Workflow definitions |
workflow_versions |
Versioned workflow snapshots (nodes + edges) |
executions |
Workflow execution records |
execution_logs |
Detailed execution logs per node |
credentials |
Encrypted credential storage |
approvals |
Human approval requests |
| Table | Description |
|---|---|
contacts |
Customer contact records (email, phone, attributes) |
templates |
Multi-channel message templates |
segments |
Dynamic customer segments with rules |
contact_events |
Customer event tracking |
scheduled_jobs |
Scheduled/delayed job queue |
Loopty uses iframe-based embedding with signed URLs - similar to Looker. This approach provides:
- Security - Signed tokens with expiration and permission scoping
- Isolation - Complete CSS/JS isolation from your app
- Simplicity - Works with any frontend framework (or none)
<!-- Option 1: Direct iframe -->
<iframe
src="https://api.loopty.dev/embed/workflow/abc123?token=eyJhbGciOi..."
width="100%"
height="600"
></iframe>// Option 2: JavaScript SDK
const embed = new LooptyEmbed({
apiUrl: "https://api.loopty.dev",
token: embedToken,
container: "#workflow-editor"
});
embed.on("save", ({ workflow }) => console.log("Saved:", workflow));
embed.on("runStarted", ({ executionId }) => console.log("Run:", executionId));// Option 3: React component
<LooptyIframe
apiUrl="https://api.loopty.dev"
token={embedToken}
onSave={({ workflow }) => console.log("Saved:", workflow)}
style={{ height: 600 }}
/>Generate signed embed URLs from your backend:
curl -X POST https://api.loopty.dev/api/embed/url \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"workflowId": "abc123-def456",
"permissions": ["view", "edit", "run"],
"expiresIn": "1h"
}'See SDK Documentation for full API reference.
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run with coverage
pnpm test:coverage# Type checking
pnpm typecheck
# Linting
pnpm lint
# Build all packages
pnpm build# Generate migration from schema changes
pnpm db:generate
# Apply migrations
pnpm db:migrate
# Push schema directly (development only)
pnpm db:push
# Open Drizzle Studio (database GUI)
pnpm db:studio
# Seed development data
pnpm db:seedSee Deployment Guide for detailed instructions on:
- Docker Compose (production)
- Kubernetes with Helm
- Environment configuration
- Security checklist
┌────────────────────────────────────────────────────────────────────┐
│ Host Application │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ <iframe> │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ Loopty Embed │ │ │
│ │ │ (Workflow Editor UI) │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ▲ postMessage ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Loopty SDK (Optional) │ │
│ └────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
│
┌─────▼─────┐
│ API │───────▶ ┌─────────────┐
│ (Express) │ │ PostgreSQL │
└─────┬─────┘ │ ───────── │
│ │ • Workflows│
│ │ • Contacts │
│ │ • Segments │
│ │ • Events │
│ └─────────────┘
┌─────▼─────┐
│ Redis │
│ (Queue) │
└─────┬─────┘
│
┌─────▼─────┐
│ Worker │
│ (BullMQ) │
└─────┬─────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│isolated-vm│ │ AI │ │ Comms │
│ (JS) │ │ (OpenAI) │ │ Channels │
└───────────┘ └───────────┘ └─────┬─────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ SMS/ │ │ CRM │ │ Push/ │
│ WhatsApp │ │Integration│ │ In-App │
└───────────┘ └───────────┘ └───────────┘
- All secrets encrypted at rest (AES-256-GCM)
- JWT + API key authentication
- Role-based access control (RBAC): Owner, Admin, Developer, Viewer
- Sandboxed code execution (isolated-vm with memory/time limits)
- HTTP egress allowlist
- See SECURITY.md for vulnerability reporting
We welcome contributions! Please read our Contributing Guide and Code of Conduct.
MIT - see LICENSE.
Built with ❤️ by the Loopty team.