fix(email): use Brevo API for booking emails instead of SMTP - #175
Merged
Conversation
- Add generic send_email_via_brevo() function in email_utils.py - Update send_booking_confirmation_email to use Brevo - Update send_booking_rejection_email to use Brevo - Update send_booking_reminder to use Brevo - Skip email sending for temporary emails (@reservo.temp, @reservo.local) - Add better error handling to schedule_telegram_booking_reminders task - Update tests to mock send_email_via_brevo Fixes BACKEND-1H: Network unreachable error when sending confirmation emails Fixes BACKEND-25: Improved logging for telegram reminder cron timeout
Upcoming bookings now show nearest first (ascending), past bookings show most recent first (descending). Sorting uses both date and time fields for correct ordering of same-day bookings.
There was a problem hiding this comment.
Pull request overview
Switches booking-related email delivery from Django SMTP (send_mail) to Brevo’s transactional email API to prevent sending failures in environments without SMTP credentials, while also skipping placeholder “phone-only” emails and improving Telegram reminder cron logging/error handling.
Changes:
- Added a generic
send_email_via_brevo()helper and refactored OTP sending to use it. - Updated booking confirmation/rejection/reminder tasks to send via Brevo and to skip
@reservo.temp/@reservo.localplaceholder emails. - Improved logging/error handling in
schedule_telegram_booking_remindersand updated booking task tests to mock Brevo sending.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| backend/apps/users/email_utils.py | Introduces generic Brevo email sender and refactors OTP email sending onto it. |
| backend/apps/bookings/tasks.py | Replaces SMTP email sends with Brevo, adds temp-email detection, and enhances Telegram reminder scheduling logging. |
| backend/apps/bookings/tests/test_tasks.py | Updates mocks/assertions to validate Brevo send usage and adds coverage for skipping temp emails. |
Comments suppressed due to low confidence (1)
backend/apps/bookings/tasks.py:165
- The task retries on any exception. If
send_email_via_brevoraises a configuration error (e.g., missingBREVO_API_KEY), retries won't succeed and will just generate noise. Consider catching non-retryable exceptions (likeValueError) and returning/logging once without callingself.retry(same pattern applies to the other booking email tasks).
except Exception as exc:
logger.error(
f"Failed to send confirmation email for booking {booking_id}: {exc}"
)
raise self.retry(exc=exc, countdown=60)
You can also share your feedback on Copilot code review. Take the survey.
Comment on lines
+399
to
+403
| <div class="content"> | ||
| <p>Hi {guest_name},</p> | ||
| <p>This is a friendly reminder about your upcoming reservation at | ||
| <strong>{restaurant_name}</strong>.</p> | ||
| <div class="details"> |
Comment on lines
+1069
to
+1077
| # Handle midnight crossing | ||
| if min_time > max_time: | ||
| # Window crosses midnight - query for today's late bookings | ||
| bookings = Booking.objects.filter( | ||
| status=Booking.Status.CONFIRMED, | ||
| reminder_tg_sent=False, | ||
| date=today, | ||
| start_time__gte=min_time, | ||
| ) |
Comment on lines
223
to
226
| logger.info( | ||
| f"OTP email sent via Brevo to {email} in {language}. Message ID: {api_response.message_id}" | ||
| f"Email sent via Brevo to {to_email}. " | ||
| f"Subject: {subject[:50]}. Message ID: {api_response.message_id}" | ||
| ) |
Comment on lines
229
to
+234
| except ApiException as e: | ||
| logger.error(f"Brevo API error when sending OTP to {email}: {e}") | ||
| logger.error(f"Brevo API error when sending email to {to_email}: {e}") | ||
| raise | ||
|
|
||
| except Exception as e: | ||
| logger.error(f"Unexpected error sending OTP via Brevo to {email}: {e}") | ||
| logger.error(f"Unexpected error sending email via Brevo to {to_email}: {e}") |
| Returns: | ||
| bool: True if sent successfully, False otherwise | ||
|
|
||
| Raises: |
Comment on lines
+113
to
+116
| <p>Hi {guest_name},</p> | ||
| <p>Your booking at <strong>{booking.restaurant.name}</strong> | ||
| has been confirmed!</p> | ||
| <div class="details"> |
Comment on lines
+246
to
+250
| reason_html = ( | ||
| f"<p><strong>Reason:</strong> {booking.cancellation_reason}</p>" | ||
| if booking.cancellation_reason | ||
| else "" | ||
| ) |
String comparison failed for mixed time formats (HH:mm vs HH:mm:ss). Now parses dates and times into actual Date/Time objects before comparing, ensuring correct sort order regardless of format.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Changes
send_email_via_brevo()function inemail_utils.pyis_real_email()helper to detect and skip temporary emails like@reservo.tempschedule_telegram_booking_remindersRoot Cause
Staging has Brevo API configured but no SMTP credentials (
EMAIL_HOST_USER,EMAIL_HOST_PASSWORD). The booking tasks were using Django'ssend_mail()which goes through SMTP, causing "Network is unreachable" errors.Test plan
@reservo.tempemails) don't trigger email errors