Loading

CyberOak Automation Hub

Production-grade scripts, orchestration walkthroughs, and developer updates.

⭐ Featured Blueprint • Data Automation

Turn Any Python Script into an Automated PDF Report — Emailed on Schedule (with WeasyPrint)

Skip the "can you send me the report" Slack messages. Learn how to generate a styled PDF from your data with WeasyPrint and email it automatically using smtplib — then schedule it to run and land in inboxes on its own with CyberOak.

preview.py
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from weasyprint import HTML

# Email and report parameters are supplied by CyberOak's
# Key-Value Parameters section.
SMTP_HOST = SMTP_HOST
SMTP_PORT = SMTP_PORT
SMTP_USER = SMTP_USER
SMTP_PASSWORD = SMTP_PASSWORD
REPORT_RECIPIENT = REPORT_RECIPIENT


def build_html_report(rows: list[dict], title: str) -> str:
    table_rows = "".join(
        f"<tr><td>{r['name']}</td><td>{r['value']}</td></tr>"
        for r in rows
    )

    return f"""
    <html>
    <head>
        <style>
            @page {{
                size: A4;
                margin: 2cm;
                @bottom-center {{
                    content: "Generated automatically by CyberOak";
                    font-size: 9px;
                    color: #999;
                }}
            }}
            body {{ font-family: Helvetica, sans-serif; }}
            h1 {{ color: #212832; }}
            table {{ width: 100%; border-collapse: collapse; }}
            th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
            th {{ background-color: #00ba94; color: white; }}
        </style>
    </head>
    <body>
        <h1>{title}</h1>
        <table>
            <tr><th>Metric</th><th>Value</th></tr>
            {table_rows}
        </table>
    </body>
    </html>
    """


def html_to_pdf(html_content: str, output_path: str):
    HTML(string=html_content).write_pdf(output_path)


def send_email_with_attachment(
    subject: str,
    body: str,
    attachment_path: str,
    recipient: str,
):
    message = MIMEMultipart()
    message["From"] = SMTP_USER
    message["To"] = recipient
    message["Subject"] = subject
    message.attach(MIMEText(body, "plain"))

    with open(attachment_path, "rb") as f:
        part = MIMEBase("application", "octet-stream")
        part.set_payload(f.read())
        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename={attachment_path}",
        )
        message.attach(part)

    print("Connecting to SMTP server...")
    with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
        server.starttls()
        server.login(SMTP_USER, SMTP_PASSWORD)
        server.sendmail(SMTP_USER, recipient, message.as_string())

    print(f"Report emailed to {recipient}")


# Example usage
if __name__ == "__main__":
    report_data = [
        {"name": "New Signups", "value": 128},
        {"name": "Active Trials", "value": 342},
        {"name": "Churned Accounts", "value": 7},
    ]

    html = build_html_report(report_data, "Weekly Growth Report")
    pdf_path = "weekly_growth_report.pdf"

    print("Generating PDF report...")
    html_to_pdf(html, pdf_path)

    send_email_with_attachment(
        subject="Weekly Growth Report",
        body="Attached is this week's automated growth report.",
        attachment_path=pdf_path,
        recipient=REPORT_RECIPIENT,
    )
Ken Muriithi
Sep 23, 2026 • 5 min read
Read Full Article →

Latest Articles

RPA / Automation • 2 min read

Scaling Intelligent Document Processing: Building an Automated PDF Splitter Robot

Large PDFs stall enterprise workflows and downstream ML pipelines. Learn how to build a high-performance, containerized RPA worker to slice bulk documents into modular chunks automatically.

Jul 09, 2026 Read Full Article →