# Notification Service

## Overview

The **Notification Service** handles multi-channel communication with bakery owners, managers, and staff through Email (SMTP) and WhatsApp (Twilio). It delivers critical operational alerts (stockouts, quality issues, equipment maintenance), business insights (daily summaries, forecast updates), and customer communications (order confirmations, delivery notifications). The service ensures that important information reaches the right people at the right time through their preferred communication channel.

## Key Features

### Multi-Channel Communication
- **Email (SMTP)** - Professional email notifications
- **WhatsApp (Twilio)** - Instant messaging for urgent alerts
- **SMS (Twilio)** - Fallback text messaging
- **Channel Prioritization** - Auto-select channel by urgency
- **Channel Preferences** - User-defined communication preferences
- **Multi-Recipient** - Send to individuals or groups
- **Delivery Tracking** - Monitor delivery status per channel

### Purchase Order Event Processing (🆕)
- **Automatic Supplier Notifications** - Sends professional HTML emails to suppliers when POs are approved
- **Event-Driven Architecture** - Consumes procurement events from RabbitMQ
- **Template Rendering** - Uses Jinja2 templates for professional, branded emails
- **Fire-and-Forget Processing** - Non-blocking event consumption for high throughput
- **Email Template Features** - Responsive design, gradient headers, line item tables, mobile-optimized

### Email Capabilities
- **HTML Templates** - Professional branded emails
- **Plain Text Fallback** - Ensure compatibility
- **Attachments** - PDF reports, invoices, documents
- **Inline Images** - Logo, charts embedded
- **Email Templates** - Pre-designed templates per alert type
- **Variable Substitution** - Dynamic content per recipient
- **Batch Sending** - Efficient bulk email delivery

### WhatsApp Integration
- **Twilio API** - Official WhatsApp Business API
- **Rich Messages** - Text, images, documents
- **Message Templates** - Pre-approved templates for compliance
- **Interactive Messages** - Quick reply buttons
- **Media Support** - Send images, PDFs
- **Delivery Receipts** - Read/delivered status
- **Opt-In Management** - GDPR-compliant consent tracking

### Notification Types
- **Critical Alerts** - Stockouts, equipment failure, quality issues
- **High Priority** - Low stock warnings, forecast anomalies
- **Medium Priority** - Daily summaries, scheduled reports
- **Low Priority** - Weekly digests, monthly reports
- **Informational** - System updates, tips, best practices
- **Customer Notifications** - Order confirmations, delivery updates

### Template Management
- **Template Library** - 20+ pre-built templates
- **Template Versioning** - Track template changes
- **Multi-Language** - Spanish, English, Catalan
- **Variable Placeholders** - Dynamic content insertion
- **Template Preview** - Test before sending
- **Custom Templates** - Create tenant-specific templates
- **Template Analytics** - Open rates, click rates

### Delivery Management
- **Queue System** - Prioritized delivery queue
- **Retry Logic** - Automatic retry on failure
- **Rate Limiting** - Respect API rate limits
- **Delivery Status** - Track sent, delivered, failed, read
- **Failure Handling** - Fallback to alternative channels
- **Bounce Management** - Handle invalid addresses
- **Unsubscribe Management** - Honor opt-out requests

### Analytics & Reporting
- **Delivery Metrics** - Success rates per channel
- **Open Rates** - Email open tracking
- **Click Rates** - Link click tracking
- **Response Times** - Time to read/acknowledge
- **Channel Effectiveness** - Compare channel performance
- **Cost Analysis** - Communication costs per channel
- **User Engagement** - Active users per channel

## Business Value

### For Bakery Owners
- **Real-Time Alerts** - Know critical issues immediately
- **Channel Flexibility** - Email for reports, WhatsApp for urgent
- **Cost Effective** - WhatsApp cheaper than SMS
- **Professional Communication** - Branded emails enhance reputation
- **Customer Engagement** - Order updates improve satisfaction
- **Remote Management** - Stay informed from anywhere

### Quantifiable Impact
- **Response Time**: 90% faster with WhatsApp alerts (minutes vs. hours)
- **Issue Resolution**: 50-70% faster with immediate notifications
- **Cost Savings**: €50-150/month (WhatsApp vs. SMS or phone calls)
- **Customer Satisfaction**: 20-30% improvement with order updates
- **Staff Efficiency**: 3-5 hours/week saved on manual communication
- **Alert Reliability**: 99%+ delivery rate

### For Operations Staff
- **Instant Alerts** - WhatsApp notifications on critical issues
- **Actionable Information** - Clear instructions in alerts
- **Mobile Access** - Receive alerts on phone
- **Alert History** - Review past notifications
- **Acknowledgment** - Confirm receipt of critical alerts

