Skip to content

Conversation

@seedspirit
Copy link
Contributor

@seedspirit seedspirit commented Jan 12, 2026

resolves #7692 (BA-3635)

Checklist: (if applicable)

  • Milestone metadata specifying the target backport version
  • Mention to the original issue
  • Installer updates including:
    • Fixtures for db schema changes
    • New mandatory config options
  • Update of end-to-end CLI integration tests in ai.backend.test
  • API server-client counterparts (e.g., manager API -> client SDK)
  • Test case(s) to:
    • Demonstrate the difference of before/after
    • Demonstrate the flow of abstract/conceptual models with a concrete implementation
  • Documentation
    • Contents in the docs directory
    • docstrings in public interfaces and type annotations

📚 Documentation preview 📚: https://sorna--7941.org.readthedocs.build/en/7941/


📚 Documentation preview 📚: https://sorna-ko--7941.org.readthedocs.build/ko/7941/

@github-actions github-actions bot added size:XL 500~ LoC comp:manager Related to Manager component comp:client Related to Client component comp:common Related to Common component comp:cli Related to CLI component area:docs Documentations labels Jan 12, 2026
@seedspirit seedspirit force-pushed the feat/BA-3635 branch 4 times, most recently from 4df76e1 to 3901df4 Compare January 12, 2026 08:23
@seedspirit seedspirit marked this pull request as ready for review January 12, 2026 08:31
Copilot AI review requested due to automatic review settings January 12, 2026 08:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds email notification channel support using SMTP to the Backend.AI notification system, enabling notifications to be sent via email in addition to the existing webhook channel.

Changes:

  • Introduces EmailConfig, SMTPConfig, and EmailMessageConfig data models for email channel configuration
  • Implements EmailChannel with SMTP support for both authenticated and relay servers
  • Updates the notification system to handle multiple channel types through a union type pattern
  • Adds comprehensive test coverage for email functionality and configuration validation

Reviewed changes

Copilot reviewed 27 out of 33 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/ai/backend/common/data/notification/types.py Adds base NotificationChannelConfig class and email-related config models (EmailConfig, SMTPConfig, EmailMessageConfig); introduces NotificationChannelConfigTypes union
src/ai/backend/common/data/notification/init.py Exports new email configuration types
src/ai/backend/common/dto/manager/notification/request.py Updates request models to use NotificationChannelConfigTypes union instead of webhook-only config
src/ai/backend/common/dto/manager/notification/response.py Adds ConfigResponse base class and EmailConfigResponse for API responses
src/ai/backend/common/exception.py Adds NotificationChannelBadRequest and NotificationChannelNotSupported exceptions
src/ai/backend/manager/data/notification/types.py Updates NotificationChannelData to use generic NotificationChannelConfig
src/ai/backend/manager/models/notification/row.py Adds email config parsing in to_data() method
src/ai/backend/manager/repositories/notification/creators.py Updates creator spec to use NotificationChannelConfig
src/ai/backend/manager/repositories/notification/updaters.py Updates updater spec and adds from_request() factory method
src/ai/backend/manager/notification/notification_center.py Adds EMAIL channel type handling with type validation
src/ai/backend/manager/notification/channels/email/channel.py Implements EmailChannel class with async SMTP sending
src/ai/backend/manager/api/notification/adapter.py Updates adapter to handle multiple config types in responses and simplifies updater building
src/ai/backend/manager/api/gql/notification/types.py Adds GraphQL types for email config and implements OneOf pattern for channel config input
src/ai/backend/client/cli/notification.py Updates CLI to support email channel creation/updates with JSON config
tests/unit/common/data/notification/test_notification_config_schema.py Tests union type validation and conversion behavior
tests/unit/manager/notification/channels/email/test_channel.py Comprehensive unit tests for EmailChannel functionality
tests/unit/manager/api/notification/test_adapter.py Adds type assertion for webhook config
tests/unit/manager/repositories/notification/test_notification_repository.py Adds config type assertions
docs/manager/graphql-reference/v2-schema.graphql Updates GraphQL schema with email types and OneOf directive
docs/manager/graphql-reference/supergraph.graphql Updates federated schema
changes/7941.feature.md Documents the new feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 33 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 33 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

tests/unit/manager/repositories/notification/test_notification_repository.py:408

  • Missing test coverage for email channel configuration in repository tests. While webhook channels are tested in test_create_channel and test_update_channel, there are no equivalent tests for EmailConfig. The repository should be tested with both channel types to ensure the config serialization/deserialization and type handling work correctly for email channels. Consider adding test cases similar to test_create_channel but using EmailConfig to ensure the model's to_data method correctly parses email configurations from the database.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@fregataa fregataa left a comment

Choose a reason for hiding this comment

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

We need to add WebhookNotificationChannelData and EmailNotificationChannelData types and functions which convert NotificationChannelData to them

Copy link
Collaborator

@HyeockJinKim HyeockJinKim left a comment

Choose a reason for hiding this comment

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

