MVP Python project that fetches GitHub Project v2 items with GraphQL, applies a small local filter, renders the results through Jinja2, writes the output to STDOUT, and can optionally send per-user emails through SMTP. The tool supports built-in fan-out so a single invocation can generate separate digests for multiple configured GitHub users.
.env.gql with the requests transport to call the GitHub GraphQL API.GITHUB_USER entry that includes an email address.read:project for queries. Depending on token type, App permissions, and org policy, you may also need repository read access for private issue repositories.python -m venv .venv
. .venv/bin/activate
pip install -e .
cp .env.example .env
Edit .env.
# Option 1: direct token/PAT. This takes precedence when set.
GITHUB_TOKEN=replace_me
# Option 2: GitHub App authentication. Used only when GITHUB_TOKEN is empty.
GITHUB_APP_ID=
GITHUB_APP_INSTALLATION_ID=
GITHUB_APP_PRIVATE_KEY=
GITHUB_APP_PRIVATE_KEY_FILE=
GITHUB_PROJECT_OWNER=octo-org
GITHUB_PROJECT_NUMBER=5
GITHUB_PROJECT_OWNER_TYPE=organization
# GITHUB_USER may be @me, a login, login:email, or a comma-separated fan-out list.
GITHUB_USER=@me
GITHUB_PROJECT_FILTER=sprint:@current assignee:@user is:issue state:open
DIGEST_OUTPUT_FORMAT=text
DUE_SOON_DAYS=2
DUE_UPCOMING_DAYS=7
SEND_EMPTY_EMAIL=true
For a user-owned Project, set:
GITHUB_PROJECT_OWNER_TYPE=user
github-project-digest
Or:
python -m github_project_digest.cli
Set DIGEST_OUTPUT_FORMAT to one of:
texthtmljsonyamltext and html use these templates:
templates/digest.txt.j2
templates/digest.html.j2
The tool supports two GitHub authentication modes.
The simplest mode is a direct token/PAT:
GITHUB_TOKEN=replace_me
For scheduled automation, you can instead use a GitHub App installation. Leave GITHUB_TOKEN empty and provide App credentials:
GITHUB_TOKEN=
GITHUB_APP_ID=123456
GITHUB_APP_INSTALLATION_ID=98765432
GITHUB_APP_PRIVATE_KEY_FILE=/run/secrets/github-app.pem
You may also provide the PEM text directly through GITHUB_APP_PRIVATE_KEY, which is useful for CI systems that store secrets as text. Literal \n sequences are converted to real newlines before signing the JWT.
If both are set, GITHUB_TOKEN wins. The GitHub App mode generates a short-lived installation token and then uses the same GraphQL code path as PAT mode.
By default, digests are written to STDOUT only. To also send a digest by email, add an email address to a GITHUB_USER entry using the username:[email protected] form and provide the required SMTP settings.
Each configured GITHUB_USER entry may include its own email address. When present, the application renders and delivers a separate digest for that user.
Examples:
GITHUB_USER=octocat:[email protected]
GITHUB_USER=octocat:[email protected],hubot:[email protected]
GITHUB_USER=@me,hubot:[email protected]
Each email is sent as a multipart message using both templates:
templates/digest.txt.j2 for text/plaintemplates/digest.html.j2 for text/htmlFor Gmail SMTP, use smtp.gmail.com, port 587, SMTP_USE_TLS=true, and a Gmail app password. Do not use your normal Google account password.
If a GITHUB_USER entry does not include an email address, no email is sent for that user. STDOUT output still follows DIGEST_OUTPUT_FORMAT, which keeps local testing and GitHub Actions logging useful.
SEND_EMPTY_EMAIL controls SMTP delivery when a configured user's filtered digest contains zero matching issues.
SEND_EMPTY_EMAIL=true
With the default value of true, the application preserves the original behavior and sends email even when no issues match the configured filters. This can be useful when a scheduled report should confirm that there is no work to report.
SEND_EMPTY_EMAIL=false
When set to false, SMTP delivery is suppressed for empty digests. STDOUT output is still generated, so local runs, GitHub Actions logs, Jenkins logs, and container logs remain useful for confirming that the digest ran successfully.
This setting affects only SMTP delivery. It does not change filtering, digest rendering, JSON output, YAML output, or STDOUT behavior.
Set GITHUB_USER to the GitHub login whose assigned issues should be included. It defaults to @me, which resolves to the authenticated token owner through GraphQL. To send email, append a destination address after a colon.
# Current authenticated user
GITHUB_USER=@me
# Specific GitHub user
GITHUB_USER=octocat
# Specific GitHub user with email delivery
GITHUB_USER=octocat:[email protected]
# Multiple GitHub users
GITHUB_USER=octocat,hubot
# Multiple GitHub users with email delivery
GITHUB_USER=octocat:[email protected],hubot:[email protected]
# Mixed forms
GITHUB_USER=@me,hubot:[email protected]
The ordinary shell USER variable is intentionally ignored so local shells and GitHub Actions runners cannot accidentally change the digest assignee.
The Project item query accepts the resolved assignee login as the GraphQL variable $assigneeLogin. GitHub Project v2 does not expose the same full filter syntax as the Project UI through this query, so this script still applies the supported MVP filter locally after fetching the Project items.
GITHUB_USER may contain a comma-separated list of user specifications.
Each entry is processed independently.
For every configured user, the application:
SEND_EMPTY_EMAIL.This preserves the existing one-user-per-digest model while allowing a single invocation to generate multiple digests.
Examples:
GITHUB_USER=octocat,hubot
GITHUB_USER=octocat:[email protected],hubot:[email protected]
Whitespace around commas is ignored.
Empty entries are rejected:
# invalid
GITHUB_USER=octocat,,hubot
For text and HTML output, multiple digests are separated in STDOUT.
For JSON and YAML output, multiple digests are emitted inside a top-level digests collection.
Although github-project-digest was originally built to provide individual contributors with a daily reminder of work assigned to them, many teams use GitHub Projects as a lightweight planning and coordination system. The digest can provide value to several different audiences depending on how it is configured.
A developer can receive a daily reminder of open work assigned to them in the current sprint:
GITHUB_USER=@me
GITHUB_PROJECT_FILTER=sprint:@current assignee:@user is:issue state:open
This configuration can help answer:
A Product Owner or Project Manager may want visibility into the overall state of a sprint rather than only their own assignments. Removing the assignee filter produces a broader project-status digest:
GITHUB_USER=project-manager
GITHUB_PROJECT_FILTER=sprint:@current is:issue state:open
This configuration can help highlight:
Many teams schedule this digest to arrive each morning before stand-up meetings, backlog refinement, sprint planning, or stakeholder check-ins.
A technical lead may prefer a team-wide operational view that includes issues and pull requests in the current sprint:
GITHUB_USER=tech-lead
GITHUB_PROJECT_FILTER=sprint:@current state:open
This configuration can help identify:
The digest serves as an early warning system that helps leaders focus attention where it is most needed.
The current implementation focuses on GitHub Project items. The same digest model could potentially be extended in the future to support additional GitHub workflows, such as:
Those capabilities are not currently implemented as standalone digest sources, but the existing normalization, filtering, rendering, and delivery pipeline provides a natural foundation for them.
This is not a full clone of GitHub Project UI search syntax. It supports only:
sprint:@currentiteration:@currentassignee:@user or assignee:@me, both resolving to the configured GITHUB_USER valueassignee:<login>user:@user or user:@me as aliases for assignee:@useruser:<login> as an alias for assignee:<login>is:issueis:pris:pullrequeststate:openstate:closedstatus:open as an alias for state:openstatus:closed as an alias for state:closedThe default is:
sprint:@current assignee:@user is:issue state:open
The GitHub Projects UI filter syntax is not passed directly to GraphQL. This project fetches Project items and applies the supported subset locally.
The sprint:@current filter checks Project iteration fields named Sprint or Iteration. It treats an iteration as current when today's date falls between startDate inclusive and startDate + duration exclusive.
Rendered digests group matching issues into these sections:
Status is BlockedStatus is In ProgressStatus is Open, or no more specific section appliesStatus is Done, or the GitHub issue state is closedWithin each section, issues with a Due Date field sort before issues without one. Issues are then sorted by due date, followed by title.
Date markers:
The future due-date marker thresholds are configurable at runtime:
DUE_SOON_DAYS=2
DUE_UPCOMING_DAYS=7
DUE_SOON_DAYS controls the final day that uses the warning marker. With the default value of 2, issues due in 1 or 2 days use ⚠️.
DUE_UPCOMING_DAYS controls the final day that uses the calendar marker. With the default value of 7, issues due in 3 through 7 days use 📅, and issues due in more than 7 days use 💤.
For example, this configuration widens the warning and upcoming windows:
DUE_SOON_DAYS=5
DUE_UPCOMING_DAYS=14
With that configuration, issues due in 1-5 days use ⚠️, issues due in 6-14 days use 📅, and issues due in more than 14 days use 💤.
DUE_SOON_DAYS must be greater than or equal to 0, and DUE_UPCOMING_DAYS must be greater than or equal to DUE_SOON_DAYS.
If your filter includes state:open or status:open, closed issues will normally be excluded before the Closed section is built. Remove that filter term if you want the digest to include closed/completed items.
The repository includes a Makefile that wraps common local development tasks. The default target is help, so running make or make help shows the available commands.
make
make help
Create the local virtual environment and install dependencies:
make venv
make deps
make dev-deps
Run formatting, checks, and tests:
make format
make check
make test
make all
make all runs the local quality workflow: dependency installation, development dependency installation, formatting, checks, and tests. It does not install the console script into either ~/.local/bin or /usr/local/bin.
Install the generated console script explicitly when needed:
make install
make system-install
make install copies the virtual-environment-generated github-project-digest script into ~/.local/bin. make system-install copies it into /usr/local/bin and may require elevated permissions depending on the local system.
Install the project with the development extras and run pytest:
python -m pip install -e '.[dev]'
PYTHONPATH=src pytest -q
Or use the Makefile wrapper:
make dev-deps
make test
The tests cover configuration loading, configured-user parsing, GitHub App token generation, filter parsing, local filtering, Project item normalization, digest grouping and sorting, text and HTML rendering, fan-out output aggregation, SMTP message construction, empty digest email delivery decisions, and GitHub client pagination behavior using mocked GraphQL responses.
Build the image:
podman build -t github-project-digest .
# or
# docker build -t github-project-digest .
Run with environment variables from a local .env file:
podman run --rm --env-file .env github-project-digest
# or
# docker run --rm --env-file .env github-project-digest
Run for a specific GitHub assignee and destination email:
podman run --rm \
--env-file .env \
-e GITHUB_USER='wesley-dean:[email protected]' \
github-project-digest
Run for multiple users in a single invocation:
podman run --rm \
--env-file .env \
-e GITHUB_USER='wesley-dean:[email protected],joe-dean:[email protected]' \
github-project-digest
The application performs fan-out internally and generates a separate digest for each configured user. Shell loops are no longer required for common multi-user use cases.
The image entrypoint is github-project-digest, so command-line arguments are not required. The container intentionally does not include your .env file; pass secrets at runtime with --env-file, individual -e values, or your CI/CD secret mechanism.
This repository includes a workflow at .github/workflows/tests.yml that runs the pytest suite on pushes, pull requests, and manual dispatches. The workflow tests against Python 3.11 and 3.12.
Content type
Image
Digest
sha256:0353461e8…
Size
54.1 MB
Last updated
3 months ago
docker pull wesleydean/github-project-digest