Ashgen12 commited on
Commit
0d930d1
·
verified ·
1 Parent(s): 015d638

Brevo + faster drafts + adaptive chart + fuzzy lookup

Browse files
backend/services/notification_service.py CHANGED
@@ -16,7 +16,6 @@ from __future__ import annotations
16
  import base64
17
  import logging
18
  import smtplib
19
- import uuid
20
  from dataclasses import dataclass
21
  from datetime import datetime
22
  from email.message import EmailMessage
@@ -169,6 +168,51 @@ def _send_via_resend(
169
  return False, f"Resend rejected the request ({response.status_code}): {response.text[:240]}"
170
 
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  def send_interview_invite(
173
  *,
174
  candidate_name: str,
@@ -197,9 +241,32 @@ def send_interview_invite(
197
  )
198
 
199
  sender = settings.smtp_from or settings.smtp_user or "onboarding@resend.dev"
200
- resend_failure: str | None = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- # Preferred: Resend (HTTPS — works even where SMTP is blocked).
 
203
  if settings.resend_api_key:
204
  try:
205
  ok, detail = _send_via_resend(
@@ -211,11 +278,13 @@ def send_interview_invite(
211
  )
212
  if ok:
213
  return InviteSendResult(sent=True, delivery="resend", detail=detail, meet_url=meet, recipients=recipients)
214
- resend_failure = detail
215
- logger.warning("Resend send failed, falling back to SMTP if configured: %s", detail)
216
  except Exception as exc:
217
- resend_failure = f"Resend transport raised: {exc}"
218
- logger.warning(resend_failure)
 
 
219
 
220
  # Fallback: SMTP (only works on hosts that allow outbound 587/465 — not HF Spaces free).
221
  if not settings.has_smtp_config or not settings.smtp_from:
@@ -318,8 +387,25 @@ def send_plain_email(
318
  )
319
 
320
  sender = settings.smtp_from or settings.smtp_user or "onboarding@resend.dev"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
- # Preferred: Resend HTTPS (works on HF Spaces).
323
  if settings.resend_api_key:
324
  try:
325
  ok, detail = _send_via_resend(
 
16
  import base64
17
  import logging
18
  import smtplib
 
19
  from dataclasses import dataclass
20
  from datetime import datetime
21
  from email.message import EmailMessage
 
168
  return False, f"Resend rejected the request ({response.status_code}): {response.text[:240]}"
169
 
170
 
171
+ def _send_via_brevo(
172
+ *,
173
+ sender_email: str,
174
+ sender_name: str,
175
+ recipients: list[str],
176
+ subject: str,
177
+ body: str,
178
+ ics_content: str | None = None,
179
+ ) -> tuple[bool, str]:
180
+ """Send an email through Brevo's HTTPS API.
181
+
182
+ Brevo only requires a single verified sender email (no DNS verification),
183
+ and lets you send TO any recipient on the free tier (300/day). Ideal when
184
+ demo recipients vary.
185
+ """
186
+ settings = get_settings()
187
+ payload: dict = {
188
+ "sender": {"email": sender_email, "name": sender_name},
189
+ "to": [{"email": addr} for addr in recipients],
190
+ "subject": subject,
191
+ "textContent": body,
192
+ }
193
+ if ics_content:
194
+ payload["attachment"] = [
195
+ {
196
+ "name": "interview.ics",
197
+ "content": base64.b64encode(ics_content.encode("utf-8")).decode("ascii"),
198
+ }
199
+ ]
200
+
201
+ response = requests.post(
202
+ "https://api.brevo.com/v3/smtp/email",
203
+ headers={
204
+ "api-key": settings.brevo_api_key or "",
205
+ "Content-Type": "application/json",
206
+ "Accept": "application/json",
207
+ },
208
+ json=payload,
209
+ timeout=20,
210
+ )
211
+ if response.ok:
212
+ return True, f"Email delivered via Brevo to {', '.join(recipients)}."
213
+ return False, f"Brevo rejected the request ({response.status_code}): {response.text[:240]}"
214
+
215
+
216
  def send_interview_invite(
217
  *,
218
  candidate_name: str,
 
241
  )
242
 
243
  sender = settings.smtp_from or settings.smtp_user or "onboarding@resend.dev"
244
+ sender_name = "Recruitment OS"
245
+ transport_failures: list[str] = []
246
+
247
+ # Preferred: Brevo (HTTPS, free 300/day, single sender-email verification —
248
+ # can send to ANY recipient on the free tier).
249
+ if settings.brevo_api_key:
250
+ try:
251
+ ok, detail = _send_via_brevo(
252
+ sender_email=sender,
253
+ sender_name=sender_name,
254
+ recipients=recipients,
255
+ subject=subject,
256
+ body=body,
257
+ ics_content=ics_content,
258
+ )
259
+ if ok:
260
+ return InviteSendResult(sent=True, delivery="brevo", detail=detail, meet_url=meet, recipients=recipients)
261
+ transport_failures.append(f"Brevo: {detail}")
262
+ logger.warning("Brevo send failed: %s", detail)
263
+ except Exception as exc:
264
+ msg = f"Brevo transport raised: {exc}"
265
+ transport_failures.append(msg)
266
+ logger.warning(msg)
267
 
268
+ # Next: Resend (HTTPS — sandbox restricts recipients to the Resend account
269
+ # email until you verify a sending domain).
270
  if settings.resend_api_key:
271
  try:
272
  ok, detail = _send_via_resend(
 
278
  )
279
  if ok:
280
  return InviteSendResult(sent=True, delivery="resend", detail=detail, meet_url=meet, recipients=recipients)
281
+ transport_failures.append(f"Resend: {detail}")
282
+ logger.warning("Resend send failed, falling back if SMTP is configured: %s", detail)
283
  except Exception as exc:
284
+ msg = f"Resend transport raised: {exc}"
285
+ transport_failures.append(msg)
286
+ logger.warning(msg)
287
+ resend_failure = "; ".join(transport_failures) if transport_failures else None
288
 
289
  # Fallback: SMTP (only works on hosts that allow outbound 587/465 — not HF Spaces free).
290
  if not settings.has_smtp_config or not settings.smtp_from:
 
387
  )
388
 
389
  sender = settings.smtp_from or settings.smtp_user or "onboarding@resend.dev"
390
+ sender_name = "Recruitment OS"
391
+
392
+ # Preferred: Brevo (free 300/day, single-sender verification, sends to any recipient).
393
+ if settings.brevo_api_key:
394
+ try:
395
+ ok, detail = _send_via_brevo(
396
+ sender_email=sender,
397
+ sender_name=sender_name,
398
+ recipients=all_recipients,
399
+ subject=subject,
400
+ body=body,
401
+ )
402
+ if ok:
403
+ return GenericSendResult(sent=True, delivery="brevo", detail=detail, recipients=all_recipients)
404
+ logger.warning("Brevo send failed: %s", detail)
405
+ except Exception as exc:
406
+ logger.warning("Brevo transport raised; falling back: %s", exc)
407
 
408
+ # Next: Resend (HTTPS sandbox limited unless domain verified).
409
  if settings.resend_api_key:
410
  try:
411
  ok, detail = _send_via_resend(