Cross-App Workflows
Cross-App Workflows
Workflows connect your Web-Nothing apps so that events in one app automatically trigger actions in another. When something happens in a source app (e.g., a new order is created), a workflow can automatically create a record in a target app, send a webhook, or send an email -- no manual intervention needed.
How Workflows Work
A workflow has four parts:
- Trigger -- what event to watch for (e.g., a new record is created)
- Condition (optional) -- a filter that must be true for the workflow to fire (e.g., amount > $500)
- Field Mapping -- how to transform the source data for the target
- Action -- what to do (create a record, send a webhook, or send an email)
Workflows run automatically in the background whenever the trigger event occurs. They never slow down the original action.
Triggers
Every workflow watches for one trigger type on a source app:
| Trigger | Fires when |
|---|---|
| Record created | A new record is added to the source data table |
| Record updated | An existing record is modified |
| Record deleted | A record is removed |
| Webhook received | An external service sends data to the app's webhook |
| API response received | An API connector call completes and stores data |
| Time-based reminder | N minutes before or after a record's datetime field — see below |
Webhook and API response triggers fire in addition to the record-created trigger, so a single inbound event can match multiple workflows.
Time-based reminders
Use the time-based reminder trigger to fire a workflow N minutes before or after a record's datetime field. Common use cases:
- Booking reminders — email a customer 24 hours before their appointment so they actually show up.
- Follow-up surveys — send a feedback form 60 minutes after a session ends.
- Renewal warnings — heads-up email 7 days before a subscription auto-renews.
- Dunning campaigns — escalation email 3 days after an invoice goes unpaid.
How it works
When a record is created (or updated, with the datetime field changed), the platform queues a reminder for record[dateField] + offsetMinutes. A scheduled background worker fires due reminders.
You provide two extra fields when configuring the workflow:
| Field | Meaning | Example |
|---|---|---|
dateField | Name of the datetime field driving the schedule | "slot_datetime" |
offsetMinutes | Minutes from the datetime field. Negative = before, positive = after. | -1440 (24h before), 60 (1h after) |
Example — 24-hour booking reminder
{
"name": "Lesson Reminder",
"description": "Email tutor + student 24h before each lesson",
"sourceAppId": "...",
"sourceSchema": "appointments",
"trigger": "scheduled_offset_from_field",
"offsetMinutes": -1440,
"dateField": "slot_datetime",
"action": "send_email",
"fieldMapping": {
"_email_to": "{{source.student_email}}",
"_email_subject": "Reminder: lesson with {{source.tutor_name}} tomorrow",
"_email_body": "Hi {{source.student_name}}, reminder that your lesson is tomorrow at {{source.slot_datetime}}."
}
}Reminder timing precision (depends on plan)
The scheduled worker runs on a fixed cron interval set by the platform's hosting plan:
| Plan | Cron interval | Practical precision |
|---|---|---|
| Hobby | Daily (09:00 UTC) | ±24 hours |
| Pro | Every 5 minutes | ±5 minutes |
Choose offsets that match your plan's precision:
- 24-hour-before reminders work on both plans. The reminder fires on the morning of the target day.
- 1-hour-before reminders are only reliable on Pro. On Hobby, they may fire 0–23 hours early because the worker only runs once per day.
- For sub-hour precision, upgrade to Pro.
This is explicit degradation, not silent. Reminders always fire for the correct event, just possibly earlier than the exact offsetMinutes would suggest.
What happens when the source record changes
| Event | Behaviour |
|---|---|
dateField changed (e.g. customer reschedules) | Old pending reminder is cancelled; new one queued for the new fireAt |
Other fields changed (dateField unchanged) | Reminder unchanged — same fireAt |
| Source record deleted | All pending reminders for that record are cancelled |
| Past-due at creation (e.g. record's slot already happened) | Schedule is silently cancelled — no spam reminders for already-happened events |
| Cron is late (Vercel retry, server downtime) | Reminder still fires when the worker next runs — better late than never |
Limits
- Maximum pending reminders per app: 10,000. New schedules beyond the cap are rejected with a clear audit-trail entry.
- Per-workflow rate limit: scheduled fires share the workflow's
maxPerHourcap (default 10) with immediate fires. - Offset range:
offsetMinutesmust be between-525600(1 year before) and525600(1 year after).
Owner observability
Each workflow exposes a summary endpoint (GET /api/apps/[appId]/workflows/[workflowId]/scheduled-summary) returning:
{ "pending": 3, "lastFireAt": "2026-05-07T09:00:00.000Z" }pending= number of reminders queued and waiting to firelastFireAt= timestamp of the most recent successful fire (null if never fired)
Use this in your dashboards or admin UI to surface "Next 3 reminders queued · last fired 2 hours ago".
Conditions
A workflow can include an optional condition. If the condition evaluates to false, the workflow is skipped.
Condition structure
{
"field": "amount",
"operator": "gt",
"value": 500
}Available operators
| Operator | Meaning |
|---|---|
eq | Equal |
neq | Not equal |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
contains | Contains substring (case-insensitive) |
exists | Field has a value |
not_exists | Field is empty or missing |
Actions
| Action | What it does |
|---|---|
| Create record | Creates a new record in the target app's data table |
| Send webhook | POSTs the mapped data to an external URL (HTTPS required) |
| Send email | Sends an email using the platform's email service |
| Send SMS | Sends a text message via your configured SMS provider (e.g. Twilio) |
Send Email Details
The send email action sends transactional emails triggered by workflow events.
Required field mapping keys:
| Key | Required | Description |
|---|---|---|
_email_to | Yes | Recipient email address |
_email_subject | Yes | Email subject line |
_email_body | Yes | Email body (rendered as HTML and plain text) |
_email_reply_to | No | Reply-to address |
Example field mapping for email:
{
"_email_to": "{{source.customer_email}}",
"_email_subject": "Booking Confirmed: {{source.service_name}}",
"_email_body": "Hi {{source.customer_name}},\n\nYour booking for {{source.service_name}} on {{source.date}} at {{source.time}} is confirmed.\n\nThank you!"
}Email rate limits:
| Level | Limit | Window |
|---|---|---|
| Platform-wide | 80 emails | 24 hours |
| Per user | 50 emails | 24 hours |
| Per recipient | 5 emails | 24 hours |
Every email includes an automatic unsubscribe link. When a recipient unsubscribes, all future emails from that app to them are silently skipped.
Send SMS Details
The send SMS action sends transactional text messages triggered by workflow events. The platform itself does not provide SMS sending — instead, you connect your own SMS provider account (Twilio or a Twilio-compatible REST API) via an API connector, and the workflow dispatches messages on your behalf using your provider credentials.
Required field mapping keys:
| Key | Required | Description |
|---|---|---|
_sms_to | Yes | Recipient phone number in E.164 format (e.g. +14155551234) |
_sms_body | Yes | Text message body (max 1600 characters, roughly 10 SMS segments) |
_sms_from | Yes | Sender phone number from your SMS provider account |
_sms_connector_id | Yes | The id of the SMS API connector you configured (see below) |
Setting up the SMS connector (one-time):
- Sign up for a Twilio account (or any Twilio-compatible SMS provider) and obtain an Account SID, Auth Token, and a sending phone number.
- In the Web-Nothing API connectors page, create a new connector:
- Base URL:
https://api.twilio.com/2010-04-01/Accounts/<YourAccountSid> - Auth type: Basic Auth
- Username: your Account SID
- Password: your Auth Token
- Base URL:
- Save the connector and copy its id — you'll paste this into
_sms_connector_idon workflows.
Credentials are encrypted at rest with the same key-encryption-key used for all API connector secrets. The platform decrypts only at send time, builds the HTTP Basic auth header in memory, and never logs or returns the username or password.
Example field mapping for an appointment reminder SMS:
{
"_sms_to": "{{source.customer_phone}}",
"_sms_body": "Hi {{source.customer_name}}, this is a reminder of your appointment for {{source.service_name}} at {{source.appointment_at}}. Reply STOP to opt out.",
"_sms_from": "+15551234567",
"_sms_connector_id": "ck_abc123..."
}SMS rate limits:
| Level | Limit | Window |
|---|---|---|
| Platform-wide | 80 messages | 24 hours |
| Per user | 50 messages | 24 hours |
| Per recipient | 5 messages | 24 hours |
These are defense-in-depth caps. Your provider also enforces its own rate and cost limits — review them in your provider dashboard.
Regulatory note (customer responsibility):
Twilio (and most major SMS providers) automatically handle STOP / UNSUBSCRIBE replies at the carrier level — opt-out is enforced by your provider, not by Web-Nothing. If you operate in regulated markets (US TCPA, UK ICO, EU GDPR), review your provider's opt-out behavior and obtain prior consent before sending marketing SMS. Time-of-purchase and appointment-confirmation SMS to customers who explicitly opted in is generally permissible; broadcast marketing without consent is not. This is not legal advice — consult your own legal counsel.
Field Mapping
Field mapping defines how source record values are transformed for the target. Use {{source.fieldName}} to reference fields from the source record.
{
"customer_name": "{{source.name}}",
"order_total": "{{source.amount}}",
"source_info": "Order from {{source.store}} on {{source.date}}",
"priority": "high"
}Rules:
{{source.fieldName}}is replaced with the value from the source record. Missing fields resolve to empty string.- Multiple templates can appear in one value (e.g.,
"{{source.first}} {{source.last}}"). - Numeric values are preserved as numbers.
- Literal values (no templates) are passed through as-is.
Creating Workflows
With AI (Recommended)
The easiest way to create workflows is through the platform MCP endpoint. Describe what you want in natural language, and the AI generates the full workflow.
Example prompt:
"When an expense over $500 is created in my Finance app, create a high-priority task in my CRM"
The AI generates the complete workflow with the correct source/target apps, trigger, condition, and field mapping.
MCP tools for workflow management:
| Tool | Description |
|---|---|
workflows_create | Create a workflow from a natural language prompt |
workflows_list | List all your workflows with execution stats |
workflows_toggle | Enable or disable a workflow |
All workflow tools require a PLATFORM-scoped agent token.
Requirements
- You need at least 1 active app to create a workflow
- Create record actions require 2 apps (source + target)
- Send webhook and send email actions only require a source app
Chaining
Workflows can chain: when a workflow creates a record in App B, that creation can fire workflows watching App B, which may create records in App C, and so on.
App A (new record) → Workflow 1 → App B (create record)
→ Workflow 2 → App C (create record)
→ Workflow 3 → App D
(chain limit reached → stop)Safety limits:
- Default chain depth: 3 levels (configurable per workflow, up to 10)
- Hard safety cap: 10 levels maximum
- Send webhook and send email actions do not trigger downstream chains
Rate Limits
Each workflow is limited to 10 executions per hour by default. This prevents a single misconfigured workflow from flooding your apps with data. The limit resets every hour.
Example: Order to Audit Trail
Scenario: Every time a high-value order (over $1,000) is created in your Order Tracker app, automatically log it in your Audit Log app.
Workflow definition:
{
"name": "High-Value Order Audit",
"description": "Logs orders over $1000 to the audit trail",
"sourceAppId": "<order-tracker-id>",
"sourceSchema": "orders",
"trigger": "record_created",
"condition": {
"field": "total",
"operator": "gt",
"value": 1000
},
"action": "create_record",
"targetAppId": "<audit-log-id>",
"targetSchema": "entries",
"fieldMapping": {
"event_type": "high_value_order",
"description": "Order #{{source.order_number}} for ${{source.total}} by {{source.customer}}",
"severity": "warning",
"source_id": "{{source.id}}"
}
}What happens: When a user creates an order for $2,500, the workflow fires, the condition passes (2500 > 1000), the fields are mapped, and a new record appears in the Audit Log with the order details.
Live execution status
Cross-app workflows fire asynchronously — the source action commits and returns immediately while the target write happens in the background. To surface a "your order has been received → being prepared → ready" view to a customer, drop a workflow-status component on the page and bind it to the source record id.
The component polls every 2 seconds and shows one badge per execution: queued, processing, succeeded, failed, skipped, or retrying. Polling stops automatically once all executions reach a terminal state.
For public order-status pages, enable visibility on the app, then link customers to /?order_id=<id> so the component reads {{query.order_id}} to scope the status surface to their record only.
If the source record is committed but the workflow fails (e.g. the target app rejects the schema), the source data is kept — workflows are status-tracked, not transactional, across apps.
Related
| Topic | Document |
|---|---|
| Receiving data from external services | Webhooks |
| Connecting external APIs | API Connectors |
| Connecting AI agents | MCP Quick Start |
| Full MCP reference | MCP Integration Guide |
| Modifying apps via chat | Agent Chat Guide |
| Full user guide | User Guide |