With Liveblocks 1.8, we’re introducing an improved way to consume our REST APIs
with types, making it quicker to integrate Liveblocks into the back end of your
application. We’re also adding a way to transform comments into HTML, Markdown,
and more, meaning sending formatted email notifications is now simpler.

- [Typed REST APIs](#typed-rest-apis)
- [Transforming Comments](#transforming-comments)
- [Sending Comments email notifications](#sending-comments-email-notifications)

## Upgrade now [#upgrade-now]

Start using the new features now by updating each Liveblocks package in your
project to the `@latest` version.

```bash
 npm install @liveblocks/client@latest @liveblocks/react@latest @liveblocks/react-comments@latest liveblocks/node@latest
```

If you use any other Liveblocks packages, make sure to include those too.

## Typed REST APIs [#typed-rest-apis]

It’s now possible to access our REST APIs using the
[`@liveblocks/node`](/docs/api-reference/liveblocks-node) package. Previously,
the only way to access our REST APIs in Node.js was via a `fetch` call.

```ts
// ❌ Before, using fetch
const response = await fetch("https://api.liveblocks.io/v2/rooms", {
  method: "POST",
  headers: {
    Authorization: "Bearer {{SECRET_KEY}}",
  },
  body: JSON.stringify({
    id: "my-room-name",
    defaultAccesses: ["room:write"],
  }),
});

// ❌ No types
const room = await response.json();
```

From now, you can set up a `Liveblocks` client, call a corresponding method, and
get type hints on the returned value.

```ts
// ✅ After, using `Liveblocks` and typed methods
const liveblocks = new Liveblocks({
  secret: "{{SECRET_KEY}}",
});

// ✅ Fully-typed room
const room = await liveblocks.createRoom("my-room-name", {
  defaultAccesses: ["room:write"],
});
```

Every existing REST API is supported, and we’ll continue to add them in the
future. Here’s an example of some more methods.

```ts
// Get users currently in a room
const activeUsers = await liveblocks.getActiveUsers("my-room-id");

// Get Storage data as JSON
const storage = await liveblocks.getStorageDocument("my-room-id", "json");

// Update Yjs data
await liveblocks.sendYjsBinaryUpdate("my-room-id", {
  update: Y.encodeStateAsUpdate(yDoc),
});

// Get the last 20 rooms that contain your custom metadata
const canvasRooms = await liveblocks.getRooms({
  limit: 20,
  metadata: { myRoomType: "canvas" },
});
```

Find all the methods in the API reference under
[`@liveblocks/node`](/docs/api-reference/liveblocks-node).

## Transforming comments

Using Liveblocks 1.8, you can transform a comment’s text into different formats,
for example HTML, Markdown, or a plain string of text. Here’s an example of a
comment converted into two different formats.

<Figure highlight={false}>
  <Image
    src="/images/blog/liveblocks-1-8/comment-body.png"
    alt="Comment with example body: 'Thank you so much @Emil Joyce!', with 'so much' in bold"
    width={820}
    height={394}
    quality={90}
  />
</Figure>

```md
Thank you **so much** @Emil Joyce!
```

```html
<p>Thank you <b>so much</b> <span data-mention>@Emil Joyce</span>!</p>
```

To enable this, we’ve added a new helper function called
[`stringifyCommentBody`](/docs/api-reference/liveblocks-node#stringify-comment-body).
Here’s one way it could be used, alongside
[`Liveblocks.getComment`](/docs/api-reference/liveblocks-node#get-comment).

```ts
// Retrieve a comment
const comment = await liveblocks.getComment({
  roomId: "my-room-id",
  threadId: "my-thread-id",
  commentId: "my-comment-id",
});

// Convert comment body into Markdown
const markdownBody = await stringifyCommentBody(comment.body, {
  format: "markdown",
});

// "Thank you **so much** emil.joyce@example.com!"
console.log(markdownBody);
```

You can also customize the output format, either completely, or on an
element-by-element basis. Make sure to read
[`stringifyCommentBody`](/docs/api-reference/liveblocks-node#stringify-comment-body)
to learn more.

### Get mentioned IDs

As a bonus, we’ve also added a utility that makes it easy to find each user
mentioned in a comment’s body. It’s called
[`getMentionedIdsFromCommentBody`](#get-mentioned-ids-from-comment-body), and
here’s how it works.

<Figure highlight={false}>
  <Image
    src="/images/blog/liveblocks-1-8/comment-mentions.png"
    alt="Comment with example body: 'Could you take a look at this @Jody Hekla, @Tatum Paolo?'"
    width={820}
    height={394}
    quality={90}
  />
</Figure>

```ts
// Retrieve a comment
const comment = await liveblocks.getComment({
  roomId: "my-room-id",
  threadId: "my-thread-id",
  commentId: "my-comment-id",
});

// Get IDs for each user mentioned
const mentionedIds = getMentionedIdsFromCommentBody(comment.body);

// ["jody.hekla@example.com", "tatum.paolo@example.com"]
console.log(mentionedIds);
```

## Sending Comments email notifications

Using the new REST API and comment transformation functions, it’s easier than
ever to send formatted email notifications. You can trigger events when comments
are created using our webhooks, and in particular the
[`CommentCreatedEvent`](/docs/platform/webhooks#CommentCreatedEvent).

<Figure highlight={false}>
  <Image
    src="/images/blog/liveblocks-1-8/comment-email.png"
    alt="Example comment email notification"
    width={820}
    height={482}
    quality={90}
  />
</Figure>

Here’s an example of a Next.js route handler that listens for new comments, then
sends a formatted HTML email with [Resend](https://resend.com).

```ts file="route.ts" isCollapsed isCollapsable
import {
  Liveblocks,
  WebhookHandler,
  stringifyCommentBody,
} from "@liveblocks/node";
import { Resend } from "resend";

// Create Resend client (add your API key)
const resend = new Resend("re_123456789");

// Add your signing key from a project's webhooks dashboard
const WEBHOOK_SECRET = "YOUR_SIGNING_SECRET";
const webhookHandler = new WebhookHandler(WEBHOOK_SECRET);

// Add your secret key from a project's API keys dashboard
const API_SECRET = "{{SECRET_KEY}}";
const liveblocks = new Liveblocks({ secret: API_SECRET });

export async function POST(request: Request) {
  const body = await request.json();
  const headers = request.headers;

  // Verify if this is a real webhook request
  let event;
  try {
    event = webhookHandler.verifyRequest({
      headers: headers,
      rawBody: JSON.stringify(body),
    });
  } catch (err) {
    console.error(err);
    return new Response("Could not verify webhook call", { status: 400 });
  }

  // When a comment has been created
  if (event.type === "commentCreated") {
    const { roomId, threadId, commentId } = event.data;

    try {
      // Get comment data and participants
      const [comment, { participantIds }] = await Promise.all([
        liveblocks.getComment({ roomId, threadId, commentId }),
        liveblocks.getThreadParticipants({ roomId, threadId }),
      ]);

      // Formatted comment body
      const commentText = await stringifyCommentBody(comment.body);

      // Get users from your database
      const users = await __getUsers__(participantIds);

      // Send email to the users' email addresses
      try {
        const data = await resend.emails.send({
          from: "My company <hello@my-company.com>",
          to: [users.map((user) => user.email)],
          subject: "New comment",
          text: commentText,
        });
      } catch (error) {
        console.error(error);
      }
    } catch (err) {
      console.log(err);
      return new Response("Could not fetch comment data", { status: 500 });
    }
  }

  return new Response(null, { status: 200 });
}
```

To learn exactly how this works, we’ve written a full guide on
[sending email notifications when comments are created](/docs/guides/how-to-send-email-notifications-when-comments-are-created).

## Contributors

Huge thanks to everyone who contributed and specifically to
[Nimesh Nayaju](https://twitter.com/nayajunimesh) and
[Marc Bouchenoire](https://twitter.com/marcbouchenoire) for their work on the
REST API methods and the Comments functions respectively. Keep checking out the
[changelog](https://github.com/liveblocks/liveblocks/blob/main/CHANGELOG.md) for
the full release notes–see you next time!