howl/SETUP.md

276 lines
7.8 KiB
Markdown
Raw Permalink Normal View History

# Setup Guide
## Prerequisites
- Python 3.11+
- PostgreSQL 14+ database (local or hosted)
- Microsoft 365 account with an Azure AD app registration
- Anthropic API key
---
## 1. Azure AD App Registration
### For a shared mailbox (recommended for production)
This uses **application permissions** — the daemon runs without any user logged in.
1. Go to [portal.azure.com](https://portal.azure.com) → **Azure Active Directory****App registrations****New registration**
2. Name it something like `howl-email-daemon`; leave the redirect URI blank
3. After creation, note the **Application (client) ID** and **Directory (tenant) ID**
4. Go to **Certificates & secrets****New client secret** → copy the value immediately
5. Go to **API permissions****Add a permission****Microsoft Graph****Application permissions**
- Add `Mail.Read`
- Add `Mail.ReadWrite`
6. Click **Grant admin consent** (requires a Global Admin)
Set in `.env`:
```
GRAPH_AUTH_MODE=client_credentials
AZURE_TENANT_ID=<Directory (tenant) ID>
AZURE_CLIENT_ID=<Application (client) ID>
AZURE_CLIENT_SECRET=<client secret value>
GRAPH_MAILBOX=shared@yourcompany.com
```
### For a personal mailbox
This uses **delegated permissions** — the daemon acts on behalf of a user.
1. Follow steps 14 above
2. Go to **API permissions****Add a permission****Microsoft Graph****Delegated permissions**
- Add `Mail.Read`
- Add `Mail.ReadWrite`
3. Under **Authentication****Advanced settings**, enable **Allow public client flows**
Set in `.env`:
```
GRAPH_AUTH_MODE=delegated
AZURE_TENANT_ID=<Directory (tenant) ID>
AZURE_CLIENT_ID=<Application (client) ID>
# No AZURE_CLIENT_SECRET needed for delegated mode
GRAPH_MAILBOX=yourname@yourcompany.com
MSAL_TOKEN_CACHE_PATH=.msal_cache.bin
```
On first `howl run`, you'll be shown a device code URL and prompted to log in. The token is cached to `MSAL_TOKEN_CACHE_PATH` and refreshes automatically.
---
## 2. PostgreSQL Database
Create a database for Howl:
```sql
CREATE DATABASE howl;
CREATE USER howl_user WITH PASSWORD 'your-password';
GRANT ALL PRIVILEGES ON DATABASE howl TO howl_user;
```
Set in `.env`:
```
DATABASE_URL=postgresql+asyncpg://howl_user:your-password@localhost:5432/howl
```
Run migrations to create all tables:
```bash
alembic upgrade head
```
---
## 3. Environment Configuration
Copy the template and fill in your values:
```bash
cp .env.example .env
```
### Required settings
| Variable | Description |
|---|---|
| `AZURE_TENANT_ID` | Azure AD directory (tenant) ID |
| `AZURE_CLIENT_ID` | App registration client ID |
| `AZURE_CLIENT_SECRET` | Client secret (client_credentials mode only) |
| `GRAPH_MAILBOX` | Email address of the mailbox to monitor |
| `ANTHROPIC_API_KEY` | Your Anthropic API key |
| `DATABASE_URL` | PostgreSQL connection string (asyncpg driver) |
### Key optional settings
| Variable | Default | Description |
|---|---|---|
| `GRAPH_AUTH_MODE` | `client_credentials` | `client_credentials` or `delegated` |
| `GRAPH_POLL_INTERVAL_SECONDS` | `300` | How often to check for new mail (seconds) |
| `GRAPH_BATCH_SIZE` | `50` | Messages fetched per poll cycle |
| `GRAPH_MAX_BODY_CHARS` | `4000` | Email body characters sent to LLM (truncated beyond this) |
| `ANTHROPIC_MODEL` | `claude-sonnet-4-6` | Claude model to use |
| `LLM_CONFIDENCE_THRESHOLD` | `0.60` | Below this, email goes to Needs Review |
| `DRY_RUN` | `false` | Set `true` to classify without touching email |
| `MAX_RETRY_COUNT` | `3` | Retry attempts for failed messages |
| `NOTIFICATION_WEBHOOK_URL` | _(empty)_ | Teams or Slack webhook for escalation alerts |
### Mail folder names
These are the display names Howl will create/find in the monitored mailbox:
| Variable | Default |
|---|---|
| `FOLDER_CUSTOMERS` | `Customers` |
| `FOLDER_VENDORS` | `Vendors` |
| `FOLDER_REVIEW` | `Needs Review` |
| `FOLDER_SPAM` | `Junk Email` |
| `FOLDER_ESCALATE` | `Escalate` |
Folders are created automatically on first use if they don't already exist.
---
## 4. Populate the Database
Add your contacts before running the daemon. Howl matches sender addresses against these tables to provide context to the LLM.
### Adding customers
```sql
-- Insert a customer
INSERT INTO customers (name, company, notes)
VALUES ('Alice Johnson', 'Acme Corp', 'Key account — priority response required');
-- Attach email addresses
INSERT INTO customer_emails (customer_id, email_address, is_primary)
VALUES (
(SELECT id FROM customers WHERE name = 'Alice Johnson'),
'alice@acme.com',
TRUE
);
```
### Adding vendors
```sql
INSERT INTO vendors (name, company, service_category)
VALUES ('SupplyPro', 'SupplyPro Inc', 'supplier');
INSERT INTO vendor_emails (vendor_id, email_address, is_primary)
VALUES (
(SELECT id FROM vendors WHERE name = 'SupplyPro'),
'billing@supplypro.com',
TRUE
);
```
### Adding whitelist entries
```sql
-- Exact email address
INSERT INTO whitelist (email_address, description, added_by)
VALUES ('audit@partnerfirm.com', 'External audit partner', 'admin');
-- Entire domain (matches any @microsoft.com sender)
INSERT INTO whitelist (domain, description, added_by)
VALUES ('microsoft.com', 'Microsoft services', 'admin');
-- Temporary entry (expires in 30 days)
INSERT INTO whitelist (email_address, description, expires_at, added_by)
VALUES ('temp@contractor.com', 'Temp contractor access', NOW() + INTERVAL '30 days', 'admin');
```
---
## 5. Running Howl
### Test first with dry-run
```bash
# Process up to 10 emails, classify them, but take no action
howl dry-run --limit 10
```
Check results:
```bash
# See what classifications were made
howl status --limit 20
# Or query directly
psql -d howl -c "SELECT sender_address, llm_classification, final_action, status FROM email_log ORDER BY created_at DESC LIMIT 10;"
```
### Start the daemon
```bash
howl run
```
The daemon polls every `GRAPH_POLL_INTERVAL_SECONDS` (default: 5 minutes). Stop it with `Ctrl+C` or `SIGTERM` — it finishes the current batch before exiting.
### Running as a systemd service (Linux)
Create `/etc/systemd/system/howl.service`:
```ini
[Unit]
Description=Howl Email Management Daemon
After=network.target postgresql.service
[Service]
Type=simple
User=howl
WorkingDirectory=/opt/projects/howl
EnvironmentFile=/opt/projects/howl/.env
ExecStart=/opt/projects/howl/.venv/bin/howl run
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl enable howl
sudo systemctl start howl
sudo journalctl -u howl -f # Follow logs
```
---
## 6. Webhook Notifications (optional)
Set `NOTIFICATION_WEBHOOK_URL` to receive alerts when an email is escalated.
### Microsoft Teams
1. In Teams, go to the channel → **...** → **Connectors****Incoming Webhook**
2. Create and copy the webhook URL
3. Set `NOTIFICATION_WEBHOOK_URL=<webhook URL>`
### Slack
1. In Slack, go to [api.slack.com/apps](https://api.slack.com/apps) → create an app → **Incoming Webhooks**
2. Activate and add to a channel, copy the URL
3. Set `NOTIFICATION_WEBHOOK_URL=<webhook URL>`
---
## Troubleshooting
**Authentication error (client_credentials)**
- Verify the client secret hasn't expired in the Azure portal
- Ensure admin consent was granted for the application permissions
**401 Unauthorized from Graph**
- For delegated mode: delete `.msal_cache.bin` and re-authenticate with `howl run`
- Check that the app registration has the correct permissions for the mailbox
**Messages not being processed**
- Check `email_log` for `status = 'failed'` rows and read `error_message`
- Ensure the mailbox address in `GRAPH_MAILBOX` exactly matches the M365 UPN or shared mailbox address
**Low LLM accuracy**
- Lower `LLM_CONFIDENCE_THRESHOLD` to move more edge cases to `Needs Review`
- Add `notes` to customer/vendor records — these are included in the LLM context
- Inspect `llm_raw_response` in `email_log` for Claude's reasoning