"""
TradeFlow AI — Notification Service (T-074)
Sends notifications via:
1. Resend (email)
2. WhatsApp Business API (optional, behind feature flag)
Called on:
- REVIEW_READY → operator email
- ACCEPTED / REJECTED → importer email + WhatsApp (if enabled)
"""
from __future__ import annotations
import logging
from typing import Any
import httpx
logger = logging.getLogger("services.notification")
RESEND_API_URL = "https://api.resend.com/emails"
class NotificationService:
def __init__(self, settings: Any) -> None:
self._settings = settings
async def notify_review_ready(
self,
batch_id: str,
operator_email: str,
crs_score: int,
risk_level: str,
) -> None:
"""Notify operator that a batch is ready for review."""
if not self._settings.RESEND_API_KEY:
logger.info("RESEND_API_KEY not set — skipping email notification")
return
subject = f"[TradeFlow AI] Batch {batch_id[:8]}… ready for review"
html = f"""
Batch Ready for Review
A new import declaration batch is ready for your review.
| Batch ID | {batch_id} |
| CRS Score | {crs_score}/100 |
| Risk Level | {risk_level} |
Review Now →
"""
await self._send_email(to=operator_email, subject=subject, html=html)
async def notify_ceisa_result(
self,
batch_id: str,
ceisa_status: str,
aju_number: str,
recipient_email: str,
recipient_phone: str | None = None,
) -> None:
"""Notify importer of CEISA acceptance or rejection."""
accepted = ceisa_status == "ACCEPTED"
subject = (
f"[TradeFlow AI] PIB {'Diterima' if accepted else 'Ditolak'} — AJU {aju_number}"
)
html = f"""
{"✅ PIB Diterima" if accepted else "❌ PIB Ditolak"}
Status PIB Anda telah diperbarui:
| Batch ID | {batch_id} |
| Nomor AJU | {aju_number} |
| Status | {ceisa_status} |
Lihat Detail →
"""
await self._send_email(to=recipient_email, subject=subject, html=html)
# WhatsApp (optional)
if (
self._settings.ENABLE_NOTIFICATIONS_WHATSAPP
and recipient_phone
and self._settings.WHATSAPP_TOKEN
):
msg = (
f"TradeFlow AI: PIB Anda {'*DITERIMA*' if accepted else '*DITOLAK*'} "
f"oleh CEISA. Nomor AJU: {aju_number}. "
f"Detail: https://app.tradeflow.ai/batches/{batch_id}"
)
await self._send_whatsapp(phone=recipient_phone, message=msg)
async def _send_email(self, to: str, subject: str, html: str) -> None:
try:
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(
RESEND_API_URL,
headers={
"Authorization": f"Bearer {self._settings.RESEND_API_KEY.get_secret_value()}",
"Content-Type": "application/json",
},
json={
"from": self._settings.NOTIFICATION_EMAIL_FROM,
"to": [to],
"subject": subject,
"html": html,
},
)
resp.raise_for_status()
logger.info(f"Email sent to {to}: {subject}")
except Exception as e:
logger.error(f"Email send failed: {e}")
async def _send_whatsapp(self, phone: str, message: str) -> None:
try:
phone_id = self._settings.WHATSAPP_PHONE_NUMBER_ID
token = self._settings.WHATSAPP_TOKEN.get_secret_value()
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(
f"https://graph.facebook.com/v20.0/{phone_id}/messages",
headers={"Authorization": f"Bearer {token}"},
json={
"messaging_product": "whatsapp",
"to": phone.replace("+", ""),
"type": "text",
"text": {"body": message},
},
)
resp.raise_for_status()
logger.info(f"WhatsApp sent to {phone}")
except Exception as e:
logger.error(f"WhatsApp send failed: {e}")