GitHub Actions Integration
Scitorβs automatic labels make it easy to build powerful automations with GitHub Actions. This guide covers common patterns for automating your support workflow.
Auto-assign based on category
Automatically assign team members based on the type of support request:
name: Auto-assign support tickets
on:
issues:
types: [labeled]
jobs:
assign:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const label = context.payload.label.name;
const assignments = {
'category:bug-report': ['dev-team-lead'],
'category:billing': ['billing-team'],
'category:account': ['account-manager'],
'category:feature-request': ['product-manager'],
};
const assignees = assignments[label];
if (assignees) {
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
assignees,
});
}
Auto-close spam
Automatically close issues with high spam scores:
name: Auto-close spam
on:
issues:
types: [labeled]
jobs:
close-spam:
if: github.event.label.name == 'spam:high'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed',
state_reason: 'not_planned',
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: 'π€ Automatically closed β high spam score detected.',
});
Slack notification for urgent issues
Send a Slack notification when a frustrated customer reports a bug:
name: Urgent issue alert
on:
issues:
types: [labeled]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Check if urgent
id: check
uses: actions/github-script@v7
with:
script: |
const labels = context.payload.issue.labels.map(l => l.name);
const isNegative = labels.includes('sentiment:negative');
const isBug = labels.includes('category:bug-report');
core.setOutput('urgent', isNegative && isBug ? 'true' : 'false');
- name: Send Slack notification
if: steps.check.outputs.urgent == 'true'
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_WEBHOOK }}
webhook-type: incoming-webhook
payload: |
{
"text": "π¨ Urgent support issue",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*π¨ Urgent: Frustrated customer bug report*\n<${{ github.event.issue.html_url }}|${{ github.event.issue.title }}>"
}
}
]
}
SLA tracking
Add a label if an issue hasnβt been responded to within a time period:
name: SLA check
on:
schedule:
- cron: '0 */4 * * *' # Every 4 hours
jobs:
check-sla:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'spam:clean',
sort: 'created',
direction: 'asc',
per_page: 50,
});
const fourHoursAgo = new Date(Date.now() - 4 * 60 * 60 * 1000);
for (const issue of issues.data) {
if (new Date(issue.created_at) < fourHoursAgo && issue.comments === 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['sla:breached'],
});
}
}
Tip
These are starting points β customize the logic, team members, and thresholds to match your teamβs workflow. GitHub Actions gives you full flexibility to build exactly the automation you need.
Auto-acknowledge inbound emails
Send an automatic thank-you reply to every non-spam inbound email. The workflow triggers when Scitor labels the issue spam:clean, posts a /send comment, and Scitor emails it back to the customer:
name: Auto-acknowledge inbound emails
on:
issues:
types: [labeled]
jobs:
acknowledge:
# Only reply to non-spam issues created by Scitor
if: >
github.event.label.name == 'spam:clean' &&
github.event.issue.user.login == 'scitor-customerops[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
// Avoid double-replying if labels are re-applied
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 5,
});
const alreadyReplied = comments.data.some(
c => c.user.login === 'github-actions[bot]' && c.body.includes('/send')
);
if (alreadyReplied) return;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: [
'/send',
'',
'Thank you for reaching out! We have received your message and a member of our team will get back to you as soon as possible.',
'',
'In the meantime, you may find answers in our documentation at [support.scitor.io](https://support.scitor.io).',
'',
'Kind regards,',
'The Support Team',
].join('\n'),
});
Tip
This triggers on spam:clean only. If you also want to acknowledge spam:low emails, change the condition to check both:
if: >
(github.event.label.name == 'spam:clean' || github.event.label.name == 'spam:low') &&
github.event.issue.user.login == 'scitor-customerops[bot]'
The duplicate-check ensures the email isnβt sent twice if labels are reapplied.
Auto-reply to common questions
Send an automated first response for questions. The /send command tells Scitor to email the reply back to the original sender:
name: Auto-reply to questions
on:
issues:
types: [labeled]
jobs:
auto-reply:
if: github.event.label.name == 'category:question'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: [
"/send",
"",
"π Thanks for reaching out! We've received your question and will get back to you shortly.",
"",
"In the meantime, you might find the answer in our documentation:",
"- [Getting Started](https://docs.scitor.io/your-org/support/getting-started/)",
"- [FAQ](https://docs.scitor.io/your-org/support/faq/)",
"",
"Our team typically responds within 4 business hours."
].join('\n'),
});
Tip
The /send command at the start of the comment tells Scitor to email the message to the original sender. Itβs automatically stripped from the email body. This works from GitHub Actions workflows β Scitor recognizes commands from github-actions[bot].
Escalate billing issues
Automatically add priority labels and notify a specific team when billing or account issues come from frustrated customers:
name: Escalate billing issues
on:
issues:
types: [labeled]
jobs:
escalate:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const labels = context.payload.issue.labels.map(l => l.name);
const isBilling = labels.includes('category:billing') || labels.includes('category:account');
const isNegative = labels.includes('sentiment:negative');
if (isBilling && isNegative) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['priority:high'],
});
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
assignees: ['billing-lead'],
});
}
Add to GitHub Projects board
Automatically add new support issues to a GitHub Projects board and set the status column based on category:
name: Add to support board
on:
issues:
types: [opened]
jobs:
add-to-project:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const projectNumber = 1; // Your project number
const org = context.repo.owner;
// Get project ID
const project = await github.graphql(`
query($org: String!, $number: Int!) {
organization(login: $org) {
projectV2(number: $number) {
id
}
}
}
`, { org, number: projectNumber });
// Add issue to project
await github.graphql(`
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: {
projectId: $projectId,
contentId: $contentId
}) {
item { id }
}
}
`, {
projectId: project.organization.projectV2.id,
contentId: context.payload.issue.node_id,
});
Weekly support digest
Generate a weekly summary of support activity and post it as a discussion or issue:
name: Weekly support digest
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9 AM
jobs:
digest:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
since: oneWeekAgo,
state: 'all',
per_page: 100,
});
const opened = issues.data.filter(i => !i.pull_request && new Date(i.created_at) > new Date(oneWeekAgo));
const closed = issues.data.filter(i => !i.pull_request && i.state === 'closed');
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 categorized issues';
const sentimentLines = Object.entries(bySentiment)
.sort((a, b) => b[1] - a[1])
.map(([k, v]) => `- ${k}: ${v}`)
.join('\n') || '- No sentiment data';
const body = [
`# π Weekly Support Digest`,
`*${new Date(oneWeekAgo).toLocaleDateString()} β ${new Date().toLocaleDateString()}*`,
``,
`## Overview`,
`- **New issues:** ${opened.length}`,
`- **Closed issues:** ${closed.length}`,
`- **Open rate:** ${opened.length > 0 ? Math.round(closed.length / opened.length * 100) : 0}% resolved`,
``,
`## By category`,
categoryLines,
``,
`## By sentiment`,
sentimentLines,
].join('\n');
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `π Weekly Support Digest β ${new Date().toLocaleDateString()}`,
body,
labels: ['digest'],
});
Auto-block spam and close
Automatically block the sender and close the issue when Scitor labels it as high spam:
name: Auto-block spam
on:
issues:
types: [labeled]
jobs:
block-spam:
if: github.event.label.name == 'spam:high'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
// Block the sender using Scitor's /block-sender command
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: '/block-sender',
});
// Close the issue
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed',
state_reason: 'not_planned',
});
Apply priority labels by sentiment
Add priority labels based on the combination of sentiment and category:
name: Priority labels
on:
issues:
types: [labeled]
jobs:
prioritize:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const labels = context.payload.issue.labels.map(l => l.name);
// Determine priority based on label combinations
let priority = null;
if (labels.includes('sentiment:negative') && labels.includes('category:bug-report')) {
priority = 'priority:critical';
} else if (labels.includes('sentiment:negative')) {
priority = 'priority:high';
} else if (labels.includes('category:bug-report')) {
priority = 'priority:medium';
} else if (labels.includes('category:feature-request')) {
priority = 'priority:low';
}
if (priority) {
// Remove any existing priority labels first
for (const label of labels) {
if (label.startsWith('priority:')) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: label,
}).catch(() => {}); // Ignore if already removed
}
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [priority],
});
}