Agent Skills: Wraps Workflow DSL

Build email and SMS automation workflows using the @wraps.dev/client TypeScript DSL. Covers triggers, steps, branching, cascades, and the full defineWorkflow API.

UncategorizedID: wraps-team/skills/wraps-workflows

Install this agent skill to your local

pnpm dlx add-skill https://github.com/wraps-team/skills/tree/HEAD/skills/wraps-workflows

Skill Files

Browse the full folder contents for wraps-workflows.

Download Skill

Loading file tree…

skills/wraps-workflows/SKILL.md

Skill Metadata

Name
wraps-workflows
Description
Build email and SMS automation workflows using the @wraps.dev/client TypeScript DSL. Covers triggers, steps, branching, cascades, and the full defineWorkflow API.

Wraps Workflow DSL

Build email and SMS automation workflows as TypeScript files using @wraps.dev/client. Workflows live in wraps/workflows/*.ts and are pushed to the Wraps dashboard with wraps email workflows push.

Installation

npm install @wraps.dev/client
# or
pnpm add @wraps.dev/client

Quick Start

import { defineWorkflow, sendEmail, delay, condition, exit } from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Welcome Sequence',
  trigger: { type: 'contact_created' },
  steps: [
    sendEmail('send-welcome', { template: 'welcome-email' }),
    delay('wait-1-day', { days: 1 }),
    condition('check-activated', {
      field: 'contact.hasActivated',
      operator: 'equals',
      value: true,
      branches: {
        yes: [exit('already-active')],
        no: [sendEmail('send-tips', { template: 'getting-started-tips' })],
      },
    }),
  ],
});

Workflow Structure

Every workflow file must:

  1. Use export default defineWorkflow({ ... })
  2. Import only what it uses from @wraps.dev/client
  3. Have exactly one trigger
  4. Have a steps array of step helper calls
import { defineWorkflow, sendEmail, delay } from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Workflow Name',
  description: 'Optional description',
  trigger: { type: 'contact_created' },
  steps: [
    sendEmail('welcome', { template: 'welcome' }),
    delay('wait', { days: 1 }),
  ],

  // Optional settings
  settings: {
    allowReentry: false,
    reentryDelaySeconds: 86400,
    maxConcurrentExecutions: 1000,
    contactCooldownSeconds: 3600,
  },

  // Optional default sender overrides
  defaults: {
    from: 'hello@example.com',
    fromName: 'My App',
    replyTo: 'support@example.com',
  },
});

Trigger Types

// New contact created
trigger: { type: 'contact_created' }

// Contact updated
trigger: { type: 'contact_updated' }

// Custom event
trigger: { type: 'event', eventName: 'cart.abandoned' }

// Contact enters a segment
trigger: { type: 'segment_entry', segmentId: 'segment-id' }

// Contact exits a segment
trigger: { type: 'segment_exit', segmentId: 'segment-id' }

// Topic subscription
trigger: { type: 'topic_subscribed', topicId: 'topic-id' }

// Topic unsubscription
trigger: { type: 'topic_unsubscribed', topicId: 'topic-id' }

// Cron schedule
trigger: { type: 'schedule', schedule: '0 9 * * 1', timezone: 'America/New_York' }

// Manual / API trigger
trigger: { type: 'api' }

Step Helpers

sendEmail

Send an email using a template slug.

sendEmail('step-id', { template: 'welcome-email' })

// With overrides
sendEmail('promo', {
  template: 'promo-email',
  from: 'marketing@example.com',
  fromName: 'Marketing Team',
  replyTo: 'replies@example.com',
})

sendSms

Send an SMS using a template slug or inline message.

// Template
sendSms('reminder', { template: 'appointment-reminder' })

// Inline message
sendSms('otp', { message: 'Your code is {{otp}}' })

// With sender ID
sendSms('alert', { template: 'alert-sms', senderId: '+15551234567' })

delay

Wait for a duration before continuing.

delay('wait-1-day', { days: 1 })
delay('wait-2-hours', { hours: 2 })
delay('wait-30-min', { minutes: 30 })

condition

Branch based on a contact field or event data.

condition('check-plan', {
  field: 'contact.plan',
  operator: 'equals',
  value: 'premium',
  branches: {
    yes: [sendEmail('premium-offer', { template: 'premium-perks' })],
    no: [sendEmail('upgrade-offer', { template: 'upgrade-cta' })],
  },
})

Operators: equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, greater_than_or_equals, less_than_or_equals, is_set, is_not_set, is_true, is_false

waitForEvent

Pause until a specific event occurs, with an optional timeout.

waitForEvent('wait-for-purchase', {
  eventName: 'purchase_completed',
  timeout: { days: 7 },
})

waitForEmailEngagement

Wait for engagement with a previous email step.

waitForEmailEngagement('wait-for-open', {
  emailStepId: 'welcome',       // ID of the sendEmail step to track
  engagementType: 'opened',     // 'opened' or 'clicked'
  timeout: { days: 3 },
})

exit

End the workflow.

exit('completed')
exit('cancelled', { reason: 'User unsubscribed', markAs: 'cancelled' })

markAs options: completed (default), cancelled, failed

updateContact

Modify contact fields.

updateContact('mark-welcomed', {
  updates: [
    { field: 'welcomeEmailSent', operation: 'set', value: true },
    { field: 'emailCount', operation: 'increment', value: 1 },
  ],
})

Operations: set, increment, decrement, append, remove

subscribeTopic / unsubscribeTopic

Manage topic subscriptions.

subscribeTopic('subscribe-newsletter', {
  topicId: 'newsletter',
  channel: 'email',    // 'email' or 'sms'
})

unsubscribeTopic('unsubscribe-promo', {
  topicId: 'promotions',
  channel: 'email',
})

webhook

Call an external URL.

webhook('notify-slack', {
  url: 'https://hooks.slack.com/services/...',
  method: 'POST',
  headers: { 'X-Custom': 'value' },
  body: { text: 'New user signed up!' },
})

cascade

Cross-channel cascade: try channels in order, stop when engagement is detected. Returns an array — use spread syntax.

...cascade('notify-user', {
  channels: [
    {
      type: 'email',
      template: 'welcome-email',
      waitFor: { hours: 2 },
      engagement: 'opened',
    },
    {
      type: 'sms',
      template: 'welcome-sms',
    },
  ],
})

Expands to: sendEmail -> waitForEmailEngagement -> condition (engaged?) -> yes: exit -> no: sendSms

Common Patterns

Welcome Sequence

import { defineWorkflow, sendEmail, delay, condition, exit } from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Welcome Sequence',
  trigger: { type: 'contact_created' },
  steps: [
    sendEmail('send-welcome', { template: 'welcome-email' }),
    delay('wait-1-day', { days: 1 }),
    condition('check-activated', {
      field: 'contact.hasActivated',
      operator: 'equals',
      value: true,
      branches: {
        yes: [exit('already-active')],
        no: [
          sendEmail('send-tips', { template: 'getting-started-tips' }),
        ],
      },
    }),
  ],
});

Cart Recovery with Cascade

import { defineWorkflow, delay, cascade, exit } from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Cart Recovery',
  trigger: { type: 'event', eventName: 'cart.abandoned' },
  steps: [
    delay('initial-wait', { minutes: 30 }),
    ...cascade('recover-cart', {
      channels: [
        { type: 'email', template: 'cart-recovery', waitFor: { hours: 2 }, engagement: 'opened' },
        { type: 'sms', template: 'cart-sms-reminder' },
      ],
    }),
    exit('cascade-complete'),
  ],
});

Re-engagement with Event Wait

import {
  defineWorkflow, sendEmail, waitForEvent, condition, delay, exit,
} from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Re-engagement',
  trigger: { type: 'event', eventName: 'contact.inactive' },
  steps: [
    sendEmail('send-win-back', { template: 'we-miss-you' }),
    waitForEvent('wait-for-activity', {
      eventName: 'contact.active',
      timeout: { days: 7 },
    }),
    condition('check-returned', {
      field: 'contact.lastActiveAt',
      operator: 'is_set',
      value: true,
      branches: {
        yes: [exit('re-engaged')],
        no: [
          sendEmail('final-offer', { template: 'last-chance-offer' }),
          delay('wait-before-cleanup', { days: 3 }),
          exit('not-engaged'),
        ],
      },
    }),
  ],
});

Rules

  1. Always use export default defineWorkflow({ ... })
  2. Import only what you use from @wraps.dev/client
  3. Step IDs must be kebab-case and unique (e.g., 'send-welcome', 'wait-1-day')
  4. Template slugs should be descriptive kebab-case (e.g., 'welcome-email', 'cart-recovery')
  5. Use the ...cascade() spread pattern when using cascade
  6. Add a JSDoc comment at the top describing the workflow purpose
  7. Keep workflows focused — typically 3-10 steps, one clear goal per workflow
  8. Use meaningful, human-readable names for the workflow and steps
  9. Include appropriate delays between messages (don't spam — wait at least hours or days)
  10. Add conditions to personalize flow based on user behavior
  11. End branches with exit nodes when appropriate

Common Event Names

These are conventions — users can define any custom event name:

  • user.signup — New user registration
  • order.completed — Purchase completed
  • order.created — Order placed
  • cart.abandoned — Cart abandoned
  • subscription.started — Subscription began
  • subscription.cancelled — Subscription cancelled
  • trial.ending — Trial expiring soon
  • contact.inactive — Contact went inactive
  • form.submitted — Form submission

CLI Commands

# Generate from a built-in template
wraps email workflows generate --template welcome

# Available templates: welcome, cart-recovery, trial-conversion, re-engagement, onboarding

# Validate a workflow
wraps email workflows validate --workflow welcome

# Push all workflows to dashboard
wraps email workflows push

File Structure

Workflow files live in wraps/workflows/ at the project root:

my-project/
├── wraps/
│   └── workflows/
│       ├── welcome.ts
│       ├── cart-recovery.ts
│       └── trial-conversion.ts
├── src/
└── package.json