How to Create Automated Client Progress Report for Remote Projects
Automated client progress reports aggregate task completion, sprint metrics, and timeline data without manual compilation, saving hours weekly. You can script reports from Linear, Jira, or GitHub APIs, format them as PDF or email, and schedule weekly/monthly delivery. This guide covers reporting pipeline architecture, template examples, and integrations for remote project teams.
Understanding the Reporting Pipeline
An automated reporting system consists of three core components:
- Data Collection — Gathering metrics from your project management tools, version control, and CI/CD systems
- Template Processing — Injecting data into a structured report format
- Delivery Mechanism — Sending the completed report via email, Slack, or webhook
For a typical remote project, you’ll pull data from sources like GitHub issues, Jira tickets, Linear boards, or Trello. The automation layer then compiles this into a human-readable format.
Choosing the Right Data Sources
Before writing a single line of automation code, decide which data sources will power your reports. The choice depends on your team’s toolstack and what your clients actually care about. Here is a comparison of the most common sources:
| Tool | Best For | API Quality | Free Tier |
|---|---|---|---|
| GitHub | Engineering teams, open-source | Excellent | Yes |
| Jira | Large enterprise projects | Good | Limited |
| Linear | Modern engineering teams | Excellent | Yes |
| Trello | Simple project boards | Good | Yes |
| Asana | Cross-functional teams | Good | Yes |
For most remote development agencies, GitHub covers the majority of client-facing metrics: what shipped, what is in progress, and what is blocking progress. If your team also uses a project management layer like Linear, pull from both and merge the data in your template.
Building the Data Collection Layer
Start by identifying which metrics matter to your clients. Common choices include:
- Completed tasks and pull requests
- Sprint velocity and burndown trends
- Bug resolution rates
- Upcoming milestones and deadlines
- Blockers and risks requiring attention
Here’s a Python script that collects data from GitHub and formats it for reporting:
import os
import requests
from datetime import datetime, timedelta
from github import Github
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
REPO_OWNER = "your-org"
REPO_NAME = "your-project"
g = Github(GITHUB_TOKEN)
repo = g.get_repo(f"{REPO_OWNER}/{REPO_NAME}")
def get_completed_prs(days=7):
"""Fetch merged PRs from the past week."""
since = datetime.now() - timedelta(days=days)
closed_prs = repo.get_pulls(state="closed", sort="updated", direction="desc")
completed = []
for pr in closed_prs:
if pr.merged_at and pr.merged_at >= since:
completed.append({
"title": pr.title,
"number": pr.number,
"author": pr.user.login,
"merged_at": pr.merged_at.strftime("%Y-%m-%d")
})
return completed
def get_open_issues():
"""Fetch open issues excluding pull requests."""
issues = repo.get_issues(state="open", sort="updated", direction="desc")
return [{"title": i.title, "number": i.number, "labels": [l.name for l in i.labels]}
for i in issues if not i.pull_request][:10]
This script fetches merged pull requests from the past week and lists current open issues. You can extend it to include commits, milestones, or any other GitHub API data relevant to your client.
Pulling Data from Linear
If your team tracks work in Linear alongside GitHub, use the Linear GraphQL API to include issue completion data. Linear’s API is particularly clean and well-documented:
import requests
LINEAR_API_KEY = os.environ.get("LINEAR_API_KEY")
LINEAR_TEAM_ID = "your-team-id"
def get_linear_completed_issues(days=7):
"""Fetch completed Linear issues from the past week."""
since = (datetime.now() - timedelta(days=days)).isoformat()
query = """
query CompletedIssues($teamId: String!, $since: DateTime!) {
issues(
filter: {
team: { id: { eq: $teamId } }
completedAt: { gte: $since }
state: { type: { eq: "completed" } }
}
) {
nodes {
title
identifier
assignee { name }
completedAt
estimate
}
}
}
"""
response = requests.post(
"https://api.linear.app/graphql",
json={"query": query, "variables": {"teamId": LINEAR_TEAM_ID, "since": since}},
headers={"Authorization": LINEAR_API_KEY}
)
data = response.json()
return data["data"]["issues"]["nodes"]
Combining Linear issues with GitHub PRs gives clients a complete picture: business-level work items alongside the actual code changes that delivered them.
Creating the Report Template
With data in hand, the next step is formatting it into a readable report. Markdown works well because it converts cleanly to HTML, PDF, or plain text depending on your delivery method.
def generate_report(completed_prs, open_issues, milestone_info):
"""Generate a markdown report from project data."""
report = []
report.append("# Project Progress Report")
report.append(f"**Report Date:** {datetime.now().strftime('%Y-%m-%d')}\n")
report.append("## Completed This Week")
if completed_prs:
for pr in completed_prs:
report.append(f"- #{pr['number']}: {pr['title']} (merged {pr['merged_at']})")
else:
report.append("- No pull requests merged this week.")
report.append("\n## Active Issues")
if open_issues:
for issue in open_issues:
labels = f"[{', '.join(issue['labels'])}]" if issue['labels'] else ""
report.append(f"- #{issue['number']}: {issue['title']} {labels}")
else:
report.append("- No open issues.")
report.append("\n## Milestones")
if milestone_info:
for m in milestone_info:
report.append(f"- **{m['title']}**: {m['progress']}% complete (due {m['due_date']})")
return "\n".join(report)
This generates a clean, scannable report that highlights what shipped, what’s in progress, and where milestones stand.
Automating Delivery
The final piece is scheduling and delivering the report. For email delivery, you can use a simple SMTP approach:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_email_report(report_content, recipients):
"""Send report via email."""
msg = MIMEMultipart()
msg["Subject"] = f"Project Progress Report - {datetime.now().strftime('%Y-%m-%d')}"
msg["From"] = "project-reports@yourcompany.com"
msg["To"] = ", ".join(recipients)
msg.attach(MIMEText(report_content, "plain"))
with smtplib.SMTP("smtp.yourprovider.com", 587) as server:
server.starttls()
server.login(os.environ.get("SMTP_USER"), os.environ.get("SMTP_PASS"))
server.send_message(msg)
For Slack integration, use the incoming webhook approach:
import json
def send_slack_report(report_content, webhook_url):
"""Send report to Slack channel."""
payload = {
"text": f"*Project Progress Report - {datetime.now().strftime('%Y-%m-%d')}*",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": report_content
}
}
]
}
requests.post(webhook_url, data=json.dumps(payload),
headers={"Content-Type": "application/json"})
Scheduling the Automation
With the scripts in place, schedule them using cron for continuous delivery. A typical setup runs weekly:
# Run every Monday at 9 AM
0 9 * * 1 /usr/bin/python3 /path/to/generate_report.py >> /var/log/project-reports.log 2>&1
For containerized environments, GitHub Actions provides a clean scheduling mechanism:
name: Weekly Progress Report
on:
schedule:
- cron: '0 9 * * 1'
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run report generator
run: python generate_report.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
Report Format Options: Choosing What Works for Each Client
Not every client wants a plain-text email. Tailor your delivery format based on the client’s preferences and technical comfort level.
| Format | Best For | Tooling Required |
|---|---|---|
| Plain-text email | Non-technical executives | SMTP only |
| HTML email | Most clients | Jinja2 + SMTP |
| PDF attachment | Compliance-heavy clients | WeasyPrint or ReportLab |
| Slack message | Technical teams | Slack Webhooks |
| Notion page update | Teams using Notion | Notion API |
For HTML emails, render your Markdown to HTML using Python’s markdown library before sending. This produces professional-looking reports without requiring a dedicated email platform.
Enhancing Reports with Additional Context
Basic metrics tell part of the story. Consider adding:
- Highlights Section: One or two accomplishments worth calling out
- Blockers: Current impediments affecting progress
- Next Week Priorities: Planned work for the upcoming period
- Risk Items: Potential issues to monitor
You can gather this context through structured conventions like a weekly standup bot that collects status updates, or by pulling from a dedicated “status” label in your issue tracker.
A practical approach is to maintain a report-notes.md file in the repository that team members update throughout the week. Your automation script reads this file at report generation time and appends it as the “Highlights” section. This keeps qualitative context attached to quantitative metrics without requiring any additional tooling.
Security and Access Considerations
When automating client reports, keep these best practices in mind:
- Limit Exposed Data: Only include information the client should see
- Use Environment Variables: Never hardcode API tokens or credentials
- Audit Logs: Track report generation and delivery for troubleshooting
- Opt-Out Mechanism: Allow clients to pause or adjust report frequency
For multi-client setups, use a configuration file per client that specifies their repository, recipients, and preferred format. This prevents accidental data cross-contamination between clients and makes it easy to onboard new accounts without modifying the core script.
Measuring Report Effectiveness
Track whether your automated reports achieve their purpose:
- Client engagement with reports (email opens, Slack message views)
- Reduction in status meeting requests
- Client satisfaction with project visibility
- Time saved compared to manual reporting
Adjust your template and delivery frequency based on feedback. The goal is consistent, valuable communication—not information overload. If a client starts ignoring reports within a few weeks, that is a signal to shorten the format, increase the signal-to-noise ratio, or shift to a different delivery channel rather than continuing to send reports nobody reads.
Building an automated client progress reporting system requires upfront development time but pays dividends through consistent stakeholder communication. Start with simple metrics and expand as you identify what matters most to your clients.
Related Articles
- macOS: Screen recording permission is required
- How to Create Remote Team Compensation Benchmarking Report
- Example: Create a booking via API
- How to Create Asynchronous Client Update Format for Remote P
- How to Create Client Communication Charter for Remote
Built by theluckystrike — More at zovo.one