Mastodon

Post to Mastodon from Nuxt with an access token: visibility, content warnings, images, editing and deleting, plus polled notifications for your own account.

Mastodon is the first of two channels where sending means publishing. The verb is post, not send, so a line that goes to the world never looks like a line that goes to your phone.

Copy this

Get an access token

In your instance: Preferences → Development → New application. Give it the scopes write:statuses and, for receiving, read:notifications. Submit, open the application again, and copy Your access token.

Configure it

.env
PIGEON_MASTODON_INSTANCE=https://mastodon.social
PIGEON_MASTODON_TOKEN=...
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-pigeon'],
  nuxtPigeon: {
    channels: {
      mastodon: { instance: 'https://mastodon.social' },
    },
  },
})

Post

server/api/hello.get.ts
export default defineEventHandler(async () => {
  const status = await mastodon.post('Release 1.0 is out', {
    visibility: 'direct',
  })

  return { url: status.url }
})

Open the url it returns. direct keeps it to mentioned accounts while you are trying things out.

Every option

OptionWhat it does
visibilitypublic, unlisted, private (followers), direct
mediaUp to four. Downloaded and uploaded again, Mastodon takes no url
spoilerTextContent warning, the post collapses behind it
sensitiveMarks the media as sensitive
languageISO 639, drives the translation offer in clients
inReplyToIdMakes it a reply, which is how threads are built
scheduledAtISO 8601, at least five minutes out
idempotencyKeyThe same key twice creates no second post
idempotencyKey is the one real retry guard Mastodon offers. If a request times out and you are not sure whether it landed, sending again with the same key cannot duplicate it.

The character count is not length

The limit is per instance, 500 is only Mastodon's default, and the counting has two rules of its own:

  • A link always costs 23, however long it is. Shortening a url changes nothing.
  • The domain of a remote mention is free. @flo@example.com costs as much as @flo.

The module asks your instance once for its real numbers and remembers them. If the instance cannot be reached it falls back to the defaults rather than blocking, because the instance decides in the end anyway.

Receiving

nuxt.config.ts
nuxtPigeon: {
  channels: {
    mastodon: { instance: 'https://mastodon.social', receive: true, intervalMs: 60000 },
  },
}

Polled, not pushed. There is no webhook for your own account, so this needs a process that stays alive and does not work on serverless.

server/plugins/mastodon.ts
export default defineNitroPlugin(() => {
  mastodon.listen((notification) => {
    console.log(notification.type, notification.account?.acct)
  })
})

What will cost you an hour

  • Mastodon never notifies you about your own doing. Testing needs a second account, or a friend who mentions you.
  • The first poll only marks where we are. Trigger something after the server started, or you will wait for an event that already happened.
  • A large upload answers 202 while it is still being processed, and posting before that finishes is refused. The module waits for you.
  • Whether an edit keeps the images is not in the docs. It is in the source: the update only touches attachments when media_ids is present. The module leaves the field out to keep them, which is the opposite work from Discord and reads the same for you.
  • Editing is public. Mastodon keeps a version history and clients show an "edited" marker.
  • Deleting answers with the post, in plain text, so a client can offer delete and redraft. Its attachments stay available for about 24 hours.

Read more