Skip to content

feat: migrate email from SendGrid to SMTP with fallback#4566

Open
GanJiaKouN16 wants to merge 6 commits into
Agenta-AI:mainfrom
GanJiaKouN16:feat/smtp-email-migration
Open

feat: migrate email from SendGrid to SMTP with fallback#4566
GanJiaKouN16 wants to merge 6 commits into
Agenta-AI:mainfrom
GanJiaKouN16:feat/smtp-email-migration

Conversation

@GanJiaKouN16
Copy link
Copy Markdown

Summary

Fixes #4536 — migrate the email backend from SendGrid to SMTP, allowing self-hosting users to use their own SMTP servers while keeping SendGrid as a legacy fallback.

Priority order

SMTP > SendGrid > no-op — the email service checks SMTP first, falls back to SendGrid if configured, and silently skips email if neither is set.

Changes

File Change
api/oss/src/services/email_service.py Rewrote to use smtplib for SMTP (priority), SendGrid as lazy fallback. Removed top-level sendgrid import.
api/oss/src/utils/env.py Added SmtpConfig class with SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM_ADDRESS, SMTP_USE_TLS. Added smtp to EnvironSettings. Updated AuthFacade.email_method to check both SMTP and SendGrid.
api/oss/src/services/organization_service.py Check env.smtp.enabled || env.sendgrid.enabled; use configured from_address
api/oss/src/services/user_service.py Same email-enabled check and from_address update
api/ee/src/services/organization_service.py Replace hardcoded account@hello.agenta.ai with configured from_address
api/ee/src/services/db_manager_ee.py Remove dead import sendgrid and unused sg client
api/pyproject.toml Remove sendgrid>=6,<7 dependency (imported lazily only when needed)
hosting/docker-compose/*/env.*.example (×4) Add SMTP vars section, mark SendGrid as legacy
docs/docs/self-host/02-configuration.mdx Add SMTP config table, mark SendGrid as legacy

New environment variables

# SMTP (primary)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=user@example.com
SMTP_PASSWORD=secret
SMTP_FROM_ADDRESS=noreply@example.com
SMTP_USE_TLS=true

# SendGrid (legacy fallback)
SENDGRID_API_KEY=
SENDGRID_FROM_ADDRESS=

Backwards compatibility

  • Existing SendGrid deployments continue to work unchanged
  • SENDGRID_FROM_ADDRESS is still read as a fallback for SMTP_FROM_ADDRESS
  • Legacy env var aliases (AGENTA_AUTHN_EMAIL_FROM, AGENTA_SEND_EMAIL_FROM_ADDRESS) still work
  • No database migration needed

Testing

  • Set SMTP_HOST, SMTP_PORT, SMTP_FROM_ADDRESS and verify invitation/reset emails are sent via SMTP
  • Set only SENDGRID_API_KEY + SENDGRID_FROM_ADDRESS and verify fallback works
  • Set neither and verify email is silently skipped (link returned for manual sharing)

Closes #4536

Wire the existing GenerateResetLinkModal and PasswordResetLinkModal
into the Actions dropdown in the workspace members table.

- Add 'Reset password' menu item for workspace members (not self)
- Add resetPassword API function in profile service
- Show confirmation dialog before generating the reset link
- Display the generated password reset link with copy functionality

Closes Agenta-AI#2572
Several tables with row-level click navigation were missing the
shouldIgnoreRowClick guard, causing clicks on interactive elements
(checkboxes, dropdowns, buttons) to accidentally trigger row navigation.

Changes:
- Consolidate shouldIgnoreRowClick with broader selector list (merges
  EvaluationRunsTablePOC's extra selectors: [role='button'],
  [role='menuitem'], [role='checkbox'], .ant-btn, etc.)
- Export INTERACTIVE_ROW_SELECTORS constant for reuse
- Add guard to ObservabilityTable (traces)
- Add guard to SessionsTable
- Add guard to PromptsPage
- Add guard to TestcasesTableShell
- Add guard to EntityTable
- Replace partial data-ivt-stop-row-click check in ScenarioListView
  with full shouldIgnoreRowClick
- Update useEntityTableState to use consolidated selectors
- Remove duplicate shouldIgnoreRowClick from navigationActions.ts
- Update EvaluationRunsTablePOC to import from shared utility

Closes Agenta-AI#3254
The evaluation table was showing a generic 'too many requests' message
instead of the actual provider error because:

1. executeViaFetch never checked for body-level errors on HTTP 200.
   The Python SDK can return HTTP 200 with a non-200 status.code
   embedded in the response body (WorkflowBatchResponse.status.code).
   This path was silently treated as success.

2. Error stacktrace/type/code were not propagated through the pipeline.
   Even when the HTTP error path was taken, only the message was
   extracted — the SDK's status.type, status.code, and status.stacktrace
   were dropped.

Changes:
- executeViaFetch: detect body-level errors on HTTP 200 by checking
  responseData.status.code !== 200 and return an error result
- executeViaFetch: extract stacktrace (coercing string[] to string),
  type, and code from both HTTP-error and body-error paths
- Add stacktrace and type to ExecutionResult, RunResult, and
  ExecuteWorkflowRevisionResult error shapes
- runInvocationAction: pass stacktrace and type through to
  upsertStepResultWithInvocation
- upsertStepResultWithInvocation: accept type field in error param

No UI changes needed — InvocationCell already renders stepError.message
and stepError.stacktrace when present; extractStepError already reads
error.code, error.type, error.stacktrace from persisted step data.

Closes Agenta-AI#3324
…iddleware

The vault middleware built env var names using f'{provider.upper()}_API_KEY'
which produces TOGETHER_AI_API_KEY for the 'together_ai' provider kind.
The actual env var is TOGETHERAI_API_KEY (no underscore), matching the
frontend (llmProviders.ts, transforms.ts), backend (env.py), and the
Daytona sandbox runner (daytona.py).

Add an explicit _PROVIDER_ENV_VAR_MAP dict (mirroring the Daytona runner
pattern) that maps each provider kind to its correct env var name, with
fallback to the original f-string pattern for any future providers.

Closes Agenta-AI#3659
… drawer

The 'Open evaluator registry' button in the Trace Drawer's
EvaluatorDetailsPopover navigated human evaluators to the registry
page (/evaluators?tab=human&openEvaluator=...) instead of the
evaluator playground. Automatic evaluators already linked correctly.

Unify both evaluator types to navigate to the playground
(/evaluators/playground?revisions=...), consistent with how other
parts of the codebase link to evaluators (EvaluatorSection,
ConfigurationView). Update button text to 'Open evaluator playground'.

Closes Agenta-AI#4535
Replace the SendGrid-only email backend with SMTP support, keeping
SendGrid as a legacy fallback for existing deployments.

Changes:
- email_service.py: use smtplib for SMTP (priority), SendGrid as
  fallback, no-op when neither is configured
- env.py: add SmtpConfig (SMTP_HOST, SMTP_PORT, SMTP_USERNAME,
  SMTP_PASSWORD, SMTP_FROM_ADDRESS, SMTP_USE_TLS), keep SendgridConfig
  for backwards compatibility; update AuthFacade.email_method to check
  both
- OSS/EE organization_service.py: use env.smtp.enabled || env.sendgrid
  .enabled; use configured from_address instead of hardcoded email
- user_service.py: same email-enabled check update
- db_manager_ee.py: remove dead sendgrid import and unused sg client
- pyproject.toml: remove sendgrid dependency (imported lazily only when
  SENDGRID_API_KEY is set)
- env example files: add SMTP vars, mark SendGrid as legacy
- docs: add SMTP config table, mark SendGrid as legacy

Closes Agenta-AI#4536
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

Someone is attempting to deploy a commit to the agenta projects Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. Backend documentation Improvements or additions to documentation enhancement New feature or request python Pull requests that update Python code typescript labels Jun 6, 2026
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Jun 6, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added SMTP email configuration with SendGrid labeled as legacy; invitation/reset flows now return manual links when no email backend is configured
    • Added password reset flow for workspace members
  • Bug Fixes

    • Improved row click handling to avoid unwanted navigation on interactive elements
    • Fixed LLM provider API key mapping for several providers
    • Enhanced error reporting with error type and stacktrace fields
  • Documentation

    • Updated configuration docs and env examples to document SMTP settings
  • Chores

    • Removed SendGrid as a primary dependency (legacy).

Walkthrough

This PR implements SMTP email support as the primary backend with SendGrid fallback, refactors table row-click handling to use centralized selectors, enriches error diagnostics with type and stacktrace fields, adds password reset for workspace members, updates evaluator navigation to use a unified playground route, and introduces provider-to-environment-variable mapping for SDK API key resolution.

Changes

SMTP Email Backend Migration

Layer / File(s) Summary
SMTP environment configuration
api/oss/src/utils/env.py
New SmtpConfig model reads SMTP env vars; EnvironSettings.smtp field added; AuthFacade.email_method prioritizes SMTP for OTP auth with SendGrid fallback.
Email service backend selection and routing
api/oss/src/services/email_service.py
Module-level flags select SMTP or SendGrid backend; _send_via_smtp and _send_via_sendgrid helpers encapsulate provider logic; send_email routes to appropriate helper or no-op when both disabled.
OSS service layer email configuration
api/oss/src/services/organization_service.py, api/oss/src/services/user_service.py
Check both SMTP and SendGrid enabled for fallback behavior; compute from_address from either provider config with fallback to default; raise ValueError if no sender configured.
EE service layer email configuration
api/ee/src/services/organization_service.py, api/ee/src/services/db_manager_ee.py
Apply same sender address logic in EE organization service; remove SendGrid client initialization from db_manager_ee.
Configuration documentation and environment examples
docs/docs/self-host/02-configuration.mdx, hosting/docker-compose/**/env*.example
Add SMTP section with canonical env variables and paths; label SendGrid as legacy in docs and examples.
Dependency cleanup
api/pyproject.toml
Remove sendgrid>=6,<7 dependency.

