Skip to main content
The flowDynamic method allows you to send messages programmatically during flow execution, enabling dynamic responses based on runtime data, API calls, or business logic.

Signature

flowDynamic(
  messages: string | string[] | FlowDynamicMessage[],
  options?: { delay?: number }
): Promise<void>

Parameters

messages
string | string[] | FlowDynamicMessage[]
required
The message(s) to send. Can be:
  • A single string
  • An array of strings
  • An array of message objects with media, buttons, and delay options
options
object
Configuration options for message sending.
options.delay
number
Delay in milliseconds before sending the message.

FlowDynamicMessage Type

type FlowDynamicMessage = {
  body?: string          // Message text
  media?: string         // URL or path to media file
  buttons?: Button[]     // Array of button objects
  delay?: number         // Delay before sending (ms)
}

Return Value

Returns a Promise<void> that resolves when all messages are sent.

Usage Examples

Basic Text Messages

Send simple dynamic text:
const greetingFlow = addKeyword('hello')
  .addAction(async (ctx, { flowDynamic }) => {
    const userName = await getUserName(ctx.from)
    await flowDynamic(`Welcome back, ${userName}!`)
  })

Multiple Messages

Send a sequence of messages:
const infoFlow = addKeyword('info')
  .addAction(async (ctx, { flowDynamic }) => {
    await flowDynamic([
      'Here is your information:',
      `Name: ${ctx.name}`,
      `Phone: ${ctx.from}`,
      'Thank you!'
    ])
  })

Messages with Media

Send images, videos, or files:
const catalogFlow = addKeyword('catalog')
  .addAction(async (ctx, { flowDynamic }) => {
    const products = await getProducts()
    
    const messages = products.map(product => ({
      body: `${product.name} - $${product.price}`,
      media: product.imageUrl
    }))
    
    await flowDynamic(messages)
  })

Messages with Delays

Add delays between messages for better UX:
const typingFlow = addKeyword('status')
  .addAction(async (ctx, { flowDynamic }) => {
    await flowDynamic([
      { body: 'Checking your order...', delay: 0 },
      { body: 'Processing...', delay: 1000 },
      { body: 'Your order is ready!', delay: 2000 }
    ])
  })

API Integration

Fetch and display data from external APIs:
const weatherFlow = addKeyword('weather')
  .addAnswer(
    'Which city?',
    { capture: true },
    async (ctx, { flowDynamic }) => {
      const city = ctx.body
      const weather = await fetchWeather(city)
      
      await flowDynamic([
        `Weather in ${city}:`,
        `Temperature: ${weather.temp}°C`,
        `Conditions: ${weather.description}`,
        { 
          body: 'Current conditions',
          media: weather.iconUrl 
        }
      ])
    }
  )

Database Queries

Display data from database:
const ordersFlow = addKeyword('my orders')
  .addAction(async (ctx, { flowDynamic }) => {
    const orders = await getOrdersByPhone(ctx.from)
    
    if (orders.length === 0) {
      await flowDynamic('You have no orders yet.')
      return
    }
    
    const messages = orders.map((order, index) => 
      `${index + 1}. Order #${order.id} - $${order.total} - ${order.status}`
    )
    
    await flowDynamic(['Your orders:', ...messages])
  })

Conditional Messages

Send different messages based on conditions:
const balanceFlow = addKeyword('balance')
  .addAction(async (ctx, { flowDynamic }) => {
    const balance = await getBalance(ctx.from)
    
    if (balance < 0) {
      await flowDynamic([
        `Your balance is $${balance}`,
        '⚠️ Your account is overdrawn',
        'Please make a payment soon'
      ])
    } else if (balance < 100) {
      await flowDynamic([
        `Your balance is $${balance}`,
        '💡 Your balance is running low'
      ])
    } else {
      await flowDynamic(`Your balance is $${balance}`)
    }
  })

Personalized Messages

