Add automation action Send email

Task 9425 · Done · Coder · created · updated

## Original request (verbatim) > Migrate the emails system from GMail API to AWS SES. Use all the best practices. The quality of communication is of highest priority. Spare no time or expense. > > [jaah.ai](http://jaah.ai) MX is currently at GMail and will remain so, but we can carve out whatever we need Epic context (BA PRD Task 9352, Architecture Task 9353) — settled: all system mail (send + receive) moves to SES on `mail.jaah.ai` (dev: `mail.dev.jaah.ai`, dev account, sends/receives real mail) with suppression, operator-only delivery log, RFC 8058 unsubscribe, DMARC monitoring, operator-approved template redesign; tenant Gmail (agent send, automation action, receive) is built in this epic behind one plan feature. All system AWS calls use the hand-signed SigV4 seam (`apps/api/app/aws/common.py` `signed_call`), no boto3. SES events and inbound mail are PULLED from SQS by system crons (precedent `apps/api/app/alarm_relay.py`, `apps/api/app/aws/sqs.py`); the only new public path is unsubscribe on the hooks edge. ## Scope of this child C9 Automation "Send email": a new automation Action that sends from the org's connected Gmail when its When fires, with templated fields. Delivers PRD R11. ## Requirements (verbatim from the PRD) ### R11 — Automation action "Send email" (A6, Q9) A new automation Action sends an email from the org's connected Gmail when its When fires, with templated fields from the trigger/event context. ## Approach (from the settled architecture) - `ActionDescriptor(type="email.send")` in `apps/api/app/automation_actions.py` (near :102); params from/to/cc/subject/body templates; render on **both** event and schedule runs (schedule context: automation name, fired_at, org) — extends `apps/api/app/automation_fire.py:369-393`. Calls `tenant_gmail.send(..., automation_run_id=<run>)` (S7). - Templating (§9.11): plain `{field}` substitution over a whitelist (`automation.name`, `fired_at`, `org.name`; event runs add the trigger's existing fields via `trigger_template.render`), no expression evaluation; every rendered header passes `mail_message._header_text` (CR/LF stripped, length-capped); recipients re-parsed as addresses after rendering. - Console: the action's form in the automation editor per `docs/standards/ui-style-guide.md` and `docs/standards/ui-copy-standards.md`. Pinned seams (build against these exactly; other children build against them too): - S7 tenant send (C7 → C8/C9): `TenantOutboundEmail(to: list[str], cc: list[str] = [], subject: str, text: str, html: str | None = None)` (1–50 recipients total); `tenant_gmail.send(session, *, org_id: UUID, connection_id: UUID | None, message: TenantOutboundEmail, actor: Actor, task_id: UUID | None = None, automation_run_id: UUID | None = None) -> SendResult`; `SendResult(send_id: UUID, gmail_message_id: str, from_address: str)`. `connection_id=None` = the org's only send-capable connection, else `AmbiguousConnection` (→ 409). Errors: `NoConnection` (→ 409), `PlanOff` (→ 403), `GmailRefused(.status)` (→ 502). C7 creates `tenant_email_sends` and writes it inside `send`; C8/C9 only pass `task_id`/`automation_run_id`. Both builders feed `mail_message.build(sender, to: list[str], cc: list[str], ...)`. Credentials & reach: | Oracle | Credential / identity / environment | Status | |---|---|---| | AC10 (non-operator 403), AC17 tenant side | `org/console/dev-orgb-login` (org B admin) | **verified** — `credential_get` ok (its recorded base_url is legacy; use `https://app.dev.jaah.ai`) | | AC14/AC15/AC16/AC17(a) live (AC16 needs a connected Gmail too) | Jaah Google OAuth client (gmail.send) + a test Workspace SA with DWD | **deferred** — operator: "Build now, verify later" (I4) | | AC5, AC18, unit/integration suites | worktree only | **verified** — nothing beyond the worktree | Out of scope: - Do NOT change `tenant_gmail.send` or the connection model (C7); do NOT add the agent MCP tool (C8). - No expression evaluation in templates. Epic out-of-scope (PRD §7, verbatim): - MUST NOT change the apex `jaah.ai` MX, SPF or `_dmarc.jaah.ai` record, nor any Workspace mailbox, alias or routing. - MUST NOT add SES Virtual Deliverability Manager, BIMI/VMC, or a dedicated IP / dedicated IP pool. - MUST NOT request Google's restricted `gmail.readonly` (or any restricted Gmail scope) via OAuth; no CASA assessment. - MUST NOT add open/click tracking pixels or link rewriting to system mail. - MUST NOT let tenant Gmail replace system mail for an org's members (Q9 option not chosen). - MUST NOT expose the delivery log or suppression list to tenant admins or users (operator only, Q7). - MUST NOT relax the verified-address rule or invitation budgets for system mail (AS6). - MUST NOT add boto3/botocore to `apps/api/requirements.txt` unless the Architect records why the hand-signed SigV4 seam (`apps/api/app/aws/`) cannot serve SES (the repo guards its absence). - MUST NOT put a tenant Gmail credential, OAuth token or service-account key on any node or in a runner's environment. - MUST NOT change Slack/Telegram delivery, the support chat, or site enquiries (no email today). Done means: - **AC16 (R11) Automation action.** → verify: create an automation (When: schedule in 1 min → Action: Send email) in that org; the email arrives with the templated fields filled. (deferred — needs a connected Gmail; operator "Build now, verify later" (I4). Substitute now: fire-engine tests for a schedule run and an event run with a stubbed `tenant_gmail.send` asserting the rendered subject/body/recipients and `automation_run_id` on the send; the action is creatable in the dev console as `org/console/dev-orgb-login` and a fire with no connection records the `NoConnection` failure on the run.) - Header injection: a subject carrying `\r\nBcc: x@y` yields one Subject header and no Bcc (test). - Quick gate `./dc check quick` green and changed-area tests green; `python3 tools/check_context.py` green (any doc this child adds/edits is routed and single-homed).

Outcome

Built the Send email automation action (schedule + event, templated, injection-safe recipients) with console form and wizard fix; quick gate + 463 API/196 web tests green; review lane clean; PR #6757 queued in merge lane. Live Gmail check deferred (I4); draft #9458 filed for send source ids. Memory: none applied.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)