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],
              });
            }

Scitor β€” Turn GitHub into your support platform