Tutorial

How to automate a weekly Jira to Slack report

· Evgeny Goncharov, building TechConcepts

Direct answer. A weekly Jira to Slack report runs one JQL query for the previous calendar week and posts the result to a channel. Jira Automation plus an incoming webhook is enough for a fixed message. Use the Jira search API and chat.postMessage when you need totals or a bot identity. Keep a person on the commentary.

FAQ

Which JQL is last calendar week, and which is the last 7 days?

updated >= startOfWeek(-1) AND updated < startOfWeek() is the previous calendar week, from the start of last week up to the start of this week. updated >= -7d is a rolling 7 days from the moment the query runs. resolutiondate with the startOfWeek window is issues resolved last calendar week. status CHANGED DURING (startOfWeek(-1), startOfWeek()) is issues whose status changed in that window.

Should the report use a Slack webhook or a bot?

Use an incoming webhook when the message is a fixed post into one channel. Use chat.postMessage with a bot token when you need to choose the channel, post as your app, or thread a reply. The webhook URL is a secret. The bot token needs the chat:write scope.

Does the old Jira search API still work?

On Jira Cloud, /rest/api/3/search is removed. Use POST /rest/api/3/search/jql. Ask for the fields you need, or the issue objects come back without them. Do not send nextPageToken on the first request. Keep calling with the token from the previous response until the property is absent.

What should stay manual in a weekly Jira report?

The counts and the issue list can be posted on a schedule. The commentary a person should still write stays manual: why the queue moved, and what the team will do about it. Every number in the post should be traceable to the JQL that produced it.

When is a Jira Automation rule not enough?

The Lookup work items action uses the first 100 work items. A scheduled JQL search that returns more than 999 work items hits the Cloud automation search limit. Past either cap, run the search API yourself, follow nextPageToken, and post the summary with chat.postMessage or a webhook.

This is the build behind the automated weekly reports offer: one recurring report, from sources you already use, delivered where people read it. If you want it built and handed over, that is a fixed-price Slack bot. The Jira side of the same work sits on Jira optimization.

1. Write JQL for the previous calendar week

Replace OPS with your project key. Run the query in Jira's issue search before you automate it. The functions startOfWeek() and startOfWeek(-1) are Jira's, evaluated in the site timezone when the query runs.

Issues updated last calendar week:

project = OPS
AND updated >= startOfWeek(-1)
AND updated < startOfWeek()
ORDER BY updated DESC

Issues resolved last calendar week. resolutiondate is empty on unresolved issues, so they drop out:

project = OPS
AND resolutiondate >= startOfWeek(-1)
AND resolutiondate < startOfWeek()

Issues whose status changed in that window:

project = OPS
AND status CHANGED DURING (startOfWeek(-1), startOfWeek())

updated >= -7d is not the same query. It is the 7 days before the run, so a Monday 08:00 job includes Sunday through the previous Monday, not the calendar week. Use it only when that is the report you want.

Keep a date bound in the JQL. The enhanced search API rejects an unbounded query.

2. Read the issues with the Jira search API

On Jira Cloud, GET /rest/api/3/search and POST /rest/api/3/search are removed. The replacement is Search for issues using JQL enhanced search: POST /rest/api/3/search/jql. Authenticate with a basic token: the Atlassian account email and an API token. The caller needs Browse projects on every project in the JQL.

Ask for fields. If you omit fields, the issue objects come back without summary, status, or assignee. Omit nextPageToken on the first request. Sending null on a GET has been rejected as an invalid token. Follow the token from each response until the property is absent.

import os
import requests
from requests.auth import HTTPBasicAuth

JQL = (
    "project = OPS AND updated >= startOfWeek(-1) "
    "AND updated < startOfWeek()"
)
auth = HTTPBasicAuth(os.environ["JIRA_EMAIL"], os.environ["JIRA_TOKEN"])
url = os.environ["JIRA_BASE"].rstrip("/") + "/rest/api/3/search/jql"

issues = []
token = None
while True:
    body = {
        "jql": JQL,
        "maxResults": 100,
        "fields": ["summary", "status", "assignee", "priority"],
    }
    if token:
        body["nextPageToken"] = token
    response = requests.post(url, json=body, auth=auth, timeout=30)
    response.raise_for_status()
    data = response.json()
    issues.extend(data.get("issues") or [])
    token = data.get("nextPageToken")
    if not token:
        break

lines = [f"Weekly Jira report: {len(issues)} issues updated last week."]
for issue in issues[:20]:
    fields = issue.get("fields") or {}
    status = (fields.get("status") or {}).get("name") or "—"
    lines.append(f"{issue.get('key')} [{status}] {fields.get('summary') or ''}")
text = "\n".join(lines)

The same call as curl, for a single page:

curl --request POST \
  --url "$JIRA_BASE/rest/api/3/search/jql" \
  --user "$JIRA_EMAIL:$JIRA_TOKEN" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --data '{"jql":"project = OPS AND updated >= startOfWeek(-1) AND updated < startOfWeek()","maxResults":100,"fields":["summary","status","assignee"]}'

Search is not read-after-write consistent. A weekly report of last week's issues does not need the reconcileIssues parameter. Use it only when you must see an edit from the last few seconds.

3. Post to Slack with a webhook or a bot

Two published Slack methods. Pick one.

Incoming webhook

Create the webhook on a Slack app (Incoming webhooks). It posts into the channel you chose when you installed it. The URL is a credential. Do not commit it.

requests.post(
    os.environ["SLACK_WEBHOOK_URL"],
    json={"text": text},
    timeout=30,
).raise_for_status()

The body is JSON with a text field. Slack also accepts blocks if you want a layout. A webhook cannot choose a different channel per run, and it cannot open a thread as a reply to an older report.

Bot: chat.postMessage

Use this when the report should come from your app, or the channel is chosen at runtime. The token is a bot token (xoxb-) with chat:write. Invite the bot to the channel. chat.postMessage takes channel and text.

posted = requests.post(
    "https://slack.com/api/chat.postMessage",
    headers={"Authorization": f"Bearer {os.environ['SLACK_BOT_TOKEN']}"},
    json={"channel": os.environ["SLACK_CHANNEL_ID"], "text": text},
    timeout=30,
)
posted.raise_for_status()
body = posted.json()
if not body.get("ok"):
    raise SystemExit(body.get("error") or "chat.postMessage failed")

Check ok. Slack returns HTTP 200 with "ok": false when the token, the scope, or the channel is wrong. not_in_channel means the bot has not been invited.

A bot that also creates Jira issues from Slack is a different workflow. The notes for that are routing Jira tickets from Slack. Hiring it as one fixed-scope bot is Slack bot development. The broader service page is Slack automation.

4. Schedule it for Monday

Two ways. The rule stays inside Jira. The script runs wherever you already run jobs.

Jira Automation

Create a rule with a Scheduled trigger. In the basic builder, pick every week, on Monday, at the hour you want. The timezone is the one the rule shows. Read the next-run preview before you enable it.

Then add Lookup work items (the action still labeled Lookup issues on some sites) and paste the calendar-week JQL. Add Send web request:

  • URL: the incoming webhook URL.
  • Method: POST.
  • Header: Content-Type: application/json.
  • Body: {"text":"Weekly Jira report: {{lookupIssues.size}} issues updated last week."}

Run the rule once from the rule screen and read the audit log. {{lookupIssues.size}} is the size of that lookup, not a second query.

Cloud automation limits, from Atlassian's service limits: the Lookup work items action uses the first 100 work items. A scheduled JQL search that returns more than 999 work items is over the search limit, and only the first results are processed. A Monday report of one team is usually under both. A site-wide dump of every update is not. Past either cap, use the script and paginate.

Rules that only move fields, labels, or SLA timers are a different catalogue: Jira automation rules. Workforce Management and Rovo do not post this report. Where the step leaves Jira, it is custom, which is the line in Rovo vs custom AI for Jira triage.

A weekly cron outside Jira

Run the Python above on a schedule you control. GitHub Actions uses UTC. 0 7 * * 1 is 07:00 UTC on Monday, which is 08:00 in Madrid in winter and 09:00 in summer. Set the cron for the hour you mean in UTC, or set the timezone on the runner if it has one.

Store JIRA_EMAIL, JIRA_TOKEN, JIRA_BASE, and either SLACK_WEBHOOK_URL or SLACK_BOT_TOKEN plus SLACK_CHANNEL_ID as secrets. Log the count and the HTTP result. Do not log the token or the webhook URL.

What we leave to a person

The homepage offer for this work is one recurring report, delivered to a Slack channel, email, or a shared doc, with every number traceable to its source, plus a short note on what stays manual. The commentary is that note. A bot can post "42 issues updated". It should not invent why.

If the report has to join Jira with a spreadsheet, a CRM, or mail, that is more than one source. The service for that pipeline is multi-system integration. Price and timeline for the weekly-report offer are agreed on the call. They are not listed as a public amount.

Evgeny Goncharov, building TechConcepts

Evgeny Goncharov

Building TechConcepts. Part of Goncharov Group.

We build Slack bots, Jira triage, and weekly reports inside your systems. You own the code. Madrid.

About LinkedIn GitHub
← All articles

Want the report built for you?

One recurring report, fixed scope, you own the code. Price and timeline are agreed on a 20-minute call.

Book a 20-minute call

Slack bot development, fixed price