Skip to content

Commit 7bccbf0

Browse files
Sai Sridharclaude
andcommitted
Add 5 new features: health score, AI search, scheduled send, newsletter view, smart compose
- GET /api/emails/health-score: inbox health score (0-100, A-F grade) with tips - POST /api/emails/ai-search: natural language search via Claude Haiku → DB filters - POST /api/emails/ai-draft: AI-generated email draft with user tone + signature - POST/GET/DELETE /api/scheduled-sends: schedule emails for future delivery - flush_scheduled_sends scheduler job every 5 min via Gmail API - send_gmail_compose() for new (non-reply) emails - /email/newsletters: grouped newsletter view by sender with unsubscribe - compose.tsx: AI Draft panel + Schedule Send datetime picker - email/index.tsx: sparkle button toggles AI natural language search mode - Layout.tsx: Newsletters nav points to grouped view - Responsive: newsletters page stacks vertically on mobile Requires Supabase migration for scheduled_sends table (see routes/scheduled.py header) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5385c62 commit 7bccbf0

35 files changed

Lines changed: 4189 additions & 285 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,4 @@ Icon
182182
Network Trash Folder
183183
Temporary Items
184184
.apdisk
185+
.gstack/

backend/ai/classifier.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
- language: ISO 639-1 language code of the email (e.g. 'en', 'es', 'fr', 'de', 'pt', 'hi', 'zh', 'ja')
1919
- is_phishing: boolean — true if the email shows signs of phishing or social engineering
2020
- phishing_indicators: array of strings listing specific suspicious elements found (empty array if none)
21+
- is_invoice: boolean — true if the email is an invoice, bill, receipt, or payment request
22+
- invoice_amount: string or null — total amount due (e.g. "$1,200.00"), null if not an invoice
23+
- invoice_due_date: string or null — payment due date as ISO date string, null if not found
24+
- invoice_vendor: string or null — name of the company sending the invoice, null if not an invoice
2125
2226
Email Subject: {subject}
2327
From: {sender}
@@ -80,6 +84,10 @@ async def classify_email(subject: str, sender: str, body: str, attachments: list
8084
result["language"] = result.get("language", "en")
8185
result["is_phishing"] = bool(result.get("is_phishing", False))
8286
result["phishing_indicators"] = result.get("phishing_indicators", [])
87+
result["is_invoice"] = bool(result.get("is_invoice", False))
88+
result["invoice_amount"] = result.get("invoice_amount") or None
89+
result["invoice_due_date"] = result.get("invoice_due_date") or None
90+
result["invoice_vendor"] = result.get("invoice_vendor") or None
8391

8492
return result
8593

backend/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ class Settings(BaseSettings):
6262
# AI Thresholds
6363
URGENCY_THRESHOLD: int = 7
6464

65+
# VAPID keys for Web Push (generate with: python -c "from py_vapid import Vapid; v=Vapid(); v.generate_keys(); print(v.private_key); print(v.public_key)")
66+
VAPID_PRIVATE_KEY: str = ""
67+
VAPID_PUBLIC_KEY: str = ""
68+
VAPID_EMAIL: str = "mailto:admin@mailair.company"
69+
6570
# Environment
6671
ENVIRONMENT: str = "development"
6772

backend/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from config import settings
1717
from limiter import limiter
18-
from routes import auth, emails, actions, replies, integrations, settings as settings_routes, billing, contacts, outlook, calendar, teams, webhooks as webhooks_routes, autoassign, crm_integrations, admin as admin_routes, waitlist as waitlist_routes
18+
from routes import auth, emails, actions, replies, integrations, settings as settings_routes, billing, contacts, outlook, calendar, teams, webhooks as webhooks_routes, autoassign, crm_integrations, admin as admin_routes, waitlist as waitlist_routes, push as push_routes, scheduled as scheduled_routes
1919
from workers.email_listener import start_email_listener, stop_email_listener
2020

2121
# ---------------------------------------------------------------------------
@@ -36,7 +36,9 @@ async def lifespan(app: FastAPI):
3636
"""Startup and shutdown hooks."""
3737
logger.info("Mailair backend starting up (env=%s).", settings.ENVIRONMENT)
3838
start_email_listener()
39+
3940
yield
41+
4042
logger.info("Mailair backend shutting down.")
4143
stop_email_listener()
4244

@@ -166,6 +168,8 @@ async def generic_exception_handler(request: Request, exc: Exception):
166168
app.include_router(crm_integrations.router, prefix="/api")
167169
app.include_router(admin_routes.router, prefix="/api")
168170
app.include_router(waitlist_routes.router)
171+
app.include_router(push_routes.router, prefix="/api")
172+
app.include_router(scheduled_routes.router, prefix="/api")
169173

170174

171175
# ---------------------------------------------------------------------------

backend/models/email.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class EmailCreate(EmailBase):
1414
user_id: str
1515
gmail_message_id: Optional[str] = None
1616
thread_id: Optional[str] = None
17+
labels: List[str] = []
1718

1819

1920
class EmailResponse(EmailBase):

backend/models/user.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ class UserUpdate(BaseModel):
2828
vacation_mode: Optional[bool] = None
2929
vacation_message: Optional[str] = None
3030
email_signature: Optional[str] = None
31+
# Email digest preferences
32+
digest_enabled: Optional[bool] = None
33+
digest_frequency: Optional[str] = None # 'daily' | 'weekly'
34+
# Selective Gmail sync filters
35+
sync_labels: Optional[list] = None # e.g. ["INBOX", "CATEGORY_PROMOTIONS"]
36+
sync_max_emails: Optional[int] = None # 10 / 25 / 50 / 100
37+
sync_days_back: Optional[int] = None # only fetch emails newer than N days
38+
sync_sender_allowlist: Optional[list] = None # only from these domains/emails
39+
sync_sender_blocklist: Optional[list] = None # never from these domains/emails
3140

3241

3342
class ReplyDraftResponse(BaseModel):

backend/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ pydantic-settings==2.7.0
1818
apscheduler==3.10.4
1919
slowapi==0.1.9
2020
tenacity==9.0.0
21+
pywebpush==2.0.0

0 commit comments

Comments
 (0)