update: Major Overhault to all subsystems
This commit is contained in:
102
.claude/crm-step-10.md
Normal file
102
.claude/crm-step-10.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# CRM Step 10 — Integration: IMAP/SMTP Email
|
||||
|
||||
## Context
|
||||
Read `.claude/crm-build-plan.md` for full context and IMPORTANT NOTES.
|
||||
Steps 01–09 must be complete.
|
||||
|
||||
## Task
|
||||
Integrate the company email mailbox so that:
|
||||
1. Emails from/to a customer's email addresses appear in their Comms tab
|
||||
2. New emails can be composed and sent from the console
|
||||
3. A background sync runs periodically to pull new emails
|
||||
|
||||
## Backend changes
|
||||
|
||||
### 1. Add email settings to `backend/config.py`
|
||||
```python
|
||||
imap_host: str = ""
|
||||
imap_port: int = 993
|
||||
imap_username: str = ""
|
||||
imap_password: str = ""
|
||||
imap_use_ssl: bool = True
|
||||
smtp_host: str = ""
|
||||
smtp_port: int = 587
|
||||
smtp_username: str = ""
|
||||
smtp_password: str = ""
|
||||
smtp_use_tls: bool = True
|
||||
email_sync_interval_minutes: int = 15
|
||||
```
|
||||
|
||||
### 2. Create `backend/crm/email_sync.py`
|
||||
Using standard library `imaplib` and `email` (no new deps).
|
||||
|
||||
```python
|
||||
async def sync_emails():
|
||||
"""
|
||||
Connect to IMAP. Search UNSEEN or since last sync date.
|
||||
For each email:
|
||||
- Parse from/to/subject/body (text/plain preferred, fallback to stripped HTML)
|
||||
- Check if from-address or to-address matches any customer contact (search crm_customers)
|
||||
- If match found: create crm_comms_log entry with type=email, ext_message_id=message-id header
|
||||
- Skip if ext_message_id already exists in crm_comms_log (dedup)
|
||||
Store last sync time in a simple SQLite table crm_sync_state:
|
||||
CREATE TABLE IF NOT EXISTS crm_sync_state (key TEXT PRIMARY KEY, value TEXT)
|
||||
"""
|
||||
|
||||
async def send_email(to: str, subject: str, body: str, cc: List[str] = []) -> str:
|
||||
"""
|
||||
Send email via SMTP. Returns message-id.
|
||||
After sending, create a crm_comms_log entry: type=email, direction=outbound.
|
||||
"""
|
||||
```
|
||||
|
||||
### 3. Add SQLite table to `backend/mqtt/database.py`
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS crm_sync_state (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### 4. Add email endpoints to `backend/crm/router.py`
|
||||
|
||||
`POST /api/crm/email/send`
|
||||
Body: `{ customer_id, to, subject, body, cc (optional) }`
|
||||
→ calls `send_email(...)`, links to customer in comms_log
|
||||
|
||||
`POST /api/crm/email/sync`
|
||||
→ manually trigger `sync_emails()` (for testing / on-demand)
|
||||
→ returns count of new emails found
|
||||
|
||||
### 5. Add background sync to `backend/main.py`
|
||||
In the `startup` event, add a periodic task:
|
||||
```python
|
||||
async def email_sync_loop():
|
||||
while True:
|
||||
await asyncio.sleep(settings.email_sync_interval_minutes * 60)
|
||||
try:
|
||||
from crm.email_sync import sync_emails
|
||||
await sync_emails()
|
||||
except Exception as e:
|
||||
print(f"[EMAIL SYNC] Error: {e}")
|
||||
|
||||
asyncio.create_task(email_sync_loop())
|
||||
```
|
||||
Only start if `settings.imap_host` is set (non-empty).
|
||||
|
||||
## Frontend changes
|
||||
|
||||
### Update Comms tab in `CustomerDetail.jsx`
|
||||
- Email entries show: from/to, subject, body (truncated with expand)
|
||||
- "Compose Email" button → modal with: to (pre-filled from customer primary email), subject, body (textarea), CC
|
||||
- On send: POST `/api/crm/email/send`, add new entry to comms list
|
||||
|
||||
### Update `InboxPage.jsx`
|
||||
- Add "Sync Now" button → POST `/api/crm/email/sync`, show result count toast
|
||||
|
||||
## Notes
|
||||
- `imaplib` is synchronous — wrap in `asyncio.run_in_executor(None, sync_fn)` for the async context
|
||||
- For HTML emails: strip tags with a simple regex or `html.parser` — no need for an HTML renderer
|
||||
- Email body matching: compare email From/To headers against ALL customer contacts where type=email
|
||||
- Don't sync attachments yet — just text content. Attachment handling can be a future step.
|
||||
- If imap_host is empty string, the sync loop doesn't start and the send endpoint returns 503
|
||||
Reference in New Issue
Block a user