Combine with state for personalization:
const recommendFlow = addKeyword('recommend')
  .addAction(async (ctx, { state, flowDynamic }) => {
    const userPrefs = state.getMyState()
    const recommendations = await getRecommendations(userPrefs)
    
    await flowDynamic('Based on your preferences:')
    
    for (const item of recommendations) {
      await flowDynamic({
        body: `${item.name} - ${item.description}`,
        media: item.image,
        delay: 500
      })
    }
  })

Progress Updates

Show progress during long operations:
const processFlow = addKeyword('process')
  .addAction(async (ctx, { flowDynamic }) => {
    await flowDynamic('Starting process...')
    
    await step1()
    await flowDynamic('✅ Step 1 complete')
    
    await step2()
    await flowDynamic('✅ Step 2 complete')
    
    await step3()
    await flowDynamic('✅ All steps completed!')
  })

Error Handling

Handle errors gracefully:
const searchFlow = addKeyword('search')
  .addAnswer(
    'What are you looking for?',
    { capture: true },
    async (ctx, { flowDynamic }) => {
      try {
        const results = await searchProducts(ctx.body)
        
        if (results.length === 0) {
          await flowDynamic('No results found. Try different keywords.')
          return
        }
        
        await flowDynamic(`Found ${results.length} results:`)
        await flowDynamic(results.map(r => r.name))
        
      } catch (error) {
        await flowDynamic('Sorry, search is temporarily unavailable.')
      }
    }
  )

Combining with Other Methods

Use with gotoFlow for advanced patterns:
const checkoutFlow = addKeyword('checkout')
  .addAction(async (ctx, { flowDynamic, gotoFlow, state }) => {
    const cart = state.get('cart') || []
    
    if (cart.length === 0) {
      await flowDynamic('Your cart is empty')
      return gotoFlow(shopFlow)
    }
    
    const total = cart.reduce((sum, item) => sum + item.price, 0)
    
    await flowDynamic([
      'Cart Summary:',
      ...cart.map(item => `- ${item.name}: $${item.price}`),
      `Total: $${total}`
    ])
    
    return gotoFlow(paymentFlow)
  })

Important Notes

Message Limits: Sending too many messages rapidly may trigger rate limits or spam filters. Use delays between messages.
  • flowDynamic is asynchronous - always use await
  • Messages are sent in the order they appear in the array
  • Media URLs must be publicly accessible or local file paths
  • The method supports up to 5 messages per call (recommended)
  • Delays are cumulative when using message objects

Message Formatting Tips

Text Formatting

await flowDynamic([
  '*Bold text*',
  '_Italic text_',
  '~Strikethrough~',
  '```Monospace```'
])

Lists and Structure

await flowDynamic([
  'Menu Options:',
  '1️⃣ First option',
  '2️⃣ Second option',
  '3️⃣ Third option',
  '',
  'Reply with the number to select'
])

Common Patterns

Pagination

const listFlow = addKeyword('list')
  .addAction(async (ctx, { flowDynamic, state }) => {
    const items = await getAllItems()
    const page = state.get('page') || 0
    const pageSize = 5
    
    const pageItems = items.slice(page * pageSize, (page + 1) * pageSize)
    
    await flowDynamic([
      `Page ${page + 1} of ${Math.ceil(items.length / pageSize)}:`,
      ...pageItems.map((item, i) => `${i + 1}. ${item.name}`)
    ])
  })

Template Messages

const notifyFlow = addKeyword('notify')
  .addAction(async (ctx, { flowDynamic }) => {
    const template = {
      header: '🔔 Notification',
      body: 'You have a new message',
      footer: 'Reply STOP to unsubscribe'
    }
    
    await flowDynamic([
      template.header,
      template.body,
      '',
      template.footer
    ])
  })

Performance Considerations

  • Batch operations: Prepare all data before calling flowDynamic
  • Use delays wisely: Don’t make users wait unnecessarily
  • Limit message count: Send 3-5 messages max per flowDynamic call
  • Cache external data: Avoid repeated API calls
  • gotoFlow - Navigate to a different flow
  • endFlow - End the conversation flow
  • fallBack - Repeat the previous message

Build docs developers (and LLMs) love