---
id: WF-ACC-TRX-016
title: E-mail an invoice
trigger: >-
  An authorized API caller requests invoice delivery to the contact address or
  explicitly named recipients.
product_area: accounting
module: transactions
revision: 1
status: draft
design_intent: confirmed
origin: >-
  Andrew’s A-10 and A-11 (no stored copy; e-mailing is an explicit step),
  services-accounting:docs/architecture/accounting-2.0/02-decisions.md#2.8
  Andrew's acceptance decisions of 2026-10-18 (about 15:35 UTC)
implementation_evidence: observed
inference: []
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-ACC-TRX-005
moves: []
crosses:
  - SVC-ACC-TRX-transactions
hops:
  - 'n': 1
    actor_or_service: Invoice e-mails
    action: >-
      Validate the retry key and a non-draft invoice and resolve up to ten
      explicit recipients or the contact's primary address.
    anchor: >-
      services-accounting:modules/transactions/services/invoices/invoice-emails.ts:emailInvoice
    seam: sync
    on_failure: 'Reject prepared drafts, absent recipients or an absent retry key.'
    left_inconsistent: The command has persisted no effects.
    observer: The API response reports the outcome and applicable refusal code.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/invoices/invoice-emails.ts:emailInvoice;
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      refuses more than ten addresses: 400
  - 'n': 2
    actor_or_service: Invoice rendering
    action: >-
      Render the attachment and message from the chosen invoice, unit or
      built-in template at send time.
    anchor: >-
      services-accounting:modules/transactions/services/invoices/invoice-rendering.ts
    seam: sync
    on_failure: Reject a selected template that is missing or inactive.
    left_inconsistent: The command has persisted no effects.
    observer: The API response reports the outcome and applicable refusal code.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/invoices/invoice-rendering.ts;
      services-accounting:modules/transactions/tests/integration/services/invoices/render-invoice.integration.spec.js#it
      is the invoice’s own template over its unit’s
  - 'n': 3
    actor_or_service: Platform queued communications
    action: Commit the communication submission and invoice history entry together.
    anchor: >-
      services-accounting:modules/transactions/services/ports/invoice-email-sender.ts
    seam: sync
    on_failure: Submission failure also undoes the history entry.
    left_inconsistent: The complete transaction is undone.
    observer: The API response reports the outcome and applicable refusal code.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/ports/invoice-email-sender.ts;
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      writes nothing, not even the event, when the sender’s statement fails: the
      command’s transaction rolls back
  - 'n': 4
    actor_or_service: Platform communications dispatcher
    action: 'After commit, ask the sender to nudge Platform communication dispatch.'
    anchor: >-
      services-accounting:modules/transactions/services/invoices/invoice-emails.ts:emailInvoice
    seam: queue
    on_failure: Platform records and recovers delivery outcomes.
    left_inconsistent: Submitted communication can await later delivery.
    observer: Communication outcomes are visible through Platform.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/invoices/invoice-emails.ts:emailInvoice
idempotency: >-
  Communication-submission deduplication protects delivery using the caller key.
  A replay may render and append history again because this path does not use
  the financial retry ledger.
concurrency: >-
  Distinct submission keys permit separate sends, with no financial-write
  locking beyond invoice reading.
branches: []
waits: []
evidence:
  - >-
    services-accounting at f47cb263: services/invoices/invoice-emails.ts,
    invoice-rendering.ts; services/ports/invoice-email-sender.ts;
    services/persistence/invoice-email-submissions.ts.
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-AVL-001, TC-AVL-004, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PRV-001, TC-PRV-002]
---
# E-mail an invoice

## What It Does

Explicit invoice delivery renders live content and delegates sending to Platform. Submission atomicity is distinct from delivery deduplication and eventual delivery.

## The Flow

```mermaid
flowchart TD
  h0["Invoice e-mails"]
  h1["Invoice rendering"]
  h0 --> h1
  h2["Platform queued communications"]
  h1 --> h2
  h3["Platform communications dispatcher"]
  h2 --> h3
```

The numbered hops identify the participating services, synchronization boundary and evidence. Branch conditions in the frontmatter explain where preparation, completion and recovery diverge.

## When Something Fails

Each hop records its rollback, retry or compensation boundary and the result visible to callers or operators. External outcomes can remain unresolved after local preparation commits; the listed recovery behavior defines what remains pending.

## Open Design Questions

The console lacks rendering and email controls; determine the staff surface without changing the recorded explicit-send decision.