Row-Click Interactive Element Handling Refactoring

Layer / File(s) Summary
Interactive selector centralization
web/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsx, web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx
Introduce INTERACTIVE_ROW_SELECTORS constant aggregating CSS selectors; refactor shouldIgnoreRowClick to use single closest() check instead of hardcoded per-element chain.
Import migration and old location removal
web/oss/src/components/EvaluationRunsTablePOC/actions/navigationActions.ts, web/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/index.tsx
Move shouldIgnoreRowClick from navigationActions to InfiniteVirtualTable export; update imports.
Entity table state hook selector usage
web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useEntityTableState.ts
Import and split INTERACTIVE_ROW_SELECTORS for default selector derivation instead of hardcoded array.
Table component click handlers
web/oss/src/components/TestcasesTableNew/components/TestcasesTableShell.tsx, web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx, web/oss/src/components/pages/observability/components/SessionsTable/index.tsx, web/oss/src/components/pages/prompts/PromptsPage.tsx
Update onRow and onClick handlers to guard row actions via shouldIgnoreRowClick(event).
Entity and annotation UI tables
web/packages/agenta-entity-ui/src/shared/EntityTable.tsx, web/packages/agenta-annotation-ui/src/components/AnnotationSession/ScenarioListView.tsx
Apply shouldIgnoreRowClick to selection and row-click handlers.

