Bounce management

This commit is contained in:
James Pattinson
2025-11-10 16:57:29 +00:00
parent 7fd237c28b
commit 051bd05149
15 changed files with 1198 additions and 9 deletions

View File

@@ -1,8 +1,14 @@
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, EmailStr
from ...services.email_service import email_service
from ...services.bounce_service import bounce_service
from ...api.dependencies import get_admin_user
from ...models.models import User
from typing import Dict, Any, List
from ...core.database import get_db
from sqlalchemy.orm import Session
router = APIRouter()
router = APIRouter()
@@ -21,7 +27,8 @@ class WelcomeEmailRequest(BaseModel):
@router.post("/test-email")
async def send_test_email(
request: TestEmailRequest,
current_user: User = Depends(get_admin_user)
current_user: User = Depends(get_admin_user),
db: Session = Depends(get_db)
):
"""Send a test email (admin only)"""
html_body = f"<html><body><p>{request.message}</p></body></html>"
@@ -29,7 +36,8 @@ async def send_test_email(
to_email=request.to_email,
subject=request.subject,
html_body=html_body,
text_body=request.message
text_body=request.message,
db=db
)
return {"success": True, "result": result}
@@ -45,3 +53,118 @@ async def send_test_welcome_email(
first_name=request.first_name
)
return {"success": True, "result": result}
@router.post("/webhooks/smtp2go/bounce")
async def smtp2go_bounce_webhook(
webhook_data: Dict[str, Any],
db: Session = Depends(get_db)
):
"""
Webhook endpoint for SMTP2GO bounce notifications
This endpoint should be configured in SMTP2GO webhook settings
"""
try:
bounces_created = bounce_service.process_smtp2go_webhook(webhook_data, db)
return {
"success": True,
"bounces_processed": len(bounces_created),
"message": f"Successfully processed {len(bounces_created)} bounce events"
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to process bounce webhook: {str(e)}")
@router.get("/bounces")
async def get_bounce_list(
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_admin_user),
db: Session = Depends(get_db)
):
"""Get list of email bounces (admin only)"""
from ...models.models import EmailBounce
bounces = db.query(EmailBounce).offset(skip).limit(limit).all()
total = db.query(EmailBounce).count()
return {
"bounces": [
{
"id": bounce.id,
"email": bounce.email,
"bounce_type": bounce.bounce_type.value,
"bounce_reason": bounce.bounce_reason,
"bounce_date": bounce.bounce_date.isoformat(),
"is_active": bounce.is_active,
"smtp2go_message_id": bounce.smtp2go_message_id
}
for bounce in bounces
],
"total": total,
"skip": skip,
"limit": limit
}
@router.get("/bounces/stats")
async def get_bounce_statistics(
current_user: User = Depends(get_admin_user),
db: Session = Depends(get_db)
):
"""Get bounce statistics (admin only)"""
stats = bounce_service.get_bounce_statistics(db)
return stats
@router.get("/bounces/{email}")
async def get_bounce_history(
email: str,
current_user: User = Depends(get_admin_user)
):
"""Get bounce history for a specific email (admin only)"""
bounces = bounce_service.get_bounce_history(email)
return {
"email": email,
"bounces": [
{
"id": bounce.id,
"bounce_type": bounce.bounce_type.value,
"bounce_reason": bounce.bounce_reason,
"bounce_date": bounce.bounce_date.isoformat(),
"is_active": bounce.is_active,
"smtp2go_message_id": bounce.smtp2go_message_id
}
for bounce in bounces
]
}
@router.post("/bounces/cleanup")
async def cleanup_old_bounces(
days_old: int = 365,
current_user: User = Depends(get_admin_user),
db: Session = Depends(get_db)
):
"""Clean up old soft bounces (admin only)"""
cleaned_count = bounce_service.cleanup_old_bounces(days_old, db)
return {
"success": True,
"message": f"Cleaned up {cleaned_count} old soft bounce records"
}
@router.delete("/bounces/deactivate/{bounce_id}")
async def deactivate_bounce(
bounce_id: int,
current_user: User = Depends(get_admin_user),
db: Session = Depends(get_db)
):
"""Deactivate a bounce record (mark as resolved) (admin only)"""
success = bounce_service.deactivate_bounce(bounce_id, db)
if not success:
raise HTTPException(status_code=404, detail="Bounce record not found")
return {"success": True, "message": "Bounce record deactivated"}