If this work only applies to CLI / API / GraphQL, that's fine, but including Email Channel support in the same PR is excessive. Please split the PR and issues:

  1. Email Channel implementation
  2. Registration work for API / GraphQL / CLI (or these could potentially be combined since they're mostly boilerplate)

@seedspirit seedspirit changed the title feat(BA-3635): Add Email Notification Channel Support feat(BA-3635): Implement Email Notification Channel Jan 15, 2026
@seedspirit seedspirit requested a review from Copilot January 15, 2026 05:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 33 changed files in this pull request and generated 15 comments.

Comments suppressed due to low confidence (4)

src/ai/backend/client/cli/notification.py:139

  • The CLI create_channel_cmd only supports webhook configuration through the URL argument. With email channel support being added, the CLI should be updated to support email channel creation as well, possibly by accepting different configuration parameters based on the channel_type or through a configuration file.
@channel.command("create")
@pass_ctx_obj
@click.argument("name", type=str)
@click.argument("url", type=str)
@click.option("--channel-type", default="webhook", help="Channel type (default: WEBHOOK)")
@click.option("--description", type=str, default=None, help="Channel description")
@click.option("--disabled", is_flag=True, help="Create channel as disabled")
def create_channel_cmd(
    ctx: CLIContext,
    name: str,
    url: str,
    channel_type: str,
    description: Optional[str],
    disabled: bool,
) -> None:
    """
    Create a new notification channel.

    \b
    NAME: Channel name
    URL: Webhook URL
    """
    with Session() as session:
        try:
            request = CreateNotificationChannelRequest(
                name=name,
                channel_type=NotificationChannelType(channel_type),
                config=WebhookSpec(url=url),
                description=description,
                enabled=not disabled,
            )
            result = session.Notification.create_channel(request)
            channel = result.channel
            print_done(f"Channel created: {channel.id}")
            print(json.dumps(channel.model_dump(mode="json"), indent=2, default=str))
        except Exception as e:
            ctx.output.print_error(e)
            sys.exit(ExitCode.FAILURE)

src/ai/backend/manager/api/notification/adapter.py:114

  • The build_updater method only supports WebhookSpec configuration updates. To support updating email channels through the REST API, this method should handle both WebhookSpec and EmailSpec types.
    def build_updater(
        self, request: UpdateNotificationChannelRequest, channel_id: UUID
    ) -> Updater[NotificationChannelRow]:
        """Convert update request to updater."""

        name = OptionalState[str].nop()
        description = OptionalState[str | None].nop()
        config = OptionalState[WebhookSpec].nop()
        enabled = OptionalState[bool].nop()

        if request.name is not None:
            name = OptionalState.update(request.name)
        if request.description is not None:
            description = OptionalState.update(request.description)
        if request.config is not None:
            # config validator ensures this is WebhookSpec
            if not isinstance(request.config, WebhookSpec):
                raise InvalidNotificationConfig(
                    f"Expected WebhookSpec, got {type(request.config).__name__}"
                )
            config = OptionalState.update(request.config)
        if request.enabled is not None:
            enabled = OptionalState.update(request.enabled)

        spec = NotificationChannelUpdaterSpec(
            name=name,
            description=description,
            config=config,
            enabled=enabled,
        )
        return Updater(spec=spec, pk_value=channel_id)

src/ai/backend/manager/api/notification/adapter.py:82

  • The REST API adapter only supports WebhookSpec in convert_to_dto and will raise an InvalidNotificationConfig error for email channels. This prevents listing or retrieving email channels through the REST API. The method should handle both WebhookSpec and EmailSpec, creating appropriate response DTOs for each type.
    def convert_to_dto(self, data: NotificationChannelData) -> NotificationChannelDTO:
        """Convert NotificationChannelData to DTO."""
        # TODO: Support EmailSpec when Email channel is implemented
        if not isinstance(data.config, WebhookSpec):
            raise InvalidNotificationConfig(
                f"Expected WebhookSpec, got {type(data.config).__name__}"
            )
        return NotificationChannelDTO(
            id=data.id,
            name=data.name,
            description=data.description,
            channel_type=data.channel_type,
            config=WebhookSpecResponse(url=data.config.url),
            enabled=data.enabled,
            created_at=data.created_at,
            created_by=data.created_by,
            updated_at=data.updated_at,
        )

src/ai/backend/manager/api/gql/notification/types.py:70

  • The to_internal method for NotificationChannelTypeGQL does not handle all possible enum values. It only has a case for WEBHOOK, but if EMAIL is added to the enum (as it should be), this method will fail with an implicit None return. Add a case for EMAIL that returns NotificationChannelType.EMAIL.
    def to_internal(self) -> NotificationChannelType:
        """Convert GraphQL enum to internal NotificationChannelType."""
        match self:
            case NotificationChannelTypeGQL.WEBHOOK:
                return NotificationChannelType.WEBHOOK

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

description: Optional[str] = None
channel_type: NotificationChannelTypeGQL = NotificationChannelTypeGQL.WEBHOOK
config: WebhookConfigInput = strawberry.field()
config: WebhookSpecInput = strawberry.field()
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to accept emailConfig in addition to WebHook.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will added it in follow up PR (REST, GQL, CLI support)

description: Optional[str] = None
channel_type: NotificationChannelTypeGQL = NotificationChannelTypeGQL.WEBHOOK
config: WebhookConfigInput = strawberry.field()
config: WebhookSpecInput = strawberry.field()
Copy link
Member

Choose a reason for hiding this comment

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

Is it a future plan to add a new EmailSpecInput?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I will add it in follow up issue

@github-actions github-actions bot added size:L 100~500 LoC and removed size:XL 500~ LoC labels Jan 15, 2026
@github-actions github-actions bot added size:XL 500~ LoC and removed size:L 100~500 LoC labels Jan 16, 2026
@HyeockJinKim HyeockJinKim added this pull request to the merge queue Jan 19, 2026
Merged via the queue into main with commit f1a00ab Jan 19, 2026
31 checks passed
@HyeockJinKim HyeockJinKim deleted the feat/BA-3635 branch January 19, 2026 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentations comp:cli Related to CLI component comp:client Related to Client component comp:common Related to Common component comp:manager Related to Manager component size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Email Notification Channel

4 participants