Skip to content

fix(email): use Brevo API for booking emails instead of SMTP - #175

Merged
MukhammadIbrokhimov merged 3 commits into
mainfrom
fix/email-brevo-and-telegram-cron
Mar 19, 2026
Merged

fix(email): use Brevo API for booking emails instead of SMTP#175
MukhammadIbrokhimov merged 3 commits into
mainfrom
fix/email-brevo-and-telegram-cron

Conversation

@MukhammadIbrokhimov

Copy link
Copy Markdown
Owner

Summary

  • Fixed booking email sending failures (BACKEND-1H) by switching from SMTP to Brevo API
  • Skip email sending for temporary/placeholder emails (phone-only users)
  • Improved logging for telegram reminder cron task (BACKEND-25)

Changes

  • Added generic send_email_via_brevo() function in email_utils.py
  • Updated booking confirmation, rejection, and reminder emails to use Brevo
  • Added is_real_email() helper to detect and skip temporary emails like @reservo.temp
  • Added better error handling and logging to schedule_telegram_booking_reminders
  • Updated tests to mock the new Brevo function

Root Cause

Staging has Brevo API configured but no SMTP credentials (EMAIL_HOST_USER, EMAIL_HOST_PASSWORD). The booking tasks were using Django's send_mail() which goes through SMTP, causing "Network is unreachable" errors.

Test plan

  • Deploy to staging and create a new booking
  • Verify confirmation email is sent via Brevo (no network errors)
  • Verify phone-only users (with @reservo.temp emails) don't trigger email errors
  • Monitor Sentry for telegram cron task success

- 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
Copilot AI review requested due to automatic review settings March 18, 2026 03:37
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.local placeholder emails.
  • Improved logging/error handling in schedule_telegram_booking_reminders and 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_brevo raises a configuration error (e.g., missing BREVO_API_KEY), retries won't succeed and will just generate noise. Consider catching non-retryable exceptions (like ValueError) and returning/logging once without calling self.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.
@MukhammadIbrokhimov
MukhammadIbrokhimov merged commit 28b038b into main Mar 19, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants