-
Notifications
You must be signed in to change notification settings - Fork 540
test: add e2e tests for annotation queue flows #4568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6eaecdd
445fc3f
585c6ad
c9d112d
01a6e6d
1706e43
ad6e140
cd01041
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import os | ||
|
|
||
| import sendgrid | ||
| from sendgrid.helpers.mail import Mail | ||
| import smtplib | ||
| from email.mime.multipart import MIMEMultipart | ||
| from email.mime.text import MIMEText | ||
|
|
||
| from fastapi import HTTPException | ||
|
|
||
|
|
@@ -10,16 +10,25 @@ | |
|
|
||
| log = get_logger(__name__) | ||
|
|
||
| # Initialize SendGrid only if enabled | ||
| if env.sendgrid.enabled: | ||
| sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key) | ||
| log.info("✓ SendGrid enabled") | ||
| # Determine which email backend to use (SMTP > SendGrid > no-op) | ||
| _USE_SMTP = env.smtp.enabled | ||
| _USE_SENDGRID = not _USE_SMTP and env.sendgrid.enabled | ||
|
|
||
| if _USE_SMTP: | ||
| log.info( | ||
| "✓ Email enabled via SMTP (%s:%s)", env.smtp.host, env.smtp.port | ||
| ) | ||
| elif _USE_SENDGRID: | ||
| import sendgrid | ||
|
|
||
| _sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key) | ||
| log.info("✓ Email enabled via SendGrid (legacy)") | ||
| else: | ||
| sg = None | ||
| _sg = None | ||
| if env.sendgrid.api_key and not env.sendgrid.from_address: | ||
| log.warn("✗ SendGrid disabled: missing sender email address") | ||
| log.warn("✗ Email disabled: missing sender email address") | ||
| else: | ||
| log.warn("✗ SendGrid disabled") | ||
| log.warn("✗ Email disabled") | ||
|
|
||
|
|
||
| def read_email_template(template_file_path): | ||
|
|
@@ -35,6 +44,46 @@ def read_email_template(template_file_path): | |
| return template_file.read() | ||
|
|
||
|
|
||
| def _send_via_smtp(to_email: str, subject: str, html_content: str, from_email: str) -> None: | ||
| """Send email using SMTP.""" | ||
| msg = MIMEMultipart("alternative") | ||
| msg["Subject"] = subject | ||
| msg["From"] = from_email | ||
| msg["To"] = to_email | ||
| msg.attach(MIMEText(html_content, "html")) | ||
|
|
||
| smtp_host = env.smtp.host | ||
| smtp_port = env.smtp.port | ||
|
|
||
| if env.smtp.use_tls: | ||
| server = smtplib.SMTP(smtp_host, smtp_port) | ||
| server.ehlo() | ||
| server.starttls() | ||
| server.ehlo() | ||
| else: | ||
| server = smtplib.SMTP(smtp_host, smtp_port) | ||
|
|
||
| try: | ||
| if env.smtp.username and env.smtp.password: | ||
| server.login(env.smtp.username, env.smtp.password) | ||
| server.sendmail(from_email, [to_email], msg.as_string()) | ||
| finally: | ||
| server.quit() | ||
|
|
||
|
Comment on lines
+47
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add timeout to SMTP connection to prevent hangs. The SMTP connection lacks a timeout parameter. If the SMTP server is unresponsive, the application will hang indefinitely on the request thread, degrading availability. 🔧 Proposed fix to add timeout- if env.smtp.use_tls:
- server = smtplib.SMTP(smtp_host, smtp_port)
+ smtp_timeout = 30 # seconds
+ if env.smtp.use_tls:
+ server = smtplib.SMTP(smtp_host, smtp_port, timeout=smtp_timeout)
server.ehlo()
server.starttls()
server.ehlo()
else:
- server = smtplib.SMTP(smtp_host, smtp_port)
+ server = smtplib.SMTP(smtp_host, smtp_port, timeout=smtp_timeout) |
||
|
|
||
| def _send_via_sendgrid(to_email: str, subject: str, html_content: str, from_email: str) -> None: | ||
| """Send email using SendGrid (legacy fallback).""" | ||
| from sendgrid.helpers.mail import Mail | ||
|
|
||
| message = Mail( | ||
| from_email=from_email, | ||
| to_emails=to_email, | ||
| subject=subject, | ||
| html_content=html_content, | ||
| ) | ||
| _sg.send(message) | ||
|
|
||
|
|
||
| async def send_email( | ||
| to_email: str, subject: str, html_content: str, from_email: str | ||
| ) -> bool: | ||
|
|
@@ -54,20 +103,15 @@ async def send_email( | |
| HTTPException: If there is an error sending the email. | ||
| """ | ||
|
|
||
| # No-op if SendGrid is disabled | ||
| if not env.sendgrid.enabled: | ||
| log.info(f"[SENDGRID] Email disabled - would send '{subject}' to {to_email}") | ||
| if not _USE_SMTP and not _USE_SENDGRID: | ||
| log.info(f"[EMAIL] Email disabled - would send '{subject}' to {to_email}") | ||
| return True | ||
|
|
||
| message = Mail( | ||
| from_email=from_email, | ||
| to_emails=to_email, | ||
| subject=subject, | ||
| html_content=html_content, | ||
| ) | ||
|
|
||
| try: | ||
| sg.send(message) | ||
| if _USE_SMTP: | ||
| _send_via_smtp(to_email, subject, html_content, from_email) | ||
| else: | ||
| _send_via_sendgrid(to_email, subject, html_content, from_email) | ||
| return True | ||
| except Exception as e: | ||
| raise HTTPException(status_code=500, detail=str(e)) | ||
|
Comment on lines
116
to
117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refine exception handling to preserve error context. Catching all exceptions with ♻️ Suggested improvement try:
if _USE_SMTP:
_send_via_smtp(to_email, subject, html_content, from_email)
else:
_send_via_sendgrid(to_email, subject, html_content, from_email)
return True
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
+ except smtplib.SMTPException as e:
+ log.error(f"SMTP error sending email to {to_email}: {e}", exc_info=True)
+ raise HTTPException(status_code=500, detail=f"Failed to send email via SMTP: {type(e).__name__}")
+ except Exception as e:
+ log.error(f"Unexpected error sending email to {to_email}: {e}", exc_info=True)
+ raise HTTPException(status_code=500, detail="Failed to send email") |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -922,12 +922,40 @@ def enabled(self) -> bool: | |||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||
| # sendgrid | ||||||||||||||
| # smtp | ||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class SmtpConfig(BaseModel): | ||||||||||||||
| """SMTP Email configuration""" | ||||||||||||||
|
|
||||||||||||||
| host: str | None = os.getenv("SMTP_HOST") | ||||||||||||||
| port: int = int(os.getenv("SMTP_PORT", "587")) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against ValueError if SMTP_PORT contains non-numeric input. If 🛡️ Proposed fix with validation- port: int = int(os.getenv("SMTP_PORT", "587"))
+ port: int = (
+ int(port_str)
+ if (port_str := os.getenv("SMTP_PORT", "587")).isdigit()
+ else 587
+ )📝 Committable suggestion
Suggested change
|
||||||||||||||
| username: str | None = os.getenv("SMTP_USERNAME") | ||||||||||||||
| password: str | None = os.getenv("SMTP_PASSWORD") | ||||||||||||||
| from_address: str | None = ( | ||||||||||||||
| os.getenv("SMTP_FROM_ADDRESS") | ||||||||||||||
| or os.getenv("SENDGRID_FROM_ADDRESS") | ||||||||||||||
| or os.getenv("AGENTA_AUTHN_EMAIL_FROM") | ||||||||||||||
| or os.getenv("AGENTA_SEND_EMAIL_FROM_ADDRESS") | ||||||||||||||
| ) | ||||||||||||||
| use_tls: bool = os.getenv("SMTP_USE_TLS", "true").lower() in ("true", "1", "yes") | ||||||||||||||
|
|
||||||||||||||
| model_config = ConfigDict(extra="ignore") | ||||||||||||||
|
|
||||||||||||||
| @property | ||||||||||||||
| def enabled(self) -> bool: | ||||||||||||||
| """SMTP enabled if host and from address are present""" | ||||||||||||||
| return bool(self.host and self.from_address) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||
| # sendgrid (legacy — kept for backwards compatibility) | ||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class SendgridConfig(BaseModel): | ||||||||||||||
| """SendGrid Email configuration""" | ||||||||||||||
| """SendGrid Email configuration (legacy)""" | ||||||||||||||
|
|
||||||||||||||
| api_key: str | None = os.getenv("SENDGRID_API_KEY") | ||||||||||||||
| from_address: str | None = ( | ||||||||||||||
|
|
@@ -1037,15 +1065,9 @@ def email_method(self) -> str: | |||||||||||||
| if env.agenta.access.email_disabled: | ||||||||||||||
| return "" | ||||||||||||||
|
|
||||||||||||||
| sendgrid_enabled = bool( | ||||||||||||||
| os.getenv("SENDGRID_API_KEY") | ||||||||||||||
| and ( | ||||||||||||||
| os.getenv("SENDGRID_FROM_ADDRESS") | ||||||||||||||
| or os.getenv("AGENTA_AUTHN_EMAIL_FROM") | ||||||||||||||
| or os.getenv("AGENTA_SEND_EMAIL_FROM_ADDRESS") | ||||||||||||||
| ) | ||||||||||||||
| ) | ||||||||||||||
| return "otp" if sendgrid_enabled else "password" | ||||||||||||||
| # SMTP takes priority, then SendGrid fallback | ||||||||||||||
| email_configured = env.smtp.enabled or env.sendgrid.enabled | ||||||||||||||
| return "otp" if email_configured else "password" | ||||||||||||||
|
|
||||||||||||||
| @property | ||||||||||||||
| def email_enabled(self) -> bool: | ||||||||||||||
|
|
@@ -1101,6 +1123,7 @@ class EnvironSettings(BaseModel): | |||||||||||||
| posthog: PostHogConfig = PostHogConfig() | ||||||||||||||
| redis: RedisConfig = RedisConfig() | ||||||||||||||
| sendgrid: SendgridConfig = SendgridConfig() | ||||||||||||||
| smtp: SmtpConfig = SmtpConfig() | ||||||||||||||
| stripe: StripeConfig = StripeConfig() | ||||||||||||||
| supertokens: SuperTokensConfig = SuperTokensConfig() | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconsider hardcoded default sender address.
Lines 157-158 use the same problematic hardcoded default sender
"account@hello.agenta.ai". This has the same issues as insend_invitation_email: SPF/DKIM failures, unmonitored replies, and inconsistency with the OSS version.Consider raising an error when no sender is configured.
🔧 Proposed fix