# Send messages, embeds and files

Reply, send and edit messages, build embeds, attach files and send direct messages

Start with a bot that can reply to `!ping`. Add these helpers to the [first bot example](https://fluxerly.neontechspace.com/docs/quick-start.md) and call them from a command or message [handler](https://fluxerly.neontechspace.com/docs/glossary.md#handler). They use the bot's existing `client` rather than opening another connection. The [last section](https://fluxerly.neontechspace.com/docs/messages.md#wire-it-up) shows where they plug into `runBot`

## Reply to a message and check what happened

Use `reply` when the answer belongs to an incoming message. Awaiting an operation in the default API gives a [Result](https://fluxerly.neontechspace.com/docs/glossary.md#result): Check `isErr()` first, then read `value`

```ts
import { describeError, type Client, type Message } from "@neontechspace/fluxerly"

export async function replyHello(client: Client, message: Message) {
    if (message.author.isBot) return

    const result = await client.messages.reply(message, `Hello, ${message.author.username}!`)
    if (result.isErr()) {
        console.error(describeError(result.error))
        return
    }
    return result.value
}
```

A plain string sends a text-only message. On success, `value` is the message Fluxer created, including its ID. The `describeError` helper prints the failure's message, code, suggested fix and cause. Replies do not notify anyone by default, not even the author of the replied-to message

<details>
<summary>What does a failed send say?</summary>

The error message names the operation and Fluxer's answer, then Fluxer's code and the HTTP status, for example `Message send failed: Fluxer reports that the bot lacks a required permission (MISSING_PERMISSIONS, HTTP 403)`.
The error's `hint` suggests a next step. For a missing permission, `details.requiredPermissions` lists the permissions Fluxer checks for the operation, such as `["ViewChannel", "SendMessages"]` for a send, and the hint names them. Fluxer does not report which one is missing, so the bot may already have some of them. When Fluxer answers with a code this SDK version does not know yet, `details.providerCode` holds it. When Fluxer's answer does not have the expected shape, `details.responseField` names the field that failed

A rejection with HTTP 401 or 403 that the application handles also logs a Warn record with the code `rest.rejected`, because it usually points at a lasting token or permission problem. The SDK holds that Warn for up to one second. A handler's failure record replaces it only if the handler fails with the same rejection while the Warn is held and no `onError` hook is configured for that failure

[Identical repeats collapse](https://fluxerly.neontechspace.com/docs/logging.md#collapse-repeated-errors) into one record per minute by default. The [error and log codes](https://fluxerly.neontechspace.com/docs/codes/) page lists every code with its meaning

</details>

<details>
<summary>Skip join notices and other system messages</summary>

Fluxer also delivers notices, such as a member joining or a pinned message, as messages. Compare `message.type` with a `MessageType` constant to tell them apart. For example, `message.type === MessageType.UserJoin` is a join notice, and ordinary messages are `MessageType.Default` or `MessageType.Reply`

</details>

A `messageCreate` or `messageUpdate` event for a community message can carry `message.member`, the author's community membership as Fluxer held it when it sent the event, with roles, nickname and join time. It is absent on messages from REST reads, direct messages, webhooks and authors who left the community, and the message cache does not keep it, so read it in the handler. With `cache.members` enabled, `members.get` then answers for that author without a request. The same member arrives as `reaction.member` on `messageReactionAdd` and `messageReactionRemove` events and as `typing.member` on `typingStart` events. A member that Fluxer sends in a form the SDK cannot read is left off, and the event still arrives

A `messageCreate` event, and no other message event, can also carry `message.channelType`, the numeric type of the channel, with 1 for a direct message and 3 for a group direct message, `message.mentionedHere`, which Fluxer sends only as `true` for a here mention, and `message.nicknames`, the nicknames of a group direct message keyed by user ID. REST reads and `messageUpdate` events never carry them, and the message cache does not keep them. A `messageDelete` event can carry `deletion.member`, the author's community membership without a username or bot flag, because Fluxer removes the member's user from it. The member cache does not store it

Received messages can carry `message.messageReference`, which points at a replied-to or forwarded message, or at the source of an announcement copy or follow notice. Its `channelId` is always present, but `id` is optional. Check `id`, then build a `MessageReference` from both fields for a fetch or reply. A follow notice identifies only the source channel. See [announcement channels](https://fluxerly.neontechspace.com/docs/announcement-channels.md) for publishing messages and handling their copies

Sends and edits accept only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications`. The SDK rejects any other flag before sending the request. Fluxer sets `Crossposted`, `IsCrosspost` and `SourceMessageDeleted` itself

## Mention someone deliberately

Create a mention with `format.userMention`, then allow a notification for that specific user. Role and channel mentions have matching `roleMention` and `channelMention` helpers

```ts
import { format, type Client } from "@neontechspace/fluxerly"

export async function notifyParticipant(client: Client, channelId: string, userId: string) {
    return await client.messages.send(channelId, {
        content: `${format.userMention(userId)} The game is starting`,
        allowedMentions: { users: [userId] },
    })
}
```

Methods that take a user, role or channel ID expect the decimal ID. To read a mention typed by a user, use `format.tryParseMention`, which returns a Result instead of throwing on invalid text. [Command arguments](https://fluxerly.neontechspace.com/docs/commands.md) can also accept either an ID or a mention

<details>
<summary>Send a text-to-speech message</summary>

Add `tts: true` to the content passed to `messages.send`, `messages.reply`, `directMessages.send` or the `reply` helper of a `runBot` handler or command. A value other than a boolean fails with reason `input` before any request.
In a community, a bot without the Send TTS Messages permission gets a normal message and no error

The `tts` field of a message is meaningful only on the message returned by the send and on the `messageCreate` event. Fluxer does not store it, so fetches, history pages and other later reads always report `false`.
Webhook messages do not accept `tts`: A webhook send, reply or forward that includes it fails with reason `input` before any request

</details>

## Send, then edit the returned message

Use `send` for a channel message that does not reply to anything. Keep the returned message to edit it later

```ts
import type { Client } from "@neontechspace/fluxerly"

export async function postStatus(client: Client, channelId: string) {
    const sent = await client.messages.send(channelId, "Preparing the report…")
    if (sent.isErr()) return sent

    return await client.messages.edit(sent.value, "Report ready")
}
```

The helper posts a message, then edits it, and returns the edit's Result or the send's failure

<details>
<summary>What if the send or the edit fails?</summary>

If the edit fails, the first message stays posted. If the connection fails after a send or edit was dispatched, the change may still have reached Fluxer. Check the returned Result before deciding whether to try again, and avoid repeating a write whose outcome is unknown.
The [reliability guide](https://fluxerly.neontechspace.com/docs/reliability.md) explains these uncertain writes and when a retry is safe

</details>

## Build an embed

An [embed](https://fluxerly.neontechspace.com/docs/glossary.md#embed) is a formatted card with a title, description, color and fields. The embed builder assembles one step by step. Pass the builder in the message's `embeds` list

```ts
import { builders, type Client } from "@neontechspace/fluxerly"

export async function postSchedule(client: Client, channelId: string) {
    const embed = builders.embed()
        .title("Tonight's game")
        .description("Meet in the lobby before the first round")
        .color("#8b7cf8")
        .field("Start", "19:00 UTC", { inline: true })
        .field("Mode", "Co-op", { inline: true })
        .footer({ text: "Snacks provided" })

    return await client.messages.send(channelId, { embeds: [embed] })
}
```

The `inline` option places fields side by side. The `color` method accepts a hex string such as `"#8b7cf8"`, a number or an RGB tuple, and `timestamp` accepts a `Date`, epoch milliseconds or an ISO 8601 string with a time zone. The send checks the embed against Fluxer's limits. It reads the builder once, so later builder changes do not alter the sent message. Use `messages.edit` for that. The builder's `build()` method returns the same embed as a plain object, which could also be written by hand

An embed's `image.url` or `thumbnail.url` can use `attachment://filename` to show a new image or video uploaded in the same request. The name must match exactly one upload, including case, and its extension must map to an image or video MIME type in Fluxer's filename lookup, such as AVIF or MP4. Referencing an existing attachment ID is not supported

In a community channel, sending or editing a message with embeds needs the `EmbedLinks` permission. New communities grant it to everyone, but where it is removed, Fluxer rejects the whole message with a missing-permissions error instead of sending it without the embeds

## Upload a small generated file

For text already in memory, encode it to bytes and give the attachment a file name. No temporary file on disk is needed

```ts
import { builders, type Client } from "@neontechspace/fluxerly"

export async function sendChecklist(client: Client, channelId: string) {
    const data = new TextEncoder().encode("[ ] Check permissions\n[ ] Test !ping\n")
    const message = builders.message()
        .content("Bot checklist")
        .attachment({ data, filename: "checklist.txt", contentType: "text/plain" })
        .build()

    return await client.messages.send(channelId, message)
}
```

The builder keeps a reference to the bytes, and the send copies them when it is called. For larger files, pass a `file` source such as a `Blob`, or a `stream` with its exact `size`, instead of `data`. The [attachment input reference](https://fluxerly.neontechspace.com/docs/api/js-ts/messages/#attachmentinput) lists these forms, which avoid loading everything into memory

## Send a file from disk

On Node.js, `openAsBlob` from `node:fs` opens a file without reading it into memory. Pass the result as `file` and give the upload a name. A path string is not accepted, so open the file first

```ts
import { openAsBlob } from "node:fs"
import { basename } from "node:path"
import { builders, type Client } from "@neontechspace/fluxerly"

export async function sendReport(client: Client, channelId: string, path: string) {
    const file = await openAsBlob(path)
    const message = builders.message()
        .content("Daily report")
        .attachment({ file, filename: basename(path) })
        .build()

    return await client.messages.send(channelId, message)
}
```

The send reads the file while it uploads, so keep the file unchanged until the send finishes

## Download an attachment when needed

Received messages describe their attachments but do not download them. Set a byte limit and handle a failed download before processing any content

```ts
import { describeError, type Client, type Message } from "@neontechspace/fluxerly"

export async function readFirstSmallFile(client: Client, message: Message) {
    const attachment = message.attachments[0]
    if (!attachment) return

    const downloaded = await client.attachments.download(attachment, { maxBytes: 64 * 1024 })
    if (downloaded.isErr()) {
        console.error(describeError(downloaded.error))
        return
    }
    return downloaded.value
}
```

Check the file type and contents before processing downloaded bytes. The download uses the attachment's media address on the bot's Fluxer instance and does not accept arbitrary URLs from message text

Buffered downloads and `attachments.stream` preserve a sanitized cause for discovery and transport failures, including a safe code such as `ECONNRESET` when available. The original transport error, attachment URL and response body are not retained in that cause

## Send a requested direct message

Send a private message only after the recipient asks for one, for example through an opt-in command. The `directMessages.send` method opens the conversation with that user and sends the message

```ts
import type { Client } from "@neontechspace/fluxerly"

export async function sendRequestedDm(client: Client, recipientId: string) {
    return await client.directMessages.send(recipientId, "The requested game instructions are ready")
}
```

A send can still fail when the recipient's privacy settings block it. To reuse the conversation, `directMessages.open` returns its channel, and existing group conversations use the normal message methods with their channel ID. Creating groups is outside the SDK's supported operations. See [private conversations](https://fluxerly.neontechspace.com/docs/api/js-ts/users-and-dms/#directmessages) for reads and permitted management actions

## Wire it up

In a `runBot` bot, each command receives a `reply` helper already bound to the incoming message. This bot answers `!schedule` with an embed and stops cleanly on Ctrl+C

```ts
import { builders, runBot } from "@neontechspace/fluxerly"

await runBot({
    token: process.env.FLUXER_BOT_TOKEN,
    commands: {
        prefix: "!",
        commands: {
            schedule: {
                description: "Show tonight's game",
                execute: ({ reply }) => {
                    const embed = builders.embed()
                        .title("Tonight's game")
                        .description("Meet in the lobby before the first round")
                        .color("#8b7cf8")
                    return reply({ embeds: [embed] })
                },
            },
        },
    },
})
```

Returning the reply's Result lets the [command router](https://fluxerly.neontechspace.com/docs/glossary.md#command-router) report a failed reply with the command name. If the bot itself stops with a failure, such as a rejected token, `runBot` logs it once and sets `process.exitCode` to 1, so the file needs no error handling of its own

Helpers that take a `client`, such as `postSchedule` above, receive it from the command context, for example with `execute: ({ client, message }) => postSchedule(client, message.channelId)`

Continue with [prefix commands](https://fluxerly.neontechspace.com/docs/commands.md) to turn these helpers into named bot actions, or see the [Effect workflow examples](https://fluxerly.neontechspace.com/docs/effect-workflows.md) for the same operations inside an Effect program
