guides/sending-messages.md

# Sending Messages

This guide covers the ExGram DSL for building and sending responses to your users.

## Understanding the DSL Philosophy

The ExGram DSL uses a **builder pattern**. DSL functions **build up** a list of actions on the context object. You **must return the context** from your handler, and ExGram will **execute all actions in order**.

### How It Works

```elixir
def handle({:command, "start", _msg}, context) do
  context
  |> answer("Welcome!")           # Action 1: queued
  |> answer("Here's a menu:")     # Action 2: queued
  |> answer_photo(photo_id)       # Action 3: queued
end
# After handler returns, ExGram executes: action 1 → action 2 → action 3
```

**Key points:**
- DSL functions **build** actions, they don't execute immediately
- You **must return** the context for actions to execute
- Actions execute **in order** after your handler completes
- This is perfect for common/basic bot logic

### Wrong patterns

#### Not carrying context updates

This won't work as expected, Elixir it's immutable, so the updated context need to be passed to the next actions all the way to the end.

```elixir
def handle({:command, "start", _msg}, context) do
  answer(context, "Welcome!") # ❌ This will never be sent!!
  answer(context, "Here's a menu:") # ❌ This will never be sent!!
  answer_photo(context, photo_id)
end
```

#### Doing actions, and then other things

There are two common got-chas.

The first one is, queueing actions, but not returning the context, this will make the actions to not be executed at all.

```elixir
def handle({:command, "start", msg}, context) do
  answer(context, "Welcome!") # ❌ This will never be sent!!

  MyBot.update_user_stats(extract_user(msg))
end

# Correct:
def handle({:command, "start", msg}, context) do
  MyBot.update_user_stats(extract_user(msg))

  answer(context, "Welcome!")
end
```

The second common mistake is the order if you mix DSL and non DSL:

```elixir
def handle({:command, "start", msg}, context) do
  context = answer(context, "Welcome!")

  # ❌ This will be sent BEFORE the "Welcome!" message, because the DSL actions are enqueued and executed AFTER the handle/2 method
  ExGram.send_photo(extract_chat_id(msg), photo_id, bot: context.name) 
  
  context
end

# Correct:
def handle({:command, "start", msg}, context) do
  chat_id = extract_id(msg) 
  # Using on_result allow you to do actions after the previous action
  context 
  |> answer("Welcome!")
  |> on_result(fn 
    {:ok, _}, name -> 
      ExGram.send_photo(chat_id, photo_id, bot: name)
    error, _name -> 
      error
  end)
end
```

### When NOT to Use the DSL

The DSL is really powerful and helps to make the bot's logic easier to follow, but there are cases where you will need to use the [Low-Level API](./low-level-api.md), for example:

- There are still no DSL action for the method you want. The DSL has been created as needed, so many methods still don't have a DSL created. Feel free to open an issue or a pull request 😄
- For complex bots with **background tasks**, **scheduled jobs**, or operations outside of handlers, in this cases you can't use the DSL at all.

```elixir
# In a background task or GenServer
def send_notification(user_id) do
  # Use Low-Level API directly
  ExGram.send_message(user_id, "Scheduled notification!", bot: :my_bot)
end
```

Read more about the Low-Level API in [this guide](./low-level-api.md)

## Sending Text Messages

### `answer/2-4`

Send a text message to the current chat.

```elixir
# Simple text
def handle({:command, "hello", _}, context) do
  answer(context, "Hello there!")
end

# With options
def handle({:command, "secret", _}, context) do
  answer(context, "🤫 Secret message", parse_mode: "Markdown", disable_notification: true)
end

# Multi-line
def handle({:command, "help", _}, context) do
  answer(context, """
  Available commands:
  /start - Start the bot
  /help - Show this help
  /settings - Configure settings
  """)
end
```