### For Customers
- **Order Confirmation** - Immediate order receipt
- **Delivery Updates** - Know when order is ready
- **Personalized Communication** - Address by name
- **Multiple Channels** - Choose email or WhatsApp
- **Professional Image** - Branded communication

## Technology Stack

- **Framework**: FastAPI (Python 3.11+) - Async web framework
- **Database**: PostgreSQL 17 - Notification history
- **Queue**: RabbitMQ 4.1 - Notification queue
- **Caching**: Redis 7.4 - Template cache
- **Email**: SMTP (SendGrid, Amazon SES, SMTP server)
- **WhatsApp**: Twilio API - WhatsApp Business
- **SMS**: Twilio API - SMS fallback
- **Templates**: Jinja2 - Template rendering
- **Logging**: Structlog - Structured JSON logging
- **Metrics**: Prometheus Client - Delivery metrics

## API Endpoints (Key Routes)

### Notification Sending
- `POST /api/v1/notifications/send` - Send notification
- `POST /api/v1/notifications/send-email` - Send email
- `POST /api/v1/notifications/send-whatsapp` - Send WhatsApp
- `POST /api/v1/notifications/send-sms` - Send SMS
- `POST /api/v1/notifications/send-batch` - Bulk send

### Notification Management
- `GET /api/v1/notifications` - List notifications
- `GET /api/v1/notifications/{notification_id}` - Get notification details
- `GET /api/v1/notifications/{notification_id}/status` - Check delivery status
- `POST /api/v1/notifications/{notification_id}/retry` - Retry failed notification
- `DELETE /api/v1/notifications/{notification_id}` - Cancel pending notification

### Template Management
- `GET /api/v1/notifications/templates` - List templates
- `GET /api/v1/notifications/templates/{template_id}` - Get template
- `POST /api/v1/notifications/templates` - Create template
- `PUT /api/v1/notifications/templates/{template_id}` - Update template
- `POST /api/v1/notifications/templates/{template_id}/preview` - Preview template
- `DELETE /api/v1/notifications/templates/{template_id}` - Delete template

### User Preferences
- `GET /api/v1/notifications/preferences` - Get user preferences
- `PUT /api/v1/notifications/preferences` - Update preferences
- `POST /api/v1/notifications/preferences/opt-out` - Opt out of notifications
- `POST /api/v1/notifications/preferences/opt-in` - Opt in to notifications

### Analytics
- `GET /api/v1/notifications/analytics/dashboard` - Notification dashboard
- `GET /api/v1/notifications/analytics/delivery-rates` - Delivery success rates
- `GET /api/v1/notifications/analytics/channel-performance` - Channel comparison
- `GET /api/v1/notifications/analytics/engagement` - User engagement metrics

### Webhooks
- `POST /api/v1/notifications/webhooks/twilio` - Twilio status webhook
- `POST /api/v1/notifications/webhooks/sendgrid` - SendGrid webhook
- `POST /api/v1/notifications/webhooks/ses` - Amazon SES webhook

## Database Schema

### Main Tables

**notifications**
```sql
CREATE TABLE notifications (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    notification_type VARCHAR(100) NOT NULL,     -- alert, report, customer, system
    priority VARCHAR(50) NOT NULL,               -- critical, high, medium, low
    channel VARCHAR(50) NOT NULL,                -- email, whatsapp, sms
    status VARCHAR(50) DEFAULT 'pending',        -- pending, queued, sent, delivered, failed, cancelled

    -- Recipient
    recipient_user_id UUID,
    recipient_name VARCHAR(255),
    recipient_email VARCHAR(255),
    recipient_phone VARCHAR(50),

    -- Content
    subject VARCHAR(500),
    message_body TEXT NOT NULL,
    template_id UUID,
    template_variables JSONB,

    -- Attachments
    attachments JSONB,                           -- Array of attachment URLs

    -- Delivery
    scheduled_at TIMESTAMP,
    sent_at TIMESTAMP,
    delivered_at TIMESTAMP,
    read_at TIMESTAMP,
    failed_at TIMESTAMP,
    failure_reason TEXT,
    retry_count INTEGER DEFAULT 0,
    max_retries INTEGER DEFAULT 3,

    -- External IDs
    external_message_id VARCHAR(255),            -- Twilio SID, SendGrid message ID
    external_status VARCHAR(100),

    -- Tracking
    opened BOOLEAN DEFAULT FALSE,
    clicked BOOLEAN DEFAULT FALSE,
    open_count INTEGER DEFAULT 0,
    click_count INTEGER DEFAULT 0,

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_notifications_tenant_status (tenant_id, status),
    INDEX idx_notifications_recipient (recipient_user_id),
    INDEX idx_notifications_scheduled (scheduled_at) WHERE status = 'pending'
);
```

**notification_templates**
```sql
CREATE TABLE notification_templates (
    id UUID PRIMARY KEY,
    tenant_id UUID,                              -- NULL for global templates
    template_name VARCHAR(255) NOT NULL,
    template_code VARCHAR(100) NOT NULL,         -- Unique code for reference
    template_type VARCHAR(100) NOT NULL,         -- alert, report, customer, system
    channel VARCHAR(50) NOT NULL,                -- email, whatsapp, sms, all

    -- Content
    subject_template TEXT,
    body_template TEXT NOT NULL,
    html_body_template TEXT,                     -- For email

    -- Variables
    required_variables JSONB,                    -- Array of required variable names
    sample_variables JSONB,                      -- Sample data for preview

    -- Configuration
    language VARCHAR(10) DEFAULT 'es',           -- es, en, ca
    is_active BOOLEAN DEFAULT TRUE,
    is_system_template BOOLEAN DEFAULT FALSE,    -- Cannot be modified by users
    version INTEGER DEFAULT 1,

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(tenant_id, template_code, channel)
);
```

**notification_preferences**
```sql
CREATE TABLE notification_preferences (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    user_id UUID NOT NULL,

    -- Channel preferences
    email_enabled BOOLEAN DEFAULT TRUE,
    whatsapp_enabled BOOLEAN DEFAULT TRUE,
    sms_enabled BOOLEAN DEFAULT TRUE,
    preferred_channel VARCHAR(50) DEFAULT 'email',

    -- Notification type preferences
    critical_alerts_enabled BOOLEAN DEFAULT TRUE,
    high_priority_enabled BOOLEAN DEFAULT TRUE,
    medium_priority_enabled BOOLEAN DEFAULT TRUE,
    low_priority_enabled BOOLEAN DEFAULT TRUE,

    -- Timing preferences
    quiet_hours_start TIME,                      -- e.g., 22:00
    quiet_hours_end TIME,                        -- e.g., 08:00
    weekend_notifications BOOLEAN DEFAULT TRUE,

    -- Specific notification types
    stockout_alerts BOOLEAN DEFAULT TRUE,
    quality_alerts BOOLEAN DEFAULT TRUE,
    forecast_updates BOOLEAN DEFAULT TRUE,
    daily_summary BOOLEAN DEFAULT TRUE,
    weekly_report BOOLEAN DEFAULT TRUE,

    -- Contact details
    email_address VARCHAR(255),
    phone_number VARCHAR(50),
    whatsapp_number VARCHAR(50),
    whatsapp_opt_in BOOLEAN DEFAULT FALSE,
    whatsapp_opt_in_date TIMESTAMP,

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(tenant_id, user_id)
);
```

**notification_delivery_log**
```sql
CREATE TABLE notification_delivery_log (
    id UUID PRIMARY KEY,
    notification_id UUID REFERENCES notifications(id) ON DELETE CASCADE,
    attempt_number INTEGER NOT NULL,
    channel VARCHAR(50) NOT NULL,
    status VARCHAR(50) NOT NULL,                 -- success, failed, bounced
    status_code VARCHAR(50),
    status_message TEXT,
    provider_response JSONB,
    attempted_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_delivery_log_notification (notification_id)
);
```

**notification_events**
```sql
CREATE TABLE notification_events (
    id UUID PRIMARY KEY,
    notification_id UUID REFERENCES notifications(id) ON DELETE CASCADE,
    event_type VARCHAR(50) NOT NULL,             -- opened, clicked, bounced, complained
    event_data JSONB,
    user_agent TEXT,
    ip_address INET,
    occurred_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_events_notification (notification_id),
    INDEX idx_events_type (notification_id, event_type)
);
```

**notification_costs**
```sql
CREATE TABLE notification_costs (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    month DATE NOT NULL,                         -- First day of month
    channel VARCHAR(50) NOT NULL,

    -- Volume
    notifications_sent INTEGER DEFAULT 0,
    notifications_delivered INTEGER DEFAULT 0,

    -- Costs (in euros)
    estimated_cost DECIMAL(10, 4) DEFAULT 0.0000,

    calculated_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(tenant_id, month, channel)
);
```

### Indexes for Performance
```sql
CREATE INDEX idx_notifications_tenant_priority ON notifications(tenant_id, priority, status);
CREATE INDEX idx_notifications_sent_at ON notifications(sent_at DESC);
CREATE INDEX idx_templates_tenant_active ON notification_templates(tenant_id, is_active);
CREATE INDEX idx_preferences_user ON notification_preferences(user_id);
CREATE INDEX idx_delivery_log_status ON notification_delivery_log(status, attempted_at DESC);
```