Error Type Enrichment and Execution Flow

Layer / File(s) Summary
Error type definitions with diagnostic fields
web/packages/agenta-entities/src/runnable/types.ts, web/packages/agenta-playground/src/executeWorkflowRevision.ts, web/packages/agenta-playground/src/state/execution/types.ts
Extend ExecutionResult, RunResult, and ExecuteWorkflowRevisionResult error objects to support optional type and stacktrace fields.
API function signature updates
web/oss/src/services/evaluations/invocations/api.ts
Update upsertStepResultWithInvocation error parameter type to include optional type field.
Execution runner error detection and normalization
web/packages/agenta-playground/src/state/execution/executionRunner.ts
Enhance executeViaFetch to detect HTTP errors and body-level status failures; extract and normalize code, type, and stacktrace from error responses; update onFail callback typing.
Error payload construction in atoms
web/oss/src/components/EvalRunDetails/atoms/runInvocationAction.ts
Construct richer error objects with message, stacktrace, and type when recording invocation failures.

Workspace Member Password Reset Feature

Layer / File(s) Summary
Password reset API endpoint
web/oss/src/services/profile/index.ts
Add resetPassword(userId) function that POSTs to api/profile/reset-password and returns reset link string.
Reset password UI and state management
web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx
Add "Reset password" menu item with Key icon; manage modal visibility and reset-link state; wire async handler to call API and control modal display.

