GitHub Actions recipe · Works on every plan
Post a weekly support digest as a GitHub Issue
Every Monday, a scheduled workflow counts the week's new and closed tickets and files the summary as a fresh Issue — no dashboard to check, no manual report to write.
What this workflow does
It runs on a cron schedule (Monday 9am UTC by default — edit the cron line to change it), reads every issue updated in the last 7 days, and breaks the new ones down by category:* and sentiment:* labels before filing the summary as a new Issue labeled digest.
What it does not do
The category and sentiment breakdown only has data if AI triage is enabled (Pro plan) — on Free, those two sections read "No data" but the new/closed counts still work. This is also not Scitor's own built-in /generate-report command, which produces a richer, chart-based monthly report inside the product itself; this recipe is a simpler, fully editable alternative you own.
The workflow file
Save as .github/workflows/weekly-digest.yml.
# Weekly support digest, posted as a GitHub Issue
#
# Every Monday at 9am UTC, summarizes the past week's tickets — new vs.
# closed, and a breakdown by category and sentiment label — and files it
# as a new GitHub Issue.
#
# What this does:
# - Runs on a schedule, not a ticket event — no label trigger, no
# duplicate-firing risk from re-labeling.
# - Reads every issue updated in the last 7 days via the REST API,
# paginating through all results rather than stopping at the first
# page (a busy repo can have more than 100 matching issues).
# - Excludes the digest's own past issues (the `digest` label) from every
# count, so last week's digest post doesn't get counted as a new or
# closed ticket in this week's numbers.
# - Counts "closed" using each issue's actual `closed_at` timestamp, not
# just "state is currently closed" — an issue closed a month ago that
# merely got a comment this week would otherwise be miscounted as
# closed this week.
# - Counts labels prefixed `category:` and `sentiment:` — these only
# exist if AI triage (Pro plan) is enabled; on the Free plan the
# digest still posts, but those two sections read "No data".
# - Creates the `digest` label on first run if it doesn't already exist
# in the repo, so this doesn't fail on a fresh install.
#
# What this does NOT do:
# - It is not Scitor's built-in `/generate-report` command (that's a
# richer, chart-based monthly report available in the product itself —
# see support.scitor.io/features). This is a simpler, fully custom
# digest you own and can edit freely.
# - It does not post to Slack by itself — combine it with the Slack
# recipe/integration if you want the digest to also land in a channel.
#
# No secrets required, but it does write: it reads issues in the repo it
# runs in and creates one new "digest" Issue each run, using the
# workflow's default GITHUB_TOKEN with `issues: write` permission.
#
# Source: adapted from Scitor's own documented example at
# https://support.scitor.io/guides/github-actions#weekly-support-digest
name: Weekly support digest
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am UTC
jobs:
digest:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const oneWeekAgoDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const oneWeekAgo = oneWeekAgoDate.toISOString();
// Paginate through every matching issue — a busy repo can easily
// have more than one page (100) of issues updated in a week.
const allIssues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
since: oneWeekAgo,
state: 'all',
per_page: 100,
});
// Exclude the digest's own past posts from every count, and skip PRs.
const issues = allIssues.filter((i) => !i.pull_request && !i.labels.some((l) => (typeof l === 'string' ? l : l.name) === 'digest'));
const opened = issues.filter((i) => new Date(i.created_at) > oneWeekAgoDate);
const closed = issues.filter((i) => i.closed_at && new Date(i.closed_at) > oneWeekAgoDate);
const byCategory = {};
const bySentiment = {};
for (const issue of opened) {
for (const label of issue.labels) {
const name = typeof label === 'string' ? label : label.name;
if (name.startsWith('category:')) byCategory[name] = (byCategory[name] || 0) + 1;
if (name.startsWith('sentiment:')) bySentiment[name] = (bySentiment[name] || 0) + 1;
}
}
const categoryLines = Object.entries(byCategory)
.sort((a, b) => b[1] - a[1])
.map(([k, v]) => `- ${k}: ${v}`)
.join('\n') || '- No data (AI triage not enabled, or no matching tickets)';
const sentimentLines = Object.entries(bySentiment)
.sort((a, b) => b[1] - a[1])
.map(([k, v]) => `- ${k}: ${v}`)
.join('\n') || '- No data (AI triage not enabled, or no matching tickets)';
const body = [
'# Weekly Support Digest',
`${new Date(oneWeekAgo).toLocaleDateString()} — ${new Date().toLocaleDateString()}`,
'',
'## Overview',
`- New tickets: ${opened.length}`,
`- Closed tickets: ${closed.length}`,
'',
'## By category',
categoryLines,
'',
'## By sentiment',
sentimentLines,
].join('\n');
// Create the label explicitly first — don't assume it already
// exists in the repo (e.g. on a fresh install).
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'digest',
color: '5319e7',
description: 'Weekly support digest posts',
});
} catch (err) {
if (err.status !== 422) throw err; // 422 = label already exists
}
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Weekly Support Digest — ${new Date().toLocaleDateString()}`,
body,
labels: ['digest'],
}); No secrets required, but it does write: it creates one new "digest" Issue each run.
Know your week's support load, automatically
Takes under 5 minutes. Free tier available. No credit card required.