## Business Logic Examples

### Send Email Notification
```python
async def send_email_notification(
    tenant_id: UUID,
    recipient_email: str,
    recipient_name: str,
    subject: str,
    body: str,
    html_body: str = None,
    priority: str = 'medium',
    attachments: list = None
) -> Notification:
    """
    Send email notification via SMTP.
    """
    # Create notification record
    notification = Notification(
        tenant_id=tenant_id,
        notification_type='email',
        priority=priority,
        channel='email',
        status='queued',
        recipient_name=recipient_name,
        recipient_email=recipient_email,
        subject=subject,
        message_body=body,
        attachments=attachments
    )
    db.add(notification)
    await db.flush()

    try:
        # Configure SMTP
        smtp_config = await get_smtp_config(tenant_id)

        # Create email message
        from email.mime.multipart import MIMEMultipart
        from email.mime.text import MIMEText
        from email.mime.base import MIMEBase
        from email import encoders

        msg = MIMEMultipart('alternative')
        msg['From'] = smtp_config.from_address
        msg['To'] = recipient_email
        msg['Subject'] = subject

        # Add plain text body
        part1 = MIMEText(body, 'plain', 'utf-8')
        msg.attach(part1)

        # Add HTML body if provided
        if html_body:
            part2 = MIMEText(html_body, 'html', 'utf-8')
            msg.attach(part2)

        # Add attachments
        if attachments:
            for attachment in attachments:
                part = MIMEBase('application', 'octet-stream')
                with open(attachment['path'], 'rb') as file:
                    part.set_payload(file.read())
                encoders.encode_base64(part)
                part.add_header(
                    'Content-Disposition',
                    f'attachment; filename= {attachment["filename"]}'
                )
                msg.attach(part)

        # Send email
        import smtplib
        with smtplib.SMTP(smtp_config.host, smtp_config.port) as server:
            if smtp_config.use_tls:
                server.starttls()
            if smtp_config.username and smtp_config.password:
                server.login(smtp_config.username, smtp_config.password)

            server.send_message(msg)

        # Update notification status
        notification.status = 'sent'
        notification.sent_at = datetime.utcnow()

        # Log delivery
        log = NotificationDeliveryLog(
            notification_id=notification.id,
            attempt_number=1,
            channel='email',
            status='success'
        )
        db.add(log)

        await db.commit()

        logger.info("Email sent successfully",
                   notification_id=str(notification.id),
                   recipient=recipient_email)

        return notification

    except Exception as e:
        notification.status = 'failed'
        notification.failed_at = datetime.utcnow()
        notification.failure_reason = str(e)
        notification.retry_count += 1

        # Log failure
        log = NotificationDeliveryLog(
            notification_id=notification.id,
            attempt_number=notification.retry_count,
            channel='email',
            status='failed',
            status_message=str(e)
        )
        db.add(log)

        await db.commit()

        logger.error("Email send failed",
                    notification_id=str(notification.id),
                    error=str(e))

        # Retry if within limits
        if notification.retry_count < notification.max_retries:
            await schedule_retry(notification.id, delay_minutes=5)

        raise
```

