Skip to main content
The Messages API provides RPC methods for managing chat messages in Hazel Chat. All methods require authentication and include rate limiting (60 requests per minute per user).

Overview

Messages are the core content of conversations in Hazel Chat. Each message belongs to a channel and has an author. Messages support:
  • Plain text content
  • Rich embeds for links and media
  • Attachments (images, files)
  • Replies to other messages
  • Thread creation

Authentication

All message operations require authentication via AuthMiddleware. The authorId is automatically set from the authenticated user context.

Rate Limiting

Message operations are rate-limited to 60 requests per minute per user to prevent spam and abuse.

Methods

message.create

Creates a new message in a channel.
channelId
string (UUID)
required
The ID of the channel to post the message in
content
string
required
The text content of the message
embeds
MessageEmbeds | null
Rich embed data for links, images, or other media
replyToMessageId
string (UUID) | null
ID of the message this is replying to
threadChannelId
string (UUID) | null
ID of the thread channel if this message creates or belongs to a thread
attachmentIds
string[]
Array of attachment IDs to link to this message. Attachments must be uploaded first.

Response

data
Message
The created message object
transactionId
string
Transaction ID for optimistic UI updates

Errors

  • ChannelNotFoundError - The specified channel does not exist
  • UnauthorizedError - User lacks permission to post in this channel
  • RateLimitExceededError - User has exceeded the rate limit (60/min)
  • InternalServerError - An unexpected error occurred

Example

import { RpcClient } from "@hazel/domain/rpc"
import { Effect } from "effect"

const createMessage = Effect.gen(function* () {
  const client = yield* RpcClient
  
  const result = yield* client.MessageCreate({
    channelId: "550e8400-e29b-41d4-a716-446655440000",
    content: "Hello, world!",
    attachmentIds: [],
    embeds: null,
    replyToMessageId: null,
    threadChannelId: null,
  })
  
  console.log("Message created:", result.data.id)
  return result
})

message.update

Updates an existing message. Only the message author or users with appropriate permissions can update messages.
Only content and embeds can be updated. Immutable fields like channelId, replyToMessageId, and threadChannelId cannot be changed to prevent moving messages between channels or fabricating conversation context.
id
string (UUID)
required
The ID of the message to update
content
string
New text content for the message
embeds
MessageEmbeds | null
Updated embed data

Response

data
Message
The updated message object (see message.create for fields)
transactionId
string
Transaction ID for optimistic UI updates

Errors

  • MessageNotFoundError - The specified message does not exist
  • UnauthorizedError - User lacks permission to update this message
  • RateLimitExceededError - User has exceeded the rate limit (60/min)
  • InternalServerError - An unexpected error occurred

Example

import { RpcClient } from "@hazel/domain/rpc"
import { Effect } from "effect"

const updateMessage = Effect.gen(function* () {
  const client = yield* RpcClient
  
  const result = yield* client.MessageUpdate({
    id: "550e8400-e29b-41d4-a716-446655440000",
    content: "Updated message content",
  })
  
  console.log("Message updated:", result.data.updatedAt)
  return result
})

message.delete

Deletes a message (soft delete). Only the message author or users with appropriate permissions can delete messages.
This is a soft delete operation. The message is marked as deleted but remains in the database with a deletedAt timestamp.
id
string (UUID)
required
The ID of the message to delete

Response

transactionId
string
Transaction ID for optimistic UI updates

Errors

  • MessageNotFoundError - The specified message does not exist
  • UnauthorizedError - User lacks permission to delete this message
  • RateLimitExceededError - User has exceeded the rate limit (60/min)
  • InternalServerError - An unexpected error occurred

Example

import { RpcClient } from "@hazel/domain/rpc"
import { Effect } from "effect"

const deleteMessage = Effect.gen(function* () {
  const client = yield* RpcClient
  
  const result = yield* client.MessageDelete({
    id: "550e8400-e29b-41d4-a716-446655440000",
  })
  
  console.log("Message deleted, transaction:", result.transactionId)
  return result
})

Best Practices

Optimistic Updates

Use the transactionId returned from all operations to implement optimistic UI updates:
const optimisticCreate = Effect.gen(function* () {
  const client = yield* RpcClient
  
  // Show message immediately in UI
  const tempMessage = {
    content: "Hello!",
    channelId: currentChannelId,
    // ... other fields
  }
  addMessageToUI(tempMessage)
  
  // Send to server
  const result = yield* client.MessageCreate(tempMessage)
  
  // Update UI with real data and transactionId
  updateMessageInUI(result.transactionId, result.data)
})

Rate Limiting

Handle rate limit errors gracefully:
const createWithRetry = Effect.gen(function* () {
  const client = yield* RpcClient
  
  const result = yield* client.MessageCreate(payload).pipe(
    Effect.catchTag("RateLimitExceededError", (error) =>
      Effect.gen(function* () {
        // Wait and retry
        yield* Effect.sleep("60 seconds")
        return yield* client.MessageCreate(payload)
      })
    )
  )
  
  return result
})

Attachment Handling

Always upload attachments before creating the message:
const createMessageWithFile = Effect.gen(function* () {
  const client = yield* RpcClient
  
  // 1. Upload attachment first
  const attachment = yield* uploadFile(file)
  
  // 2. Create message with attachment ID
  const message = yield* client.MessageCreate({
    channelId: currentChannelId,
    content: "Here's the file!",
    attachmentIds: [attachment.id],
  })
  
  return message
})

Source Code Reference

  • RPC Contracts: packages/domain/src/rpc/messages.ts
  • Message Model: packages/domain/src/models/message-model.ts
  • Backend Handlers: apps/backend/src/rpc/handlers/messages.ts
  • Message Policy: apps/backend/src/policies/message-policy.ts

Build docs developers (and LLMs) love