feat: add email_blast tool for NEMO email sending #32

Merged
forgejo_admin merged 1 commit from 31-email-blast-tool into main 2026-04-03 23:25:18 +00:00

Summary

Adds an email_blast tool so NEMO can send branded emails to player groups via the basketball-api /admin/email/blast endpoint. Includes the confirmation flow (write operation requires "yes" reply) and test_email safety pattern.

Changes

  • app/basketball.py — Added generic post(path, body) method to BasketballClient, mirroring the existing get() pattern but with POST + JSON body
  • app/ai.py — Added email_blast tool definition to TOOLS list (tagged as write operation), execution handler in _execute_write_tool, human-readable action description in _ACTION_DESCRIPTIONS, and updated SYSTEM_PROMPT with email capabilities guidance (layouts, email_types, composition instructions, test_email safety)
  • tests/test_ai.py — 11 new tests: tool existence, schema validation, execution dispatch, test_email/query inclusion, error handling, action descriptions, system prompt content
  • tests/test_basketball.py — 4 new tests: post with body, post without body, slash prepending, error raising

Test Plan

  • All 125 tests pass (was 110, added 15)
  • Verify email_blast tool triggers confirmation flow (write operation)
  • Verify test_email parameter sends to single address before real blast
  • Verify query and test_email are only included in POST body when provided

Review Checklist

  • Tests pass (125/125)
  • Tool tagged as write operation so confirmation flow triggers
  • SYSTEM_PROMPT updated with email guidance
  • Action description renders readable confirmation prompt
  • No secrets or credentials in diff

None.

Closes #31

## Summary Adds an `email_blast` tool so NEMO can send branded emails to player groups via the basketball-api `/admin/email/blast` endpoint. Includes the confirmation flow (write operation requires "yes" reply) and test_email safety pattern. ## Changes - `app/basketball.py` — Added generic `post(path, body)` method to `BasketballClient`, mirroring the existing `get()` pattern but with POST + JSON body - `app/ai.py` — Added `email_blast` tool definition to TOOLS list (tagged as write operation), execution handler in `_execute_write_tool`, human-readable action description in `_ACTION_DESCRIPTIONS`, and updated `SYSTEM_PROMPT` with email capabilities guidance (layouts, email_types, composition instructions, test_email safety) - `tests/test_ai.py` — 11 new tests: tool existence, schema validation, execution dispatch, test_email/query inclusion, error handling, action descriptions, system prompt content - `tests/test_basketball.py` — 4 new tests: post with body, post without body, slash prepending, error raising ## Test Plan - All 125 tests pass (was 110, added 15) - Verify email_blast tool triggers confirmation flow (write operation) - Verify test_email parameter sends to single address before real blast - Verify query and test_email are only included in POST body when provided ## Review Checklist - [x] Tests pass (125/125) - [x] Tool tagged as write operation so confirmation flow triggers - [x] SYSTEM_PROMPT updated with email guidance - [x] Action description renders readable confirmation prompt - [x] No secrets or credentials in diff ## Related Notes None. ## Related Closes #31
feat: add email_blast tool for sending branded emails via basketball-api
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
fbd7e1ec17
Enables NEMO to send branded email blasts through the basketball-api
/admin/email/blast endpoint, with confirmation flow and test_email safety.

- Add generic post() method to BasketballClient
- Add email_blast tool definition with layout/email_type/subject/data/test_email
- Add execution handler dispatching to client.post("/admin/email/blast", body)
- Update SYSTEM_PROMPT with email capabilities guidance
- Add 15 new tests covering tool schema, execution, descriptions, and post()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

PR #32 Review

DOMAIN REVIEW

Tech stack: Python / async / Anthropic tool-use pattern

The PR adds an email_blast tool to the NEMO AI assistant, following established patterns in the codebase. Four files changed across two modules (app, tests) with 322 additions and 7 deletions (count adjustments).

Pattern compliance: The new tool follows the exact same structure as existing write tools -- tool definition in TOOLS list with metadata.operation = "write", execution branch in _execute_write_tool, action description lambda in _ACTION_DESCRIPTIONS, and system prompt guidance. This consistency is good.

BasketballClient.post() method: Clean addition mirroring the existing get() pattern. Delegates to _request("POST", path, json=body). Path normalization (prepend /) matches get(). Type hint body: dict | None = None is correct for optional JSON payloads.

Optional field handling: query and test_email are conditionally included in the POST body via tool_input.get(), which correctly excludes them when not provided. This avoids sending null values to the basketball-api endpoint.

Safety: Tool is tagged as write operation, triggering the confirmation flow before execution. System prompt instructs the model to suggest test_email before real blasts. These are appropriate guardrails for an email-sending tool.