### Send WhatsApp Notification
```python
async def send_whatsapp_notification(
    tenant_id: UUID,
    recipient_phone: str,
    recipient_name: str,
    message: str,
    priority: str = 'high',
    template_name: str = None,
    template_variables: dict = None
) -> Notification:
    """
    Send WhatsApp notification via Twilio.
    """
    # Check user opt-in
    preferences = await get_user_preferences_by_phone(tenant_id, recipient_phone)
    if not preferences or not preferences.whatsapp_opt_in:
        raise ValueError("User has not opted in to WhatsApp notifications")

    # Create notification record
    notification = Notification(
        tenant_id=tenant_id,
        notification_type='alert',
        priority=priority,
        channel='whatsapp',
        status='queued',
        recipient_name=recipient_name,
        recipient_phone=recipient_phone,
        message_body=message
    )
    db.add(notification)
    await db.flush()

    try:
        # Configure Twilio
        from twilio.rest import Client

        twilio_config = await get_twilio_config(tenant_id)
        client = Client(twilio_config.account_sid, twilio_config.auth_token)

        # Format phone number (E.164 format)
        formatted_phone = format_phone_e164(recipient_phone)

        # Send WhatsApp message
        if template_name:
            # Use WhatsApp template (pre-approved)
            twilio_message = client.messages.create(
                from_=f'whatsapp:{twilio_config.whatsapp_number}',
                to=f'whatsapp:{formatted_phone}',
                content_sid=template_name,
                content_variables=json.dumps(template_variables) if template_variables else None
            )
        else:
            # Send freeform message
            twilio_message = client.messages.create(
                from_=f'whatsapp:{twilio_config.whatsapp_number}',
                to=f'whatsapp:{formatted_phone}',
                body=message
            )

        # Update notification status
        notification.status = 'sent'
        notification.sent_at = datetime.utcnow()
        notification.external_message_id = twilio_message.sid
        notification.external_status = twilio_message.status

        # Log delivery
        log = NotificationDeliveryLog(
            notification_id=notification.id,
            attempt_number=1,
            channel='whatsapp',
            status='success',
            status_code=twilio_message.status,
            provider_response={'sid': twilio_message.sid}
        )
        db.add(log)

        await db.commit()

        logger.info("WhatsApp sent successfully",
                   notification_id=str(notification.id),
                   recipient=recipient_phone,
                   twilio_sid=twilio_message.sid)

        return notification

    except Exception as e:
        notification.status = 'failed'
        notification.failed_at = datetime.utcnow()
        notification.failure_reason = str(e)
        notification.retry_count += 1

        log = NotificationDeliveryLog(
            notification_id=notification.id,
            attempt_number=notification.retry_count,
            channel='whatsapp',
            status='failed',
            status_message=str(e)
        )
        db.add(log)

        await db.commit()

        logger.error("WhatsApp send failed",
                    notification_id=str(notification.id),
                    error=str(e))

        # Fallback to SMS if critical
        if priority == 'critical' and notification.retry_count >= notification.max_retries:
            await send_sms_notification(
                tenant_id, recipient_phone, recipient_name, message, priority
            )

        raise

def format_phone_e164(phone: str) -> str:
    """
    Format phone number to E.164 standard (e.g., +34612345678).
    """
    import phonenumbers

    # Parse phone number (assume Spain +34 if no country code)
    try:
        parsed = phonenumbers.parse(phone, 'ES')
        return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
    except:
        # If parsing fails, return as-is
        return phone
```

### Template Rendering
```python
async def render_notification_template(
    template_id: UUID,
    variables: dict
) -> dict:
    """
    Render notification template with variables.
    """
    # Get template
    template = await db.get(NotificationTemplate, template_id)
    if not template:
        raise ValueError("Template not found")

    # Validate required variables
    required_vars = template.required_variables or []
    missing_vars = [v for v in required_vars if v not in variables]
    if missing_vars:
        raise ValueError(f"Missing required variables: {', '.join(missing_vars)}")

    # Render subject
    from jinja2 import Template

    subject = None
    if template.subject_template:
        subject_template = Template(template.subject_template)
        subject = subject_template.render(**variables)

    # Render body
    body_template = Template(template.body_template)
    body = body_template.render(**variables)

    # Render HTML body if available
    html_body = None
    if template.html_body_template:
        html_template = Template(template.html_body_template)
        html_body = html_template.render(**variables)

    return {
        'subject': subject,
        'body': body,
        'html_body': html_body,
        'template_name': template.template_name,
        'channel': template.channel
    }
```

### Smart Channel Selection
```python
async def send_smart_notification(
    tenant_id: UUID,
    user_id: UUID,
    notification_type: str,
    priority: str,
    subject: str,
    message: str,
    template_id: UUID = None,
    template_variables: dict = None
) -> Notification:
    """
    Send notification via optimal channel based on priority and user preferences.
    """
    # Get user preferences
    preferences = await get_user_preferences(tenant_id, user_id)
    user = await get_user(user_id)

    # Determine channel based on priority and preferences
    channel = None

    if priority == 'critical':
        # Critical: WhatsApp if enabled, else SMS, else email
        if preferences.whatsapp_enabled and preferences.whatsapp_opt_in:
            channel = 'whatsapp'
        elif preferences.sms_enabled:
            channel = 'sms'
        else:
            channel = 'email'
    elif priority == 'high':
        # High: Preferred channel if enabled
        if preferences.preferred_channel == 'whatsapp' and preferences.whatsapp_enabled:
            channel = 'whatsapp'
        elif preferences.preferred_channel == 'sms' and preferences.sms_enabled:
            channel = 'sms'
        else:
            channel = 'email'
    else:
        # Medium/Low: Email default
        channel = 'email'

    # Check quiet hours
    if not priority == 'critical':
        if await is_quiet_hours(preferences):
            # Delay notification until quiet hours end
            send_at = await calculate_quiet_hours_end(preferences)
            logger.info("Delaying notification due to quiet hours",
                       user_id=str(user_id),
                       send_at=send_at)
            return await schedule_notification(
                tenant_id, user_id, channel, subject, message,
                scheduled_at=send_at
            )

    # Send via selected channel
    if channel == 'whatsapp':
        return await send_whatsapp_notification(
            tenant_id,
            preferences.whatsapp_number or user.phone,
            user.name,
            message,
            priority,
            template_variables=template_variables
        )
    elif channel == 'sms':
        return await send_sms_notification(
            tenant_id,
            user.phone,
            user.name,
            message,
            priority
        )
    else:  # email
        # Render template if provided
        if template_id:
            rendered = await render_notification_template(template_id, template_variables)
            subject = rendered['subject']
            message = rendered['body']
            html_body = rendered['html_body']
        else:
            html_body = None

        return await send_email_notification(
            tenant_id,
            preferences.email_address or user.email,
            user.name,
            subject,
            message,
            html_body=html_body,
            priority=priority
        )
```

