DEV Community

Samy
Samy

Posted on

Build a Daily Tech-News Bot for Slack or Discord in 15 Minutes (Node.js + NewTqnia API)

Every team I've been on has a #general channel that goes quiet by 11am. A daily digest bot is the easiest way to give people a reason to glance back at it, and "today's tech headlines" is a low-effort, high-signal thing to post that nobody minds seeing every morning.

Here's a complete Slack/Discord bot, built on NewTqnia's free public news API, that you can have running in about 15 minutes.

What we're building

A small Node script that:

  1. Fetches today's tech headlines (AI, robotics, space, health, energy, and a few other beats) from NewTqnia's API
  2. Formats them as a Slack or Discord message
  3. Posts them to a webhook
  4. Runs on a schedule via GitHub Actions, so there's no server to keep alive

Step 1: Install the SDK

npm install newtqnia-node
Enter fullscreen mode Exit fullscreen mode

It's a small, typed client around NewTqnia's REST API. No API key is required for this.

Step 2: The script

// digest-bot.mjs
import { NewTqniaClient } from "newtqnia-node";

const client = new NewTqniaClient({ application: "tech-news-digest-bot" });

async function getDigest(locale = "en", limit = 5) {
  // "today" can be empty early in the day (it resets on the Asia/Dubai
  // boundary), so fall back to "latest" if there's nothing yet.
  const today = await client.news.today({ locale, limit });
  if (today.articles.length) return today;
  return client.news.latest({ locale, limit });
}

function toDiscordPayload(digest) {
  const lines = digest.articles
    .map((a) => `• [${a.title}](${a.url})`)
    .join("\n");
  return {
    content: `**Today in Tech, via [NewTqnia](${digest.publisher.url})**\n\n${lines}`,
  };
}

function toSlackPayload(digest) {
  const lines = digest.articles
    .map((a) => `• <${a.url}|${a.title}>`)
    .join("\n");
  return {
    text: `*Today in Tech, via <${digest.publisher.url}|NewTqnia>*\n\n${lines}`,
  };
}

async function postDigest(webhookUrl, kind = "discord", locale = "en") {
  const digest = await getDigest(locale);
  const payload = kind === "slack" ? toSlackPayload(digest) : toDiscordPayload(digest);

  const res = await fetch(webhookUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  if (!res.ok) {
    throw new Error(`Webhook post failed: ${res.status} ${await res.text()}`);
  }
}

const webhookUrl = process.env.DISCORD_WEBHOOK_URL || process.env.SLACK_WEBHOOK_URL;
const kind = process.env.SLACK_WEBHOOK_URL ? "slack" : "discord";

if (!webhookUrl) {
  console.error("Set DISCORD_WEBHOOK_URL or SLACK_WEBHOOK_URL");
  process.exit(1);
}

postDigest(webhookUrl, kind).catch((err) => {
  console.error(err);
  process.exit(1);
});
Enter fullscreen mode Exit fullscreen mode

A couple of things worth calling out:

  • today vs latest: NewTqnia's today endpoint resets on the Asia/Dubai day boundary and can legitimately return an empty array if nothing's published yet when your job runs. Falling back to latest avoids posting an empty digest.
  • Attribution: NewTqnia's API terms ask that you keep a visible link back when you display its content and preserve the article URLs it returns. Both formatters above do that by linking the publisher name, so you get this right by default rather than as an afterthought.

Step 3: Get a webhook URL

Discord: Server Settings > Integrations > Webhooks > New Webhook > copy the URL.

Slack: create an Incoming Webhook for the channel you want and copy the URL it gives you.

Test locally:

DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." node digest-bot.mjs
Enter fullscreen mode Exit fullscreen mode

Step 4: Run it on a schedule, for free

No server needed. A scheduled GitHub Action does the job:

# .github/workflows/digest.yml
name: Post daily tech digest
on:
  schedule:
    - cron: "0 6 * * *" # 06:00 UTC daily, adjust to your team's morning
  workflow_dispatch: {}

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install newtqnia-node
      - run: node digest-bot.mjs
        env:
          DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
Enter fullscreen mode Exit fullscreen mode

Add DISCORD_WEBHOOK_URL (or SLACK_WEBHOOK_URL) as a repo secret, push, and you're done. workflow_dispatch is there so you can trigger a test run by hand from the Actions tab instead of waiting for the cron.

Where to take it from here

  • Filter by beat with category (e.g. client.news.today({ category: "artificial-intelligence" })) if your team only wants AI news, not space and health too.
  • Post in Arabic by passing locale: "ar". The API and SDK are bilingual by default.
  • If you'd rather not run a script at all, NewTqnia also has a drop-in embeddable widget for putting the same feed directly on a webpage instead of a chat channel.

Full API reference and the PHP SDK are on the developer page if you want to build something more involved than a digest bot.

Top comments (0)