Test coverage: 15 new tests covering:

  • Tool existence and write-operation tagging
  • Schema required/optional fields
  • Execution dispatch with mock client (happy path)
  • test_email and query conditional inclusion
  • Error propagation (RuntimeError -> error string)
  • Action description rendering (with and without test_email)
  • System prompt content assertions
  • Generic post(): with body, without body, slash prepending, error raising

This is thorough coverage across happy path, edge cases, and error handling.

BLOCKERS

None.

NITS

  1. Enum-style validation for layout: The layout field accepts any string, but only action, notification, and announcement are valid. Consider adding an enum constraint to the JSON schema so the LLM gets stronger guidance and invalid values are caught before hitting the API. This is non-blocking since basketball-api presumably validates on its end.

  2. test_email naming in system prompt: The system prompt says "setting test_email to the user's email address" but the tool name in the prompt is referenced as part of general guidance, not explicitly as the email_blast tool parameter. Minor clarity improvement possible -- again non-blocking since the tool description itself is clear.

SOP COMPLIANCE

  • Branch named after issue (31-email-blast-tool)
  • PR body follows template (Summary, Changes, Test Plan, Related)
  • Related references parent issue (Closes #31)
  • Related section does not reference a plan slug (acceptable -- "None" listed, no plan appears to exist for this work)
  • No secrets committed
  • No unnecessary file changes (all 4 files are directly relevant)
  • Tests exist and cover new functionality (15 new tests, 125 total passing)

PROCESS OBSERVATIONS

Clean, well-scoped PR. One issue, one feature, one PR. The 15 new tests maintain the repo's strong test culture (110 -> 125). The generic post() method on BasketballClient creates a reusable primitive for future write tools that need to POST to arbitrary endpoints, reducing future implementation cost.

VERDICT: APPROVED

## PR #32 Review ### DOMAIN REVIEW **Tech stack**: Python / async / Anthropic tool-use pattern The PR adds an `email_blast` tool to the NEMO AI assistant, following established patterns in the codebase. Four files changed across two modules (app, tests) with 322 additions and 7 deletions (count adjustments). **Pattern compliance**: The new tool follows the exact same structure as existing write tools -- tool definition in `TOOLS` list with `metadata.operation = "write"`, execution branch in `_execute_write_tool`, action description lambda in `_ACTION_DESCRIPTIONS`, and system prompt guidance. This consistency is good. **`BasketballClient.post()` method**: Clean addition mirroring the existing `get()` pattern. Delegates to `_request("POST", path, json=body)`. Path normalization (prepend `/`) matches `get()`. Type hint `body: dict | None = None` is correct for optional JSON payloads. **Optional field handling**: `query` and `test_email` are conditionally included in the POST body via `tool_input.get()`, which correctly excludes them when not provided. This avoids sending `null` values to the basketball-api endpoint. **Safety**: Tool is tagged as `write` operation, triggering the confirmation flow before execution. System prompt instructs the model to suggest `test_email` before real blasts. These are appropriate guardrails for an email-sending tool. **Test coverage**: 15 new tests covering: - Tool existence and write-operation tagging - Schema required/optional fields - Execution dispatch with mock client (happy path) - `test_email` and `query` conditional inclusion - Error propagation (`RuntimeError` -> error string) - Action description rendering (with and without `test_email`) - System prompt content assertions - Generic `post()`: with body, without body, slash prepending, error raising This is thorough coverage across happy path, edge cases, and error handling. ### BLOCKERS None. ### NITS 1. **Enum-style validation for `layout`**: The `layout` field accepts any string, but only `action`, `notification`, and `announcement` are valid. Consider adding an `enum` constraint to the JSON schema so the LLM gets stronger guidance and invalid values are caught before hitting the API. This is non-blocking since basketball-api presumably validates on its end. 2. **`test_email` naming in system prompt**: The system prompt says "setting test_email to the user's email address" but the tool name in the prompt is referenced as part of general guidance, not explicitly as the `email_blast` tool parameter. Minor clarity improvement possible -- again non-blocking since the tool description itself is clear. ### SOP COMPLIANCE - [x] Branch named after issue (`31-email-blast-tool`) - [x] PR body follows template (Summary, Changes, Test Plan, Related) - [x] Related references parent issue (`Closes #31`) - [ ] Related section does not reference a plan slug (acceptable -- "None" listed, no plan appears to exist for this work) - [x] No secrets committed - [x] No unnecessary file changes (all 4 files are directly relevant) - [x] Tests exist and cover new functionality (15 new tests, 125 total passing) ### PROCESS OBSERVATIONS Clean, well-scoped PR. One issue, one feature, one PR. The 15 new tests maintain the repo's strong test culture (110 -> 125). The generic `post()` method on `BasketballClient` creates a reusable primitive for future write tools that need to POST to arbitrary endpoints, reducing future implementation cost. ### VERDICT: APPROVED
forgejo_admin deleted branch 31-email-blast-tool 2026-04-03 23:25:18 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/westside-ai-assistant!32
No description provided.