## Events & Messaging

### Published Events (RabbitMQ)

**Exchange**: `notifications`
**Routing Keys**: `notifications.sent`, `notifications.failed`, `notifications.delivered`

**Notification Sent Event**
```json
{
    "event_type": "notification_sent",
    "tenant_id": "uuid",
    "notification_id": "uuid",
    "channel": "whatsapp",
    "priority": "critical",
    "recipient_name": "Juan García",
    "notification_type": "stockout_alert",
    "timestamp": "2025-11-06T09:00:00Z"
}
```

**Notification Failed Event**
```json
{
    "event_type": "notification_failed",
    "tenant_id": "uuid",
    "notification_id": "uuid",
    "channel": "email",
    "priority": "high",
    "failure_reason": "SMTP connection timeout",
    "retry_count": 2,
    "max_retries": 3,
    "will_retry": true,
    "timestamp": "2025-11-06T10:30:00Z"
}
```

**Notification Delivered Event**
```json
{
    "event_type": "notification_delivered",
    "tenant_id": "uuid",
    "notification_id": "uuid",
    "channel": "whatsapp",
    "delivered_at": "2025-11-06T09:01:00Z",
    "read_at": "2025-11-06T09:02:00Z",
    "delivery_time_seconds": 60,
    "timestamp": "2025-11-06T09:02:00Z"
}
```

### Consumed Events

**From Procurement Service (🆕)**
- **Purchase Order Approved** (`po.approved`) - Automatically sends professional email notification to supplier with PO details, line items, and delivery requirements
- **Purchase Order Rejected** (`po.rejected`) - Notifies stakeholders when a PO is rejected with reason
- **Purchase Order Sent** (`po.sent_to_supplier`) - Confirms PO dispatch to supplier
- **Delivery Received** (`delivery.received`) - Notifies relevant parties when deliveries are received

**From Other Services**
- **From Alert Processor**: Alert events trigger notifications
- **From Orchestrator**: Daily summaries, scheduled reports
- **From Orders**: Order confirmations, delivery updates
- **From Production**: Quality issue alerts, batch completion

## Custom Metrics (Prometheus)

```python
# Notification metrics
notifications_sent_total = Counter(
    'notifications_sent_total',
    'Total notifications sent',
    ['tenant_id', 'channel', 'priority', 'status']
)

notification_delivery_time_seconds = Histogram(
    'notification_delivery_time_seconds',
    'Time from creation to delivery',
    ['tenant_id', 'channel'],
    buckets=[1, 5, 10, 30, 60, 300, 600]
)

notification_delivery_rate = Gauge(
    'notification_delivery_rate_percentage',
    'Notification delivery success rate',
    ['tenant_id', 'channel']
)

notification_costs_euros = Counter(
    'notification_costs_euros_total',
    'Total notification costs',
    ['tenant_id', 'channel']
)

email_open_rate = Gauge(
    'email_open_rate_percentage',
    'Email open rate',
    ['tenant_id', 'template_type']
)
```

## Configuration

### Environment Variables

**Service Configuration:**
- `PORT` - Service port (default: 8015)
- `DATABASE_URL` - PostgreSQL connection string
- `REDIS_URL` - Redis connection string
- `RABBITMQ_URL` - RabbitMQ connection string

**Email (SMTP) Configuration:**
- `SMTP_HOST` - SMTP server host
- `SMTP_PORT` - SMTP server port (default: 587)
- `SMTP_USERNAME` - SMTP username (optional for trusted relay)
- `SMTP_PASSWORD` - SMTP password (optional for trusted relay)
- `SMTP_FROM_ADDRESS` - From email address
- `SMTP_USE_TLS` - Enable TLS (default: true)
- `SMTP_REQUIRE_AUTH` - Require SMTP authentication (default: false for trusted relay)

**Twilio Configuration:**
- `TWILIO_ACCOUNT_SID` - Twilio account SID
- `TWILIO_AUTH_TOKEN` - Twilio auth token
- `TWILIO_WHATSAPP_NUMBER` - WhatsApp sender number (format: +1234567890)
- `TWILIO_SMS_NUMBER` - SMS sender number

**Delivery Configuration:**
- `MAX_RETRY_ATTEMPTS` - Maximum retry attempts (default: 3)
- `RETRY_DELAY_MINUTES` - Delay between retries (default: 5)
- `ENABLE_QUIET_HOURS` - Respect user quiet hours (default: true)
- `BATCH_SIZE` - Bulk sending batch size (default: 100)

**Cost Configuration:**
- `WHATSAPP_COST_PER_MESSAGE` - Cost per WhatsApp (default: 0.005 EUR)
- `SMS_COST_PER_MESSAGE` - Cost per SMS (default: 0.08 EUR)
- `EMAIL_COST_PER_MESSAGE` - Cost per email (default: 0.001 EUR)

## Mailu Integration

The notification service is designed to work seamlessly with Mailu email servers, especially for internal bakery communications.

### Internal Email Delivery (Recommended)
When sending emails to addresses within the same Mailu domain (e.g., `user@your-bakery.com`), Mailu handles these as internal messages:

- **No SMTP authentication required** - Mailu trusts internal subnet connections
- **Direct inbox delivery** - Messages stay within Mailu's internal mail store
- **Faster delivery** - No external SMTP relay needed
- **Better reliability** - No dependency on external email providers

### Configuration for Mailu

```bash
# Mailu internal configuration (recommended for bakery communications)
export SMTP_HOST=mailu.your-domain.com  # or mailu.internal if using internal DNS
export SMTP_PORT=587                    # Standard submission port
export SMTP_REQUIRE_AUTH=false         # No authentication needed for internal relay
export DEFAULT_FROM_EMAIL=noreply@your-bakery.com

# Optional: If using Mailu's webmail interface
export SMTP_PORT=25                   # Alternative: use port 25 for internal relay
```

### Mailu Subnet Trust Configuration

For Mailu to accept unauthenticated connections from the notification service:

1. **Configure Mailu's relay networks** in `mailu.env`:
   ```ini
   # Add your Kubernetes service subnet to trusted networks
   RELAYNETS=10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
   ```

2. **Ensure DNS resolution** works between services:
   ```bash
   # From notification service pod, test Mailu connectivity
   nslookup mailu.your-domain.com
   telnet mailu.your-domain.com 587
   ```

3. **Verify internal delivery** with Mailu logs:
   ```bash
   # Check Mailu container logs for internal message processing
   kubectl logs -f mailu-pod-name | grep "internal delivery"
   ```

## Trusted Relay Configuration

The notification service supports both authenticated SMTP and trusted relay modes:

### Trusted Relay Mode (Default)
For internal Mailu servers or other trusted SMTP relays that don't require authentication:

```bash
# No authentication - trusted relay mode (default)
export SMTP_HOST=mailu.example.com
export SMTP_PORT=587
export SMTP_REQUIRE_AUTH=false  # Explicitly disable auth (default is false)
# SMTP_USERNAME and SMTP_PASSWORD can be empty
```

### Authenticated SMTP Mode
For external SMTP services like SendGrid, Amazon SES, or Gmail:

```bash
# With authentication - external SMTP services
export SMTP_HOST=smtp.sendgrid.net
export SMTP_PORT=587
export SMTP_USERNAME=apikey
export SMTP_PASSWORD=your_sendgrid_api_key
export SMTP_REQUIRE_AUTH=true  # Explicitly enable auth
```

The service automatically detects the configuration:
- If `SMTP_REQUIRE_AUTH=false` (default), authentication is skipped
- If `SMTP_REQUIRE_AUTH=true` but credentials are missing, it will fail with clear error
- If credentials are provided but `SMTP_REQUIRE_AUTH=false`, it will still skip authentication

## Development Setup

### Prerequisites
- Python 3.11+
- PostgreSQL 17
- Redis 7.4
- RabbitMQ 4.1
- SMTP server or SendGrid account
- Twilio account (for WhatsApp/SMS)

### Local Development

#### With Mailu (Recommended for Bakery-IA)
```bash
cd services/notification
python -m venv venv
source venv/bin/activate

pip install -r requirements.txt

export DATABASE_URL=postgresql://user:pass@localhost:5432/notification
export REDIS_URL=redis://localhost:6379/0
export RABBITMQ_URL=amqp://guest:guest@localhost:5672/

# Mailu configuration (internal bakery communications)
export SMTP_HOST=mailu.bakery-ia.local  # Internal Mailu service
export SMTP_PORT=587                    # Submission port
export SMTP_REQUIRE_AUTH=false         # No auth needed - Mailu subnet trust
export DEFAULT_FROM_EMAIL=noreply@bakeryforecast.es

# Optional: For testing external emails
export ENABLE_EMAIL_NOTIFICATIONS=true

export TWILIO_ACCOUNT_SID=your_twilio_sid
export TWILIO_AUTH_TOKEN=your_twilio_token

alembic upgrade head
python main.py
```

