npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@msgly/line

v1.4.0

Published

LINE Messaging API adapter for Msgly

Readme

@msgly/line

LINE Messaging API adapter for Msgly. Send and receive LINE messages through the unified hub — text, image, video, audio, location, buttons, and quick replies. Supports LINE's free reply-token sends. Zero classes, runs in Node, Next.js, and Edge runtimes.

📖 Docs & channel reference: https://ayushjain070401.github.io/msgly/

Install

npm install @msgly/core @msgly/line

Quick start

import express from 'express';
import { createHub } from '@msgly/core';
import { createLineAdapter } from '@msgly/line';

const hub = createHub();

hub.register(
  createLineAdapter({
    channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN!,
    channelSecret: process.env.LINE_CHANNEL_SECRET!,
  }),
);

await hub.connect({ throwOnFailure: true });

hub.on('message', async (msg) => {
  if (msg.channel === 'line' && msg.content.type === 'text') {
    // Use the free reply token when available
    await hub.send({
      channel: 'line',
      account: msg.account,
      contact: msg.contact,
      content: { type: 'text', text: `You said: ${msg.content.text}` },
      metadata: { replyToken: msg.metadata?.replyToken },
    });
  }
});

const app = express();
app.use(express.json({ verify: (req, _r, buf) => ((req as any).rawBody = new Uint8Array(buf)) }));

const handlers = hub.createWebhookHandler();
app.get('/webhook/:channel', handlers.get);
app.post('/webhook/:channel', handlers.post);

app.listen(3000);

Config

interface LineConfig {
  /** Long-lived channel access token from the Messaging API tab. */
  channelAccessToken: string;

  /** Channel secret from Basic settings — used for webhook signature verification. */
  channelSecret: string;

  /** Override for tests. Defaults to https://api.line.me */
  apiBase?: string;

  /** Override for tests. Defaults to https://api-data.line.me (media endpoints). */
  dataApiBase?: string;
}

Setup (10 minutes)

  1. Sign up at developers.line.biz. Create a Provider, then a Messaging API channel inside it.
  2. Channel secret. Open the channel → Basic settings → copy Channel secret → set as LINE_CHANNEL_SECRET.
  3. Channel access token. Messaging API tab → under "Channel access token (long-lived)" click Issue → copy → set as LINE_CHANNEL_ACCESS_TOKEN.
  4. Webhook URL. On the Messaging API tab:
    • Webhook URL: <PUBLIC_URL>/webhook/line
    • Toggle Use webhook to ON
    • Click Verify — should succeed
  5. Disable LINE's built-in replies. Same tab:
    • "Auto-reply messages": OFF (otherwise LINE answers before your bot)
    • "Greeting messages": OFF (optional)
  6. Add the bot to a chat. Find the bot's QR code on the Messaging API tab → scan with the LINE app → add as friend → message it.

Capabilities

| Feature | Supported | | ------------- | --------- | | text | ✓ | | image | ✓ | | video | ✓ | | audio | ✓ | | file | — | | location | ✓ | | buttons | ✓ | | quick replies | ✓ (max 13, 20-char labels) | | templates | — | | reactions | — | | typing | ✓ |

Typing indicator

hub.on('message', async (msg) => {
  if (msg.channel !== 'line') return;

  const adapter = hub.getAdapter('line') as LineAdapter;
  await adapter.sendTyping?.(msg.contact);  // shows the loading animation

  const reply = await generateReply(msg);   // AI work, etc.

  await hub.send({ channel: 'line', account: msg.account, contact: msg.contact,
    content: { type: 'text', text: reply } });
});

This calls LINE's Loading Animation API (POST /v2/bot/chat/loading/start) with loadingSeconds: 20. The animation disappears when you send a message or after the specified time. Errors are silently swallowed — a missing animation is non-fatal.

1:1 chats only. The Loading Animation API only works in conversations where the user has followed your Official Account. It will silently fail in group chats.

Reply tokens (free vs push)

LINE's pricing model:

  • Reply API (uses a replyToken from the inbound event) — free, doesn't count against your monthly quota.
  • Push API (no reply token) — counts against your push quota.

Reply tokens are single-use and expire about 1 minute after the inbound message. Use them when responding immediately:

hub.on('message', async (msg) => {
  if (msg.channel === 'line' && msg.content.type === 'text') {
    await hub.send({
      channel: 'line',
      account: msg.account,
      contact: msg.contact,
      content: { type: 'text', text: 'thanks!' },
      metadata: { replyToken: msg.metadata?.replyToken },  // ← free reply
    });
  }
});

Send without replyToken and the adapter falls back to push automatically.

Sending examples

Image

await hub.send({
  channel: 'line',
  account, contact,
  content: {
    type: 'image',
    mediaRef: { kind: 'url', value: 'https://example.com/cat.jpg' },
  },
});

LINE requires media URLs to be public HTTPS with no auth.

Quick replies

await hub.send({
  channel: 'line',
  account, contact,
  content: {
    type: 'interactive',
    text: 'Pick one:',
    buttons: [
      { id: 'a', label: 'Option A' },
      { id: 'b', label: 'Option B' },
    ],
  },
});

User tap → you receive a text message whose content.text matches the button id.

Common pitfalls

  • Bot replies but user doesn't see it: in the LINE Developers Console, "Auto-reply messages" must be OFF. It overrides your bot's responses.
  • Invalid reply token: the token expired (>1 min) or was already used. Fall back to push by omitting metadata.replyToken.
  • InvalidSignature: LINE_CHANNEL_SECRET is wrong, or your Express app isn't capturing the raw body. The verify callback in express.json() is essential.
  • Webhook verify fails in console: server must be running and reachable at the public URL when you click Verify.

Documentation

Full setup walkthrough and multi-channel usage: https://github.com/AyushJain070401/msgly

Broadcast — the campaign primitive

LINE is one of the few channels with a true broadcast API. Reaching 100,000 friends is one request, not 100,000 — so use these rather than sendBulk:

// Every friend of the official account
await line.broadcast(
  { type: 'text', text: 'Spring sale starts now' },
  { retryKey: 'spring-2026' },   // LINE de-duplicates retries with the same key
);

// A segment — max 500 user ids per call
await line.multicast(segmentIds, { type: 'text', text: 'Just for you' });

// How much of the monthly plan quota is left (null = unlimited plan)
const left = await line.getQuotaRemaining();

The retry key matters: without it, a network timeout on a broadcast leaves you unable to retry safely, because you cannot tell whether the campaign went out. With it, LINE discards the duplicate.

A broadcast consumes your plan's monthly message quota and cannot be recalled. A 429 back is the quota or a rate limit — the adapter marks it retryable, while a 4xx is marked permanent.

Pass notificationDisabled: true to deliver silently.

License

MIT