feat(notifications): add test-send endpoint for single-address preview
POST /api/v1/notifications/schedules/<pk>/test-send/ accepts {"email": "..."},
renders the schedule's email, delivers to that address only with [TEST] prefix.
Does not touch last_run_at or last_status.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,7 @@ urlpatterns = [
|
|||||||
path('notifications/schedules/<int:pk>/recipients/', views.NotificationRecipientView.as_view(), name='notification-recipient-create'),
|
path('notifications/schedules/<int:pk>/recipients/', views.NotificationRecipientView.as_view(), name='notification-recipient-create'),
|
||||||
path('notifications/schedules/<int:pk>/recipients/<int:rid>/', views.NotificationRecipientDetailView.as_view(), name='notification-recipient-detail'),
|
path('notifications/schedules/<int:pk>/recipients/<int:rid>/', views.NotificationRecipientDetailView.as_view(), name='notification-recipient-detail'),
|
||||||
path('notifications/schedules/<int:pk>/send-now/', views.NotificationScheduleSendNowView.as_view(), name='notification-schedule-send-now'),
|
path('notifications/schedules/<int:pk>/send-now/', views.NotificationScheduleSendNowView.as_view(), name='notification-schedule-send-now'),
|
||||||
|
path('notifications/schedules/<int:pk>/test-send/', views.NotificationScheduleTestSendView.as_view(), name='notification-schedule-test-send'),
|
||||||
|
|
||||||
# Ad Control
|
# Ad Control
|
||||||
path('ad-control/', include('ad_control.urls')),
|
path('ad-control/', include('ad_control.urls')),
|
||||||
|
|||||||
@@ -2783,3 +2783,51 @@ class NotificationScheduleSendNowView(APIView):
|
|||||||
'recipientCount': recipient_count,
|
'recipientCount': recipient_count,
|
||||||
'schedule': _serialize_schedule(schedule),
|
'schedule': _serialize_schedule(schedule),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationScheduleTestSendView(APIView):
|
||||||
|
"""Send a preview of this schedule's email to a single address.
|
||||||
|
|
||||||
|
Does NOT update last_run_at / last_status — purely for previewing content.
|
||||||
|
"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request, pk):
|
||||||
|
from notifications.models import NotificationSchedule
|
||||||
|
from notifications.emails import BUILDERS
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
schedule = get_object_or_404(NotificationSchedule, pk=pk)
|
||||||
|
|
||||||
|
email = (request.data.get('email') or '').strip().lower()
|
||||||
|
if not email:
|
||||||
|
return Response({'error': 'email is required'}, status=400)
|
||||||
|
|
||||||
|
builder = BUILDERS.get(schedule.notification_type)
|
||||||
|
if builder is None:
|
||||||
|
return Response(
|
||||||
|
{'error': f'No builder for type: {schedule.notification_type}'},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subject, html = builder(schedule)
|
||||||
|
subject = f'[TEST] {subject}'
|
||||||
|
msg = EmailMessage(
|
||||||
|
subject=subject,
|
||||||
|
body=html,
|
||||||
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
to=[email],
|
||||||
|
)
|
||||||
|
msg.content_subtype = 'html'
|
||||||
|
msg.send(fail_silently=False)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log('error', f'Test-send failed for schedule #{pk}: {exc}',
|
||||||
|
request=request, user=request.user)
|
||||||
|
return Response({'error': str(exc)}, status=500)
|
||||||
|
|
||||||
|
log('info', f'Test email sent for schedule #{pk} → {email}',
|
||||||
|
request=request, user=request.user)
|
||||||
|
return Response({'ok': True, 'sentTo': email})
|
||||||
|
|||||||
Reference in New Issue
Block a user