#### With External SMTP (SendGrid, Gmail, etc.)
```bash
cd services/notification
python -m venv venv
source venv/bin/activate

pip install -r requirements.txt

export DATABASE_URL=postgresql://user:pass@localhost:5432/notification
export REDIS_URL=redis://localhost:6379/0
export RABBITMQ_URL=amqp://guest:guest@localhost:5672/

# External SMTP configuration (SendGrid example)
export SMTP_HOST=smtp.sendgrid.net
export SMTP_PORT=587
export SMTP_USERNAME=apikey
export SMTP_PASSWORD=your_sendgrid_api_key
export SMTP_REQUIRE_AUTH=true  # Explicit authentication required
export DEFAULT_FROM_EMAIL=noreply@yourdomain.com

export TWILIO_ACCOUNT_SID=your_twilio_sid
export TWILIO_AUTH_TOKEN=your_twilio_token

alembic upgrade head
python main.py
```

## Integration Points

### Dependencies
- **SMTP Server** - Email delivery (SendGrid, SES, SMTP)
- **Twilio API** - WhatsApp and SMS delivery
- **Auth Service** - User information
- **PostgreSQL** - Notification history
- **Redis** - Template caching
- **RabbitMQ** - Alert consumption and procurement event processing (🆕)
- **Procurement Service** - Purchase order events (🆕)

### Dependents
- **Alert Processor** - Sends alerts via notifications
- **Orders Service** - Customer order notifications
- **Orchestrator** - Daily summaries and reports
- **All Services** - Critical alerts routing
- **Frontend Dashboard** - Notification preferences UI
- **Suppliers** - Receive PO notifications via email (🆕)

## Business Value for VUE Madrid

### Problem Statement
Spanish bakeries struggle with:
- Delayed awareness of critical issues (stockouts discovered too late)
- Manual phone calls and texts consuming staff time
- No systematic customer communication (order confirmations)
- Expensive SMS costs (€0.08/message in Spain)
- No record of communications sent
- Staff missing important alerts

### Solution
Bakery-IA Notification Service provides:
- **Real-Time Alerts**: WhatsApp notifications within seconds
- **Multi-Channel**: Email for reports, WhatsApp for urgent
- **Cost Effective**: WhatsApp 90% cheaper than SMS
- **Customer Communication**: Professional order updates
- **Communication History**: Complete audit trail
- **Smart Routing**: Right message, right channel, right time

### Quantifiable Impact

**Cost Savings:**
- €50-150/month using WhatsApp vs. SMS (90% cost reduction)
- 3-5 hours/week saved on manual phone calls/texts (€180-300/month)
- **Total: €230-450/month savings**

**Operational Efficiency:**
- 90% faster response to critical issues (minutes vs. hours)
- 50-70% faster issue resolution with immediate awareness
- 99%+ alert delivery reliability
- 24/7 notification delivery (no manual intervention)

**Customer Satisfaction:**
- 20-30% improvement with order confirmation/updates
- Professional brand image with branded emails
- Customer choice of email or WhatsApp
- Personalized communication

### Target Market Fit (Spanish Bakeries)
- **WhatsApp Culture**: Spain has 91% WhatsApp penetration rate
- **Mobile First**: Bakery owners/managers always on mobile
- **Cost Sensitive**: SMS costs high in Spain (€0.08 vs. €0.005 WhatsApp)
- **Communication Style**: Spanish business culture values personal touch
- **GDPR Compliance**: Opt-in management meets EU regulations

### ROI Calculation
**Investment**: €0 additional (included in subscription) + Twilio costs
**Cost Savings**: €230-450/month (vs. SMS + manual communication)
**Operational Value**: 50-70% faster issue resolution
**Monthly Value**: €230-450 savings + operational efficiency
**Annual ROI**: €2,760-5,400 value per bakery
**Payback**: Immediate (cost savings from day one)

### Competitive Advantage
- **WhatsApp Integration**: Few Spanish bakery platforms offer WhatsApp
- **Multi-Channel**: Flexibility competitors don't provide
- **Smart Routing**: Auto-select channel by urgency/preference
- **Cost Effective**: 90% cheaper than SMS-only solutions
- **GDPR Compliant**: Built-in opt-in management

---

**Copyright © 2025 Bakery-IA. All rights reserved.**