Evaluator Navigation Routing Update

Layer / File(s) Summary
Evaluator navigation refactor
web/oss/src/components/SharedDrawers/TraceDrawer/components/EvaluatorDetailsPopover.tsx, web/oss/src/components/SharedDrawers/TraceDrawer/hooks/useEvaluatorNavigation.ts
Always route to /evaluators/playground with type field set to "human" or "auto"; update button text from "registry" to "playground".

LLM Provider API Key Environment Variable Mapping

Layer / File(s) Summary
Provider to API key mapping
sdks/python/agenta/sdk/middlewares/running/vault.py
Add PROVIDER_ENV_VAR_MAPPING constant for cases where env var name differs from provider kind; update secret lookup to use mapping with fallback to convention.

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to evaluator navigation, error type enrichment, row-click handling, and password reset functionality are unrelated to the SMTP migration objective in issue #4536. Remove or relocate changes unrelated to SMTP migration (evaluator navigation, error types, row-click handlers, password reset) to a separate PR focused on those improvements.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main change: migrating email from SendGrid to SMTP with fallback support.
Description check ✅ Passed The PR description clearly relates to the changeset, providing detailed context about the SMTP migration, environment variables, backwards compatibility, and testing guidance.
Linked Issues check ✅ Passed The PR satisfies all objectives from issue #4536: migrates code to SMTP [email_service.py], adds SMTP env vars [env.py], preserves SendGrid fallback [email_service.py, organization/user services], and updates documentation [docs and env.example files].
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 60.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/oss/src/components/SharedDrawers/TraceDrawer/hooks/useEvaluatorNavigation.ts (1)

19-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align evaluator type detection with the canonical URI-first rule.

Line 37 currently depends on a local isHumanEvaluator that only checks flags/meta, but the canonical logic also checks URI category first. This can misclassify some human evaluators as "auto".

Suggested patch
 const isHumanEvaluator = (evaluator: any) => {
-    return Boolean(evaluator?.flags?.is_feedback || evaluator?.meta?.is_feedback)
+    const uri = evaluator?.data?.uri
+    if (typeof uri === "string") {
+        return uri.split(":")[2] === "feedback"
+    }
+    return Boolean(evaluator?.flags?.is_feedback || evaluator?.meta?.is_feedback)
 }

Also applies to: 37-37

🧹 Nitpick comments (1)
api/oss/src/services/email_service.py (1)

47-48: ⚡ Quick win

Apply keyword-only params for new API service function signatures.

New helper signatures are positional; this diverges from the API Python guideline and makes callsites easier to misuse.

As per coding guidelines, "Prefer keyword-only parameters using * in function signatures." and "Use grouped sections in function signatures/calls with # separators for readability."

Also applies to: 74-75, 87-89

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ca0382dc-7e01-463b-a6ce-91fa346f98b5

📥 Commits

Reviewing files that changed from the base of the PR and between 98b8a9d and 7b29544.

