diff --git a/cortex/tools/__init__.py b/cortex/tools/__init__.py index 65469cc..d612d37 100644 --- a/cortex/tools/__init__.py +++ b/cortex/tools/__init__.py @@ -48,7 +48,7 @@ from tools.scratch import ( from tools.system import cortex_restart as _cortex_restart, cortex_logs as _cortex_logs from tools.web import http_fetch as _http_fetch from tools.files import file_list as _file_list, file_write as _file_write -from tools.notify import nc_talk_send as _nc_talk_send +from tools.notify import nc_talk_send as _nc_talk_send, email_send as _email_send # --------------------------------------------------------------------------- @@ -296,6 +296,7 @@ _CALLABLES: dict[str, callable] = { "cortex_restart": _cortex_restart, "cortex_logs": _cortex_logs, "http_fetch": _http_fetch, + "email_send": _email_send, "nc_talk_send": _nc_talk_send, "task_list": _task_list, "task_create": _task_create, @@ -755,6 +756,24 @@ _file_write_declaration = types.FunctionDeclaration( ), ) +_email_send_declaration = types.FunctionDeclaration( + name="email_send", + description=( + "Send an email from the server's configured SMTP account. Use for delivering " + "summaries, reports, reminders, or any content the user wants emailed. " + "body is plain text; newlines are preserved." + ), + parameters=types.Schema( + type=types.Type.OBJECT, + properties={ + "to": types.Schema(type=types.Type.STRING, description="Recipient email address"), + "subject": types.Schema(type=types.Type.STRING, description="Email subject line"), + "body": types.Schema(type=types.Type.STRING, description="Plain-text email body"), + }, + required=["to", "subject", "body"], + ), +) + _nc_talk_send_declaration = types.FunctionDeclaration( name="nc_talk_send", description=( @@ -791,6 +810,8 @@ TOOL_ROLES: dict[str, str] = { "file_list": "admin", "file_write": "admin", "ae_task_list": "admin", # reads agents_sync kanban + "email_send": "admin", # sends from server SMTP account + "nc_talk_send": "admin", } # Tools that require explicit user confirmation before executing. @@ -831,6 +852,7 @@ _ALL_DECLARATIONS: list[types.FunctionDeclaration] = [ _cortex_restart_declaration, _cortex_logs_declaration, _http_fetch_declaration, + _email_send_declaration, _nc_talk_send_declaration, _task_list_declaration, _task_create_declaration, diff --git a/cortex/tools/notify.py b/cortex/tools/notify.py index fdc2c14..779844f 100644 --- a/cortex/tools/notify.py +++ b/cortex/tools/notify.py @@ -2,9 +2,10 @@ Notification tools — proactively send messages to user channels. nc_talk_send routes through notification.py → channels.json. -Requires notification_channel and notification_room set in the user's channels.json. +email_send uses the server SMTP config from .env (smtp_server, smtp_from_*). """ +import asyncio import logging from persona import get_user @@ -12,6 +13,21 @@ from persona import get_user logger = logging.getLogger(__name__) +async def email_send(to: str, subject: str, body: str) -> str: + """Send an email via the server's configured SMTP account.""" + from email_utils import send_email + ok = await asyncio.to_thread( + send_email, + to_email=to, + subject=subject, + body_text=body, + body_html=body.replace("\n", "
"), + ) + if ok: + return f"Email sent to {to}." + return "Failed to send email — check SMTP configuration in .env." + + async def nc_talk_send(message: str) -> str: """Send a message to the user via their configured notification channel.