Metadata-Version: 2.4
Name: reportmailer
Version: 1.4.5
Summary: Generate HTML reports and send them via email with a single function call
Author-email: Prakash R <prakash@reportmailer.dev>
License: MIT
Keywords: report,email,html,automation,data
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=1.5
Requires-Dist: openpyxl>=3.1
Requires-Dist: jinja2>=3.1
Requires-Dist: pyyaml>=6.0
Provides-Extra: db
Requires-Dist: sqlalchemy>=1.4; extra == "db"
Requires-Dist: pymysql>=1.0; extra == "db"
Requires-Dist: psycopg2-binary>=2.9; extra == "db"
Requires-Dist: pyodbc>=5.0; extra == "db"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pytest-mock>=3.10; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: pre-commit>=3.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.5; extra == "docs"
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-cov>=4.0; extra == "test"
Dynamic: license-file

# ReportMailer

ReportMailer is an open-source Python package that enables users to read data from multiple data sources, generate beautiful HTML reports, attach Excel/CSV files, and send emails with a single function call.

```python
from reportmailer import send_report

send_report(
    source=df,
    to="manager@company.com",
    subject="Daily Report",
)
```

---

## Installation

```bash
pip install reportmailer
```

Works with Python 3.10+. Database sources (MySQL, PostgreSQL, SQL Server, SQLite) need extra dependencies:

```bash
pip install "reportmailer[db]"
```

---

## Configuration (sample YAML)

Put your Gmail SMTP settings and database connections in `~/.reportmailer/config.yaml`.
Then `send_report()` picks up both automatically — no credentials in your code.

```yaml
# ~/.reportmailer/config.yaml
profiles:
  gmail:
    smtp:
      host: smtp.gmail.com
      port: 587
      username: your.email@gmail.com
      password: ${GMAIL_APP_PASS}    # Gmail app password (16 chars, no spaces)

default_profile: gmail               # used automatically by send_report()

databases:
  mydb:
    type: mysql                      # mysql | postgres | mssql | sqlite
    host: localhost
    port: 3306
    username: your_mysql_user
    password: ${DB_PASS}
    database: analytics
```

Set the referenced environment variables:

```bash
# Linux / macOS
export GMAIL_APP_PASS="abcd efgh ijkl mnop"
export DB_PASS="your-mysql-password"

# Windows PowerShell
$env:GMAIL_APP_PASS = "abcd efgh ijkl mnop"
$env:DB_PASS = "your-mysql-password"
```

Now use the database by **name** — Gmail SMTP loads automatically from `default_profile`:

```python
from reportmailer import send_report

send_report(
    db="mydb",
    query="SELECT `check`, COUNT(*) AS cnt FROM analytics.test GROUP BY `check`",
    to="manager@company.com",
    subject="Analytics Report",
)
```

> Full reference: [docs/configuration.md](docs/configuration.md) — including multiple
> databases in one report and direct connection strings.

---

## Quick Start

```python
from reportmailer import send_report
import pandas as pd

df = pd.DataFrame({"Name": ["Alice", "Bob"], "Sales": [100, 200]})

send_report(
    source=df,
    to="user@example.com",
    subject="Weekly Report",
)
```

---

## Data Sources

ReportMailer auto-detects the source type. No need to specify it manually.

| Source | Example | Detected By |
|--------|---------|-------------|
| Pandas DataFrame | `source=df` | `isinstance(source, pd.DataFrame)` |
| Excel file | `source="report.xlsx"` | `.xlsx` / `.xls` extension |
| CSV file | `source="data.csv"` | `.csv` extension |
| SQLite file | `source="database.db"` | `.db` / `.sqlite` extension |
| MySQL | `source="mysql+pymysql://user:pass@host/db"` | `mysql+` prefix |
| PostgreSQL | `source="postgresql://user:pass@host/db"` | `postgresql://` prefix |
| SQL Server | `source="mssql+pyodbc://user:pass@host/db"` | `mssql+` prefix |

For database sources, provide the `query` parameter:

```python
send_report(
    source="mysql+pymysql://user:password@localhost/sales_db",
    query="SELECT customer_name, total_sales FROM monthly_sales ORDER BY total_sales DESC",
    to="manager@company.com",
    subject="Monthly Sales Report",
)
```

### Database config via YAML (recommended)

Instead of connection strings in your code, define databases once in
`~/.reportmailer/config.yaml` and pass the **name** (see
[Configuration (sample YAML)](#configuration-sample-yaml)):

```python
send_report(
    db="sales_db",
    query="SELECT customer_name, total_sales FROM monthly_sales",
    to="manager@company.com",
    subject="Monthly Sales Report",
)
```

Requires `pip install "reportmailer[db]"`.

---

## Gmail Configuration (Step by Step)

Gmail requires an **App Password** because it blocks regular passwords for security.

### Step 1: Generate a Gmail App Password

1. Go to https://myaccount.google.com/security
2. Turn on **2-Step Verification** (if not already enabled)
3. Go to https://myaccount.google.com/apppasswords
4. Select app: **Mail** → device: **Other** → name it `ReportMailer`
5. Click **Generate** → copy the **16-character password** (looks like `abcd efgh ijkl mnop`)

### Step 2: Choose a config method

---

**Option A — Direct argument (easiest for testing)**

```python
from reportmailer import send_report
import pandas as pd

df = pd.DataFrame({"Name": ["Alice", "Bob"], "Sales": [100, 200]})

send_report(
    source=df,
    to="your.email@gmail.com",
    subject="Test from ReportMailer",
    smtp={
        "host": "smtp.gmail.com",
        "port": 587,
        "username": "your.email@gmail.com",
        "password": "your-16-char-app-password",  # no spaces
    },
)
```

---

**Option B — Environment variables (good for scripts/servers)**

```bash
# Windows PowerShell
$env:REPORTMAILER_SMTP_HOST="smtp.gmail.com"
$env:REPORTMAILER_SMTP_PORT="587"
$env:REPORTMAILER_SMTP_USERNAME="your.email@gmail.com"
$env:REPORTMAILER_SMTP_PASSWORD="your-16-char-app-password"
```

```bash
# Windows cmd
set REPORTMAILER_SMTP_HOST=smtp.gmail.com
set REPORTMAILER_SMTP_PORT=587
set REPORTMAILER_SMTP_USERNAME=your.email@gmail.com
set REPORTMAILER_SMTP_PASSWORD=your-16-char-app-password
```

```bash
# Linux / macOS
export REPORTMAILER_SMTP_HOST=smtp.gmail.com
export REPORTMAILER_SMTP_PORT=587
export REPORTMAILER_SMTP_USERNAME=your.email@gmail.com
export REPORTMAILER_SMTP_PASSWORD=your-16-char-app-password
```

---

**Option C — CLI profile (persistent — set once, forget)**

```bash
# 1. Initialize config (creates ~/.reportmailer/config.yaml)
reportmailer init

# 2. Add your Gmail profile
reportmailer profile add gmail --host smtp.gmail.com --port 587 --username your.email@gmail.com --password "your-16-char-app-password"

# 3. Set it as the default
reportmailer profile use gmail

# 4. Verify
reportmailer config show
```

This writes the Gmail profile into `~/.reportmailer/config.yaml` (see
[Configuration (sample YAML)](#configuration-sample-yaml) for the full file).
After this, `send_report()` will auto-load Gmail SMTP from the profile:

```python
send_report(source=df, to="your.email@gmail.com", subject="Report")
```

> **Important:** The `default_profile` value must match a profile name exactly, and the profile must exist under `profiles:`. Otherwise you'll get "Missing configuration: smtp.host".

---

## Themes

Four built-in HTML themes:

```python
send_report(..., theme="corporate")   # Blue header, white background (default)
send_report(..., theme="modern")      # Clean, minimal with rounded corners
send_report(..., theme="dark")        # Dark background with purple accents
send_report(..., theme="minimal")     # Black & white, no frills
```

---

## Attachments

```python
send_report(..., attach="excel")  # Generate and attach as .xlsx (default)
send_report(..., attach="csv")    # Generate and attach as .csv
send_report(..., attach=None)     # No attachment
```

You can also attach an **existing file** by passing its path:

```python
send_report(
    source=df,
    to="user@example.com",
    subject="Quarterly Report",
    attach="path/to/existing_report.xlsx",  # attach a pre-existing file
)
```

This works for `.xlsx`, `.xls`, and `.csv` files. The file is attached as-is — no data generation is performed.

---

## Email Body & Table Position

The `body` parameter lets you write custom text — it replaces the default subject heading so you have full control over the email content. Use `{table}` as a placeholder to control **exactly where** the data table appears:

```python
send_report(
    source=df,
    to="user@example.com",
    subject="Weekly Report",
    body="# Weekly Sales\n\nHi Team,\n\nHere is the sales data:\n\n{table}\n\nRegards,\nAutomation",
)
```

The table renders at `{table}` — between your intro and footer.

> When `body` is provided, the subject line is used only for the email's subject header — it does **not** appear as a heading inside the email body. Include your own heading (e.g., `# Weekly Sales`) in the body text if desired.

### Multiple Tables

Pass a dict of named DataFrames to `tables`. Each can be placed with `{table:Name}`:

```python
send_report(
    tables={"Sales": df_sales, "Inventory": df_inv},
    to="manager@company.com",
    subject="Full Report",
    body="Sales:\n\n{table:Sales}\n\nInventory:\n\n{table:Inventory}\n\n--End",
)
```

Each table becomes a separate section in the HTML and a separate sheet in the Excel attachment.

### Hide Table from Body

Use `show_table=False` to send **only the body text** (data goes in attachment only):

```python
send_report(
    source=df,
    to="user@example.com",
    subject="Report",
    body="Report attached.",
    show_table=False,
    attach="excel",
)
```

---

## CC / BCC / Reply-To

```python
send_report(
    source=df,
    to=["manager@company.com", "finance@company.com"],
    cc="director@company.com",
    bcc="archive@company.com",
    subject="Quarterly Report",
)
```

---

## Advanced Configuration

### Config priority (highest to lowest)

```
1. smtp argument in send_report()
2. Environment variables (REPORTMAILER_SMTP_*)
3. CLI profile (~/.reportmailer/config.yaml)
4. → ConfigurationError if nothing found
```

### Direct SMTP (no config file needed)

```python
send_report(
    source=df,
    to="user@example.com",
    subject="Report",
    smtp={
        "host": "smtp.gmail.com",
        "port": 587,
        "username": "you@gmail.com",
        "password": "your-app-password",
        "use_tls": True,            # True for 587, False for 465 (SSL)
    },
)
```

### Hybrid: Env vars + profile

Set sensitive data via env vars (safer for CI/CD):

```bash
set REPORTMAILER_SMTP_PASSWORD=your-app-password
```

And store non-sensitive config in the profile:

```yaml
# ~/.reportmailer/config.yaml
profiles:
  gmail:
    smtp:
      host: smtp.gmail.com
      port: 587
      username: you@gmail.com
      # password comes from env var
default_profile: gmail
```

Env vars override individual profile fields while keeping defaults from the file.

### More YAML config (env var refs, multiple profiles, databases, multi-DB reports)

See the full config reference at [docs/configuration.md](docs/configuration.md). It covers
`${VAR_NAME}` environment references, multiple SMTP profiles, named database
connections, and combining several databases in one email.

---

## Conditional Formatting

Apply cell-level styles based on values — in both the HTML email body and the Excel attachment.

### Highlight rules

Color cells that match a condition using an operator:

```python
send_report(
    source=df,
    to="user@example.com",
    subject="Sales Report",
    conditionals={
        "Sales": [
            {"type": "highlight", "op": "gte", "value": 1000, "bg": "#c8e6c9"},   # green
            {"type": "highlight", "op": "lt", "value": 500, "bg": "#ffcdd2"},     # red
        ],
        "Status": [
            {"type": "highlight", "op": "eq", "value": "Complete", "bg": "#c8e6c9"},
        ],
    },
)
```

| Operator | Description |
|----------|-------------|
| `eq` | Equal to value |
| `ne` | Not equal to value |
| `gt` | Greater than value |
| `gte` | Greater than or equal to value |
| `lt` | Less than value |
| `lte` | Less than or equal to value |
| `between` | Between `min` and `max` |
| `in` | Value in a list (`values: [...]`) |
| `contains` | String contains value |

### Color scale

Apply a two-color gradient across a numeric column:

```python
conditionals={
    "Score": [{"type": "color_scale", "low_color": "#ffcdd2", "high_color": "#c8e6c9"}],
}
```

### Text pattern highlight

Highlight cells matching a regex pattern:

```python
conditionals={
    "Notes": [{"type": "text_highlight", "pattern": "urgent", "bg": "#fff3cd", "fg": "#000"}],
}
```

### Multiple tables

When using `tables=`, nest conditionals by table name:

```python
send_report(
    tables={"Sales": df_sales, "Inventory": df_inv},
    ...
    conditionals={
        "Sales": {"Amount": [{"type": "highlight", "op": "gte", "value": 1000, "bg": "#c8e6c9"}]},
        "Inventory": {"Quantity": [{"type": "color_scale", "low_color": "#ffcdd2", "high_color": "#c8e6c9"}]},
    },
)
```

> Conditional styles apply to **both** the HTML email body and the **Excel attachment** (`.xlsx` only).

---

## Advanced API (Report class)

For step-by-step control, use the `Report` class:

```python
from reportmailer import Report
import pandas as pd

df = pd.DataFrame({"Name": ["Alice", "Bob"], "Sales": [100, 200]})

report = Report(source=df)
report.read() \
    .render(theme="modern", title="Sales Overview") \
    .attach("csv") \
    .send(
        to=["finance@company.com", "ops@company.com"],
        subject="Sales Overview",
    )
```

You can also chain selectively:

```python
report = Report(source=df)
report.read()                              # load data
html = report.render(theme="dark")._html   # get HTML string (inspect/manual use)
report.attach("excel").attach("csv")       # multiple attachments
report.send(to="user@example.com", subject="Full Report")
```

---

## Full API Reference

### `send_report(source=None, query=None, sql_file=None, db=None, tables=None, to=None, cc=None, bcc=None, subject=None, body=None, show_table=True, theme="corporate", attach="excel", conditionals=None, smtp=None)`

| Parameter | Type | Description |
|-----------|------|-------------|
| `source` | DataFrame / str / None | Data source (DataFrame, file path, or DB URI). Omit if using `tables` only. |
| `query` | str / None | SQL query for database sources |
| `sql_file` | str / None | Path to `.sql` file as alternative to `query` |
| `db` | str / None | Name of a database in `~/.reportmailer/config.yaml` under `databases` (uses its host/port/username/password) |
| `tables` | dict / None | `{name: DataFrame}` dict for multiple named tables. A value may also be `{"db": ..., "query": ...}` to read from a configured database |
| `to` | str / list | Recipient email(s) |
| `cc` | str / list | CC recipient(s) |
| `bcc` | str / list | BCC recipient(s) |
| `subject` | str | Email subject line |
| `body` | str | Custom body text; use `{table}` / `{table:Name}` to position tables |
| `show_table` | bool | Whether to render the table in the email body (default `True`) |
| `theme` | str | HTML theme: `corporate`, `modern`, `dark`, `minimal` |
| `attach` | str / None | `"excel"`, `"csv"`, `None`, or a file path (`.xlsx`/`.xls`/`.csv`) to attach an existing file |
| `conditionals` | dict / None | Conditional formatting rules per column (see [Conditional Formatting](#conditional-formatting)) |
| `smtp` | dict / None | SMTP config override: `{"host", "port", "username", "password", "use_tls"}` |

**Returns:** `{"success": True/False, "message": "...", "source_type": "..."}`

---

## CLI Commands

```bash
reportmailer init                # Initialize config file
reportmailer profile add NAME    # Add SMTP profile (--host, --port, --username, --password)
reportmailer profile list        # List all profiles
reportmailer profile use NAME    # Set default profile
reportmailer profile delete NAME # Delete a profile
reportmailer config show         # Show current config
reportmailer config reset        # Reset config to default
```

---

## License

MIT