**Options:** All the `ExGram.send_message/3` options, you can see them in [the documentation](https://hexdocs.pm/ex_gram/ExGram.html#send_message/3)

### Multiple Messages

Chain multiple `answer` calls to send several messages:

```elixir
def handle({:command, "story", _}, context) do
  context
  |> answer("Once upon a time...")
  |> answer("There was a bot...")
  |> answer("The end!")
end
```

## Sending Media

All the fields that are files, will support three ways of sending that file:

- String: This is a file_id previously received in Telegram responses or messages.
- `{:file, "/path/to/file"}`: This will read the file and send it 
- `{:file_content, "content", "filename.jpg"}`: Will send the "content" directly. It can be a `String.t`, `iodata()` or a `Enum.t()`, useful for streaming data directly without loading everything in memory.

For now only `answer_document` has a DSL method, we'll add more DSL for sending media files

```elixir
# Documents
answer_document(context, {:file, "/path/to/document.txt"}, opts \\ [])
```

## Keyboards

Create interactive buttons that users can press.

### Inline Keyboard

There is a neat DSL to create keyboards!

```elixir
import ExGram.Dsl.Keyboard # It is not added by default, you have to import it

def handle({:command, "choose", _}, context) do
  markup = 
    keyboard :inline do
      row do
        inline_button "Option A", callback_data: "option_a"
        inline_button "Option B", callback_data: "option_b"
      end
      
      row do
        inline_button "Cancel", callback_data: "cancel"
      end
    end
  
  answer(context, "Choose an option:", reply_markup: markup)
end
```

The `inline_button` accepts all the options that the `ExGram.Model.InlineKeyboardButton` accepts, for example:

```elixir
inline_button "Visit website", url: "https://example.com", style: "success"
```

### Reply keyboards

These are the keyboards that pop up at the bottom of the screen. You can also create them with the DSL.

```elixir
keyboard :reply do
  row do
    reply_button "Help", style: "success"
    reply_button "Send my location", request_location: true, style: "danger"
  end
end
```

This keyboards accept more options too, check [the documentation](https://hexdocs.pm/ex_gram/ExGram.Model.ReplyKeyboardMarkup.html) for available options:

```elixir
keyboard :reply, [is_persistent: true, one_time_keyboard: true, resize_keyboard: true] do
  row do
    reply_button "Help", style: "success"
  end
end
```

### Dynamic building

This keyboards might look static, but you can actually do things like this to dynamically build your keyboards:

```elixir
keyboard :inline do
    # Returning rows will make each element it's own row
    Enum.map(1..3, fn index ->
        row do
            button to_string(index), callback_data: "index:#{index}"
        end
    end)

    # Returning buttons will make all the elements be the same row
    Enum.map(4..6, fn index -> 
        # Without a row block, buttons are placed in a single row
        button to_string(index), callback_data: "index:#{index}"
    end)
    
    # Optional rows and buttons
    if some_thing?() do
        row do
           button "Some thing happens", url: "https://something.com"
        end
    end
end
```

### Inspecting Keyboards

Keyboard models implement the `Inspect` protocol, so when you inspect them in IEx or logs, you see a visual layout instead of a wall of struct fields:

```elixir
iex> markup = keyboard :inline do
...>   row do
...>     button "1", callback_data: "one"
...>     button "2", callback_data: "two"
...>     button "3", url: "https://example.com"
...>   end
...>   row do
...>     button "Back", callback_data: "back"
...>     button "Next", callback_data: "next"
...>   end
...> end
#InlineKeyboardMarkup<
  [ 1 (cb) ][ 2 (cb) ][ 3 (url) ]
  [ Back (cb) ][ Next (cb) ]
>
```

Each button shows its action type in parentheses: `cb` for `callback_data`, `url` for `url`, `web_app`, `pay`, etc.

To see the actual action values, pass `verbose: true` via `custom_options`:

```elixir
iex> inspect(markup, custom_options: [verbose: true])
#InlineKeyboardMarkup<
  [ 1 (cb: "one") ][ 2 (cb: "two") ][ 3 (url: "https://example.com") ]
  [ Back (cb: "back") ][ Next (cb: "next") ]
>
```

Reply keyboards show their options at the top:

```elixir
iex> keyboard :reply, [resize_keyboard: true, one_time_keyboard: true] do
...>   row do
...>     reply_button "Help"
...>     reply_button "Settings"
...>   end
...> end
#ReplyKeyboardMarkup<resize: true, one_time: true,
  [ Help ][ Settings ]
>
```

Individual buttons also have compact inspect output, showing only non-nil fields:

```elixir
iex> %ExGram.Model.InlineKeyboardButton{text: "OK", callback_data: "ok"}
#InlineKeyboardButton<"OK" callback_data: "ok">

iex> %ExGram.Model.KeyboardButton{text: "Share Location", request_location: true}
#KeyboardButton<"Share Location" request_location: true>
```

## Callback Queries

### `answer_callback/2-3`

Always respond to callback queries to remove the loading indicator:

```elixir
# Simple acknowledgment
def handle({:callback_query, %{data: "click"}}, context) do
  answer_callback(context, "Button clicked!")
end

# Show alert (popup)
def handle({:callback_query, %{data: "alert"}}, context) do
  answer_callback(context, "This is an alert!", show_alert: true)
end

# Silent acknowledgment
def handle({:callback_query, _}, context) do
  answer_callback(context)
end
```

## Inline Queries

### `answer_inline_query/2-3`

Respond to inline queries (`@yourbot search term`):

```elixir
def handle({:inline_query, %{query: query}}, context) do
  results = search_results(query)
  |> Enum.map(fn result ->
    %{
      type: "article",
      id: result.id,
      title: result.title,
      description: result.description,
      input_message_content: %{
        message_text: result.content
      }
    }
  end)
  
  answer_inline_query(context, results, cache_time: 300)
end
```

See [Telegram InlineQueryResult docs](https://core.telegram.org/bots/api#inlinequeryresult) for result types.

## Editing Messages

### `edit/2-4`

Edit a previous message:

```elixir
# In callback query handler - edits the message with the button
def handle({:callback_query, %{data: "refresh"}}, context) do
  context
  |> answer_callback("Refreshing...")
  |> edit("Updated content at #{DateTime.utc_now()}")
end

# Edit with new markup
def handle({:callback_query, %{data: "next_page"}}, context) do
  new_markup = create_inline([[%{text: "Back", callback_data: "prev_page"}]])
  
  context
  |> answer_callback()
  |> edit("Page 2", reply_markup: new_markup)
end
```

### `edit_inline/2-4`

Edit inline query result messages:

```elixir
edit_inline(context, "Updated inline result")
```

### `edit_markup/2`

Update only the inline keyboard:

```elixir
def handle({:callback_query, %{data: "toggle"}}, context) do
  new_markup = keyboard do
    row do
      button "Toggled!", callback_data: "toggle"
    end
  end
  
  context
  |> answer_callback()
  |> edit_markup(new_markup)
end
```

## Deleting Messages

### `delete/1-3`

Delete messages:

```elixir
# Delete the message that triggered the update
def handle({:callback_query, %{data: "delete"}}, context) do
  context
  |> answer_callback("Deleting...")
  |> delete()
end

# Delete specific message
def handle({:command, "cleanup", _}, context) do
  chat_id = extract_id(context)
  message_id = "some_message_id"
  msg = %{chat_id: chat_id, message_id: message_id}
  delete(context, msg)
end
```

## Chaining Results with `on_result/2`

Tap into the execution chain and do something with the result of the previous action.

The callback receives two parameters:
- result: `{:ok, x} | {:error, error}`
- name: The bot's name

```elixir
def handle({:command, "pin", _}, context) do
  context
  |> answer("Important announcement!")
  |> on_result(fn 
    {:ok, %{message_id: msg_id}}, name ->
      # Pin the message we just sent
      ExGram.pin_chat_message(extract_id(context), msg_id, bot: name)
      
    error, _name -> 
      error
  end)
end

def handle({:command, "forward_to_admin", _}, context) do
  admin_chat_id = Application.get_env(:my_app, :admin_chat_id)
  
  context
  |> answer("Message sent to admin!")
  |> on_result(fn 
    {:ok, message}, name ->
      # Forward the confirmation to admin
      ExGram.forward_message(admin_chat_id, extract_id(message), extract_message_id(message), bot: name)
      
    error, _name -> 
      error
  end)
end
```

**Note:** `on_result/2` receives the result of the previous action. What you return will be treated as the new result of that action. 

## Context Helper Functions

ExGram provides helper functions to extract information from the context:

### `extract_id/1`

Get the origin id from the update, if it's a chat, will be the chat id, if it's a private conversation will be the user id.

Used to know who to answer.

```elixir
chat_id = extract_id(context)
```

### `extract_user/1`

Get the user who triggered the update:

```elixir
%{id: user_id, username: username} = extract_user(context)
```

### `extract_chat/1`

Get the chat where the update occurred:

```elixir
chat = extract_chat(context)
```

### `extract_message_id/1`

Get the message ID:

```elixir
message_id = extract_message_id(context)
```

### `extract_callback_id/1`

Get callback query ID

```elixir
callback_id = extract_callback_id(context)
```

### `extract_update_type/1`

Get the update type:

```elixir
case extract_update_type(update) do
  :message -> # ...
  :callback_query -> # ...
  :inline_query -> # ...
end
```

### `extract_message_type/1`

Get the message type:

```elixir
case extract_message_type(message) do
  :text -> # ...
  :photo -> # ...
  :document -> # ...
end
```

### Other Helpers

```elixir
extract_response_id(context)      # Get response ID for editing
extract_inline_id_params(context) # Get inline message params
```

## Complete Example

Here's a bot that demonstrates multiple DSL features:

```elixir
defmodule MyBot.Bot do
  use ExGram.Bot, name: :my_bot, setup_commands: true
  
  import ExGram.Dsl.Keyboard

  command("start", description: "Start")
  command("menu", description: "Show menu")
  command("info", description: "Information")

  def handle({:command, :start, _}, context) do
    user = extract_user(context)
    
    context
    |> answer("Welcome, #{user.first_name}!")
    |> answer("I'm here to help you. Use /menu to see options.")
  end

  def handle({:command, :menu, _}, context) do
    markup = keyboard :inline do
      row do
        button "📊 Stats", callback_data: "stats"
        button "⚙️ Settings", callback_data: "settings"
      end
      
      row do
        button "ℹ️ Info", callback_data: "info"
        button "❌ Close", callback_data: "close"
      end
    end
    
    answer(context, "Main Menu:", reply_markup: markup)
  end

  def handle({:callback_query, %{data: "stats"}}, context) do
    user = extract_user(context)
    stats = get_user_stats(user.id)
    
    context
    |> answer_callback()
    |> edit("📊 Your Stats:\n\nMessages: #{stats.messages}\nCommands: #{stats.commands}")
  end

  def handle({:callback_query, %{data: "close"}}, context) do
    context
    |> answer_callback("Closing menu")
    |> delete()
  end

  defp get_user_stats(user_id) do
    # Fetch from database
    %{messages: 42, commands: 15}
  end
end
```

## Next Steps

- [Message Entities](message-entities.md) - Format messages without Markdown or HTML
- [Middlewares](middlewares.md) - Add preprocessing logic
- [Low-Level API](low-level-api.md) - Direct API calls for complex scenarios
- [Cheatsheet](cheatsheet.md) - Quick reference for all DSL functions