Skip to main content
Glama
Receivables Reminder Engine

Receivables Reminder Engine

by candykobli
README.md
# Receivables Reminder Engine

**Payment reminders that a finance team can trust: the AI prepares, people decide.**

![Approval page](docs/images/approval-page.png)

**Try the interactive demo:** https://claude.ai/artifact/8mfiu4ABYxhwFJqKNbdf5M (synthetic data, nothing is sent)


An AI-ready tool that chases overdue invoices the way a careful credit controller would. It finds what is overdue, checks it is still unpaid and not disputed, drafts one reminder per customer in their language, and sends **only after a named person approves**. It logs every step and sends the CFO a note for each email sent.

There are three ways to use it, all on the same engine and data:
- **AI assistant:** a remote MCP server for Claude, Microsoft Copilot Studio or any MCP client
- **Approval page:** a web page where finance users review, approve or reject, and send (no AI)
- **Scheduled job:** prepares drafts automatically every day and never sends on its own

> Version 0.1, built entirely on synthetic data: all companies, people and amounts are invented.

## What it does

| Step | What happens |
|---|---|
| Preview | Lists the emails it would prepare and every skipped invoice with its reason (paid, disputed, not due, too early, final already sent) |
| Prepare | One draft per customer, in FR, EN, ES or PT. Final notices always go in a separate email. Each invoice PDF is read and checked against the register. |
| Approve / reject | A named approver. Drafts with warnings (for example a PDF total that differs from the register) can't be approved without explicit acknowledgement. |
| Send | Re-checks payments first and cancels a draft if the invoice was paid after approval. Then sends, logs each invoice's level and sends the CFO a note. |
| Audit | Full history per invoice and an audit log of who did what and when |

| Catches problems before sending | Writes in the customer's language |
|---|---|
| ![PDF mismatch warning](docs/images/pdf-mismatch-warning.png) | ![Spanish final notice](docs/images/final-notice-es.png) |

**Reminder levels** (configurable in `config/settings.json`, created from `config/settings.example.json` on first start):

| Level | Default | Wording |
|---|---|---|
| 1st reminder | 7 days overdue | Polite reminder |
| 2nd reminder | 21 days overdue | Same template, "second" instead of "first" |
| Final notice | 35 days overdue | Firm deadline (8 days). For EU customers: statutory interest (reference rate + 8 points) and EUR 40 compensation per invoice under Directive 2011/7/EU. For non-EU customers: contractual wording. |

**Plug-ins:** `general` (any overdue invoice), `vat_recovery` (VAT wording) and `intercompany` (friendly tone, netting, no final notice).

## Where the data comes from

Each list has its own source and a column mapping. Both are set on the **Settings** page, so no file editing is needed.

| Data | Possible sources |
|---|---|
| Open invoices (required) | Excel/CSV upload · file on the server · OneDrive · SharePoint · SQL database · web API / OData (SAP, Dynamics 365) |
| Customer contacts (required) | Same as above |
| Payments, disputes (optional) | Same as above, or *not used* when the export already has an open-amount column |
| Invoice PDFs | Folder on the server · OneDrive · SharePoint, found by file-name pattern or by a link column |
| Sending email | Test mode (.eml files) · Outlook through Microsoft 365, from your own or a shared mailbox |

![Column matching](docs/images/column-matching.png)

Column names are matched automatically in English, French, Spanish, Portuguese, Danish and German, and for common ERP field names. European number and date formats, Excel dates, title rows above the table and country names are all handled. Rows that can't be read are reported, never guessed. Secrets (database passwords, API keys, the Microsoft 365 app secret) stay in environment variables.

## Architecture

```
MCP client (Claude, Copilot, …)
        │  streamable HTTP
reminders/server.py   ← MCP tools
reminders/service.py  ← workflow: preview → prepare → approve → send → log
 ├─ engine.py      who gets which level (pure logic)
 ├─ render.py      HTML + text email, invoice table, final-notice figures
 ├─ templates.py   approved wording in 4 languages
 ├─ plugins.py     general / VAT recovery / intercompany
 ├─ extract.py     reads invoice PDFs and cross-checks the register
 ├─ connectors/    files (local/OneDrive/SharePoint), SQL, web API, Microsoft Graph, column mapping
 ├─ mailer.py      test outbox (.eml files); Outlook sending is in connectors/graph.py
 └─ store.py       drafts, reminder log, audit trail (SQLite now; Azure SQL next)
```

## Run it

**For users:** double-click `Start Reminders.bat` (Windows) or `Start Reminders.command` (Mac). See TESTING.md and SETUP_MICROSOFT365.md.

**For developers:**

```bash
pip install -r requirements.txt
python data/generate_synthetic.py --as-of 2026-11-04   # synthetic invoices, contacts, payments, PDFs
python -m reminders.server --port 8765                  # MCP endpoint: http://127.0.0.1:8765/mcp
python demo_client.py http://127.0.0.1:8765/mcp         # full cycle through the MCP protocol
python run_job.py --as-of 2026-11-04                    # scheduled job: prepares drafts
python run_web.py                                       # approval page: http://127.0.0.1:8080/?as_of=2026-11-04
pytest -q                                               # automatic checks
```

Sent emails are written to `outbox/` as `.eml` files, which open in Outlook.

To try it interactively, point the MCP Inspector (`npx @modelcontextprotocol/inspector`) at the URL above. To use it from Claude or Copilot, deploy it behind a public HTTPS URL and add that URL as a custom connector.

## Deploy to Azure (EU region)

```bash
az group create -n rg-reminders -l westeurope
az containerapp up -n reminders-mcp -g rg-reminders --source . --ingress external --target-port 8000
```

Before you use it with real data:
- Add OAuth sign-in (Microsoft Entra ID).
- Keep secrets in Azure Key Vault.
- Mount persistent storage, or switch the store to Azure SQL.

## Use it from an AI assistant

Add the server to the Claude desktop app (Settings → Developer → Edit Config):
```json
{"mcpServers": {"receivables-reminders": {"command": "C:\\path\\to\\receivables-reminder-mcp\\.venv\\Scripts\\python.exe",
  "args": ["C:\\path\\to\\receivables-reminder-mcp\\run_server.py", "--transport", "stdio"]}}}
```
In the cloud, the same server runs over HTTP (`/mcp`) for Claude or Microsoft Copilot Studio.

## Roadmap

1. **Hosting** on Azure with Microsoft Entra sign-in for the approval page, so users only open a link
2. **Approver identity from the login** instead of a typed name
3. **Notification** to the approver when new drafts are waiting (email or Teams)
4. **Dispute detection** from customer replies, which pauses the reminder chain
5. **Weekly CFO digest** as an alternative to one note per send

## Privacy by design

- Personal configuration (company details, data sources, Microsoft 365 tenant and client IDs) is stored in `config/settings.json` and `config/sources.json`. Both files are created locally from the `*.example.json` templates and are excluded from version control.
- The Microsoft 365 sign-in is kept only in `config/.m365_token_cache.json`, which is also excluded. Secrets such as the app secret and database passwords are read from environment variables.
- All data in this repository is synthetic.

## Author

Marie Candy Kobli, Business Analyst. Part of a portfolio of AI tools built from real finance, manufacturing, M&A and project-feasibility processes.