📒 Files selected for processing (34)
  • api/ee/src/services/db_manager_ee.py
  • api/ee/src/services/organization_service.py
  • api/oss/src/services/email_service.py
  • api/oss/src/services/organization_service.py
  • api/oss/src/services/user_service.py
  • api/oss/src/utils/env.py
  • api/pyproject.toml
  • docs/docs/self-host/02-configuration.mdx
  • hosting/docker-compose/ee/env.ee.dev.example
  • hosting/docker-compose/ee/env.ee.gh.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/docker-compose/oss/env.oss.gh.example
  • sdks/python/agenta/sdk/middlewares/running/vault.py
  • web/oss/src/components/EvalRunDetails/atoms/runInvocationAction.ts
  • web/oss/src/components/EvaluationRunsTablePOC/actions/navigationActions.ts
  • web/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/index.tsx
  • web/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/EvaluatorDetailsPopover.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/hooks/useEvaluatorNavigation.ts
  • web/oss/src/components/TestcasesTableNew/components/TestcasesTableShell.tsx
  • web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx
  • web/oss/src/components/pages/observability/components/SessionsTable/index.tsx
  • web/oss/src/components/pages/prompts/PromptsPage.tsx
  • web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx
  • web/oss/src/services/evaluations/invocations/api.ts
  • web/oss/src/services/profile/index.ts
  • web/packages/agenta-annotation-ui/src/components/AnnotationSession/ScenarioListView.tsx
  • web/packages/agenta-entities/src/runnable/types.ts
  • web/packages/agenta-entity-ui/src/shared/EntityTable.tsx
  • web/packages/agenta-playground/src/executeWorkflowRevision.ts
  • web/packages/agenta-playground/src/state/execution/executionRunner.ts
  • web/packages/agenta-playground/src/state/execution/types.ts
  • web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useEntityTableState.ts
  • web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx
💤 Files with no reviewable changes (3)
  • web/oss/src/components/EvaluationRunsTablePOC/actions/navigationActions.ts
  • api/pyproject.toml
  • api/ee/src/services/db_manager_ee.py

Comment on lines +22 to +25
import sendgrid

_sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key)
log.info("✓ Email enabled via SendGrid (legacy)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether sendgrid remains declared in dependency manifests.
fd -i 'pyproject.toml|requirements*.txt|poetry.lock|uv.lock|Pipfile'
rg -n --iglob 'pyproject.toml' 'sendgrid'
rg -n --iglob 'requirements*.txt' 'sendgrid'

Repository: Agenta-AI/agenta

Length of output: 1458


Guard legacy SendGrid import to prevent startup crash when SendGrid env is enabled but sendgrid isn’t installed.

rg found no sendgrid declarations in any **/pyproject.toml or **/requirements*.txt, so the module-scope import sendgrid can raise ImportError during startup.

Proposed fix
 elif _USE_SENDGRID:
-    import sendgrid
-
-    _sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key)
-    log.info("✓ Email enabled via SendGrid (legacy)")
+    try:
+        import sendgrid
+        _sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key)
+        log.info("✓ Email enabled via SendGrid (legacy)")
+    except ImportError:
+        _sg = None
+        log.warning("✗ SendGrid configured but package is not installed; email disabled")
+        _USE_SENDGRID = False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import sendgrid
_sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key)
log.info("✓ Email enabled via SendGrid (legacy)")
try:
import sendgrid
_sg = sendgrid.SendGridAPIClient(api_key=env.sendgrid.api_key)
log.info("✓ Email enabled via SendGrid (legacy)")
except ImportError:
_sg = None
log.warning("✗ SendGrid configured but package is not installed; email disabled")
_USE_SENDGRID = False

Comment on lines +59 to +65
server = smtplib.SMTP(smtp_host, smtp_port)
server.ehlo()
server.starttls()
server.ehlo()
else:
server = smtplib.SMTP(smtp_host, smtp_port)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find SMTP client constructions and check whether timeout is passed.
rg -nP --type=py -C2 'smtplib\.SMTP\(' api

Repository: Agenta-AI/agenta

Length of output: 690


Add SMTP connection timeout to prevent request hangs on stalled mail servers

api/oss/src/services/email_service.py creates smtplib.SMTP(smtp_host, smtp_port) in both the TLS and non-TLS branches without a timeout, so a slow/unreachable SMTP server can block the request. Add a timeout= (e.g., from existing env.smtp config) to both SMTP connection sites.

Comment on lines +106 to 108
if not _USE_SMTP and not _USE_SENDGRID:
log.info(f"[EMAIL] Email disabled - would send '{subject}' to {to_email}")
return True
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid logging recipient email and subject in disabled-email mode.

Line 107 logs direct recipient and subject values, which can expose PII in normal operational logs.

Proposed fix
-        log.info(f"[EMAIL] Email disabled - would send '{subject}' to {to_email}")
+        log.info("[EMAIL] Email disabled - skipped outbound message")

Comment thread api/oss/src/utils/env.py
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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use _TRUTHY for SMTP_USE_TLS parsing to avoid silent misconfiguration.

Line 942 currently accepts only true/1/yes; values already accepted elsewhere (for example on, enabled) will evaluate to false and unexpectedly disable TLS.

Proposed fix
-    use_tls: bool = os.getenv("SMTP_USE_TLS", "true").lower() in ("true", "1", "yes")
+    use_tls: bool = (os.getenv("SMTP_USE_TLS") or "true").lower() in _TRUTHY
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use_tls: bool = os.getenv("SMTP_USE_TLS", "true").lower() in ("true", "1", "yes")
use_tls: bool = (os.getenv("SMTP_USE_TLS") or "true").lower() in _TRUTHY

Comment on lines +64 to +71
export const resetPassword = async (userId: string): Promise<string> => {
const base = getBaseUrl()
const url = new URL("api/profile/reset-password", base)
url.searchParams.set("user_id", userId)
const data = await fetchJson<string>(url, {
method: "POST",
})
return data
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use the Fern client for this new profile endpoint.

New frontend API wrappers in web are supposed to go through the shared SDK client, not fetchJson. Adding resetPassword() here creates a parallel API path and skips the repo-standard queryParams boundary. If the generated client does not expose this route yet, please add/regenerate that binding first and call it from here.

As per coding guidelines, "All new frontend API code must go through the Fern-generated client, not raw axios", "Use getAgentaSdkClient({host: getAgentaApiUrl()}) — it is a lazy singleton, share it across calls", and "Pass query params via {queryParams: {...}}, NOT axios's {params: {...}}".

Source: Coding guidelines

Comment on lines 1677 to 1679
const handleRowClick = useCallback((_event: React.MouseEvent, record: ScenarioTableRow) => {
const target = _event.target as HTMLElement
if (target?.closest("[data-ivt-stop-row-click]")) return
if (shouldIgnoreRowClick(_event)) return
setDrawerScenarioId(record.scenarioId)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether legacy marker usage still exists and whether shared selectors account for it.

echo "=== Shared interactive selector contract ==="
rg -n -C3 'INTERACTIVE_ROW_SELECTORS|data-ivt-stop-row-click|data-interactive' web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx

echo
echo "=== Legacy marker usages in repo ==="
rg -n -C2 'data-ivt-stop-row-click'

echo
echo "=== New marker usages in repo ==="
rg -n -C2 'data-interactive'

Repository: Agenta-AI/agenta

Length of output: 5344


Fix row-click ignore contract: data-ivt-stop-row-click is not covered by shouldIgnoreRowClick

  • shouldIgnoreRowClick (via INTERACTIVE_ROW_SELECTORS) ignores [data-interactive] but does not include "[data-ivt-stop-row-click]" (web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx).
  • The legacy attribute is still emitted in the repo (e.g. web/packages/agenta-entity-ui/src/shared/SharedGenerationResultUtils.tsx and multiple web/oss table/action components).
  • Update remaining row-action elements to use [data-interactive] (or extend INTERACTIVE_ROW_SELECTORS to temporarily include "[data-ivt-stop-row-click]") to prevent ScenarioListView-style row navigation regressions.

Comment on lines +718 to +731
if (bodyStatus && typeof bodyStatus === "object" && bodyStatus.code && bodyStatus.code !== 200) {
const traceId = extractTraceIdFromPayload(responseData)
const spanId = extractSpanIdFromPayload(responseData)
const st = bodyStatus.stacktrace
return {
executionId,
status: "error",
startedAt,
completedAt: new Date().toISOString(),
error: {
message: bodyStatus.message || "Invocation failed",
...(bodyStatus.code ? {code: bodyStatus.code.toString()} : {}),
...(bodyStatus.type ? {type: bodyStatus.type} : {}),
...(st ? {stacktrace: Array.isArray(st) ? st.join("\n") : st} : {}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize status.code before comparing to 200.

At Line 718, "200" (string) is treated as a failure because it is compared to numeric 200. That can incorrectly route successful responses into the error path.

Proposed fix
-        const bodyStatus = responseData?.status
-        if (bodyStatus && typeof bodyStatus === "object" && bodyStatus.code && bodyStatus.code !== 200) {
+        const bodyStatus = responseData?.status
+        const bodyStatusCode =
+            bodyStatus && typeof bodyStatus === "object" && bodyStatus.code !== undefined
+                ? String(bodyStatus.code).trim()
+                : undefined
+        if (bodyStatus && typeof bodyStatus === "object" && bodyStatusCode && bodyStatusCode !== "200") {
             const traceId = extractTraceIdFromPayload(responseData)
             const spanId = extractSpanIdFromPayload(responseData)
             const st = bodyStatus.stacktrace
             return {
                 executionId,
                 status: "error",
                 startedAt,
                 completedAt: new Date().toISOString(),
                 error: {
                     message: bodyStatus.message || "Invocation failed",
-                    ...(bodyStatus.code ? {code: bodyStatus.code.toString()} : {}),
+                    ...(bodyStatusCode ? {code: bodyStatusCode} : {}),
                     ...(bodyStatus.type ? {type: bodyStatus.type} : {}),
                     ...(st ? {stacktrace: Array.isArray(st) ? st.join("\n") : st} : {}),
                 },

@GanJiaKouN16 GanJiaKouN16 force-pushed the feat/smtp-email-migration branch from 7b29544 to 1706e43 Compare June 6, 2026 15:47
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx (1)

26-27: ⚡ Quick win

Use EnhancedModal for this new reset-password modal flow.

These new modal imports are part of newly added UX; please align the underlying modal components to the project modal standard (@agenta/ui EnhancedModal) instead of raw antd Modal.

As per coding guidelines: “Use EnhancedModal from @agenta/ui instead of raw antd Modal when building new modals.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a98f1230-8685-4cbd-985d-29783ca9e0a1

📥 Commits

Reviewing files that changed from the base of the PR and between 7b29544 and 1706e43.

📒 Files selected for processing (34)
  • api/ee/src/services/db_manager_ee.py
  • api/ee/src/services/organization_service.py
  • api/oss/src/services/email_service.py
  • api/oss/src/services/organization_service.py
  • api/oss/src/services/user_service.py
  • api/oss/src/utils/env.py
  • api/pyproject.toml
  • docs/docs/self-host/02-configuration.mdx
  • hosting/docker-compose/ee/env.ee.dev.example
  • hosting/docker-compose/ee/env.ee.gh.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • hosting/docker-compose/oss/env.oss.gh.example
  • sdks/python/agenta/sdk/middlewares/running/vault.py
  • web/oss/src/components/EvalRunDetails/atoms/runInvocationAction.ts
  • web/oss/src/components/EvaluationRunsTablePOC/actions/navigationActions.ts
  • web/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/index.tsx
  • web/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/EvaluatorDetailsPopover.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/hooks/useEvaluatorNavigation.ts
  • web/oss/src/components/TestcasesTableNew/components/TestcasesTableShell.tsx
  • web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx
  • web/oss/src/components/pages/observability/components/SessionsTable/index.tsx
  • web/oss/src/components/pages/prompts/PromptsPage.tsx
  • web/oss/src/components/pages/settings/WorkspaceManage/cellRenderers.tsx
  • web/oss/src/services/evaluations/invocations/api.ts
  • web/oss/src/services/profile/index.ts
  • web/packages/agenta-annotation-ui/src/components/AnnotationSession/ScenarioListView.tsx
  • web/packages/agenta-entities/src/runnable/types.ts
  • web/packages/agenta-entity-ui/src/shared/EntityTable.tsx
  • web/packages/agenta-playground/src/executeWorkflowRevision.ts
  • web/packages/agenta-playground/src/state/execution/executionRunner.ts
  • web/packages/agenta-playground/src/state/execution/types.ts
  • web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useEntityTableState.ts
  • web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx
💤 Files with no reviewable changes (3)
  • web/oss/src/components/EvaluationRunsTablePOC/actions/navigationActions.ts
  • api/pyproject.toml
  • api/ee/src/services/db_manager_ee.py
✅ Files skipped from review due to trivial changes (7)
  • web/oss/src/components/SharedDrawers/TraceDrawer/components/EvaluatorDetailsPopover.tsx
  • web/packages/agenta-playground/src/executeWorkflowRevision.ts
  • hosting/docker-compose/ee/env.ee.gh.example
  • hosting/docker-compose/oss/env.oss.dev.example
  • docs/docs/self-host/02-configuration.mdx
  • hosting/docker-compose/oss/env.oss.gh.example
  • web/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/index.tsx
🚧 Files skipped from review as they are similar to previous changes (20)
  • web/packages/agenta-playground/src/state/execution/types.ts
  • web/oss/src/services/evaluations/invocations/api.ts
  • web/packages/agenta-entities/src/runnable/types.ts
  • web/oss/src/components/pages/prompts/PromptsPage.tsx
  • web/packages/agenta-annotation-ui/src/components/AnnotationSession/ScenarioListView.tsx
  • web/oss/src/components/SharedDrawers/TraceDrawer/hooks/useEvaluatorNavigation.ts
  • web/oss/src/components/pages/observability/components/SessionsTable/index.tsx
  • web/packages/agenta-entity-ui/src/shared/EntityTable.tsx
  • web/oss/src/components/TestcasesTableNew/components/TestcasesTableShell.tsx
  • api/oss/src/services/organization_service.py
  • web/oss/src/components/EvalRunDetails/atoms/runInvocationAction.ts
  • web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx
  • web/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsx
  • sdks/python/agenta/sdk/middlewares/running/vault.py
  • web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useEntityTableState.ts
  • web/packages/agenta-playground/src/state/execution/executionRunner.ts
  • web/oss/src/services/profile/index.ts
  • hosting/docker-compose/ee/env.ee.dev.example
  • api/oss/src/services/email_service.py
  • api/ee/src/services/organization_service.py

Comment on lines 57 to 61
export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>): boolean => {
const target = event.target as HTMLElement

// Check if clicking on interactive elements
if (
target.closest("button") ||
target.closest("a") ||
target.closest(".ant-dropdown-trigger") ||
target.closest(".ant-checkbox-wrapper") ||
target.closest(".ant-select") ||
target.closest("input") ||
target.closest("textarea")
) {
return true
}

return false
if (!target) return false
return Boolean(target.closest(INTERACTIVE_ROW_SELECTORS))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use instanceof type guard for safer type narrowing.

The as HTMLElement cast bypasses TypeScript's type checking. While this works at runtime (mouse events on table rows always have HTMLElement targets), TypeScript cannot verify that event.target has the closest() method. Using an instanceof guard provides proper type narrowing.

🔍 Proposed fix using instanceof
 export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>): boolean => {
-    const target = event.target as HTMLElement
-    if (!target) return false
+    const target = event.target
+    if (!(target instanceof HTMLElement)) return false
     return Boolean(target.closest(INTERACTIVE_ROW_SELECTORS))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>): boolean => {
const target = event.target as HTMLElement
// Check if clicking on interactive elements
if (
target.closest("button") ||
target.closest("a") ||
target.closest(".ant-dropdown-trigger") ||
target.closest(".ant-checkbox-wrapper") ||
target.closest(".ant-select") ||
target.closest("input") ||
target.closest("textarea")
) {
return true
}
return false
if (!target) return false
return Boolean(target.closest(INTERACTIVE_ROW_SELECTORS))
}
export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>): boolean => {
const target = event.target
if (!(target instanceof HTMLElement)) return false
return Boolean(target.closest(INTERACTIVE_ROW_SELECTORS))
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend documentation Improvements or additions to documentation enhancement New feature or request python Pull requests that update Python code size:L This PR changes 100-499 lines, ignoring generated files. typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Swap sendgrid by SMTP

2 participants