---
id: TST-ACC-TRX-051
title: Email invoice suite
kind: integration
product_area: accounting
module: transactions
revision: 1
status: draft
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
repository: services-accounting
files:
  - >-
    modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js
implements:
  - ATS-ACC-TRX-005-03
  - ATS-ACC-TRX-005-04
  - ATS-ACC-TRX-005-05
implements_parts:
  - scenario: ATS-ACC-TRX-005-03
    assertion: 'it refuses a draft: it has no number and no receivable'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      refuses a draft: it has no number and no receivable
  - scenario: ATS-ACC-TRX-005-03
    assertion: 'it refuses an invoice whose bill-to contact has no address, in 1.0’s words'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      refuses an invoice whose bill-to contact has no address, in 1.0’s words
  - scenario: ATS-ACC-TRX-005-03
    assertion: it refuses an organization’s invoice with no address named
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      refuses an organization’s invoice with no address named
  - scenario: ATS-ACC-TRX-005-03
    assertion: 'it refuses more than ten addresses: 400'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      refuses more than ten addresses: 400
  - scenario: ATS-ACC-TRX-005-03
    assertion: >-
      it goes to the addresses the caller names instead, canonical, without the
      contact as a recipient
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      goes to the addresses the caller names instead, canonical, without the
      contact as a recipient
  - scenario: ATS-ACC-TRX-005-04
    assertion: >-
      it refuses an e-mail without an idempotency key: 400
      idempotency_key_required
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      refuses an e-mail without an idempotency key: 400 idempotency_key_required
  - scenario: ATS-ACC-TRX-005-04
    assertion: >-
      it answers the request’s derived id, the same for the repeated key, and
      one pointer row names it
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js#it
      answers the request’s derived id, the same for the repeated key, and one
      pointer row names it
  - scenario: ATS-ACC-TRX-005-05
    assertion: >-
      it writes nothing, not even the event, when the sender’s statement fails:
      the command’s transaction rolls back
    provenance: test
    source: >-
      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
environment: local
implementation_state: implemented
latest_result: not-run
latest_run_at: null
latest_run_ref: null
conformance: not-verified
estimated_minutes: 3
isolation: >-
  Suite-marked records in the prepared local test database; fixture teardown
  removes owned records.
provenance: test
source: >-
  services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js
evidence:
  - provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/email-invoice.integration.spec.js
    assertion_titles:
      - e-mailing an invoice (integration)
      - the e-mail
      - >-
        goes to the bill-to contact’s primary address, from the template, with
        the document attached, in the transaction
      - >-
        records the invoice’s email event, with the request and the addresses,
        and no change of status
      - >-
        goes to the addresses the caller names instead, canonical, without the
        contact as a recipient
      - >-
        carries the caller’s subject and message, the message escaped, and its
        copies beside the template’s
      - >-
        takes the built-in invoice’s e-mail for an invoice without a template,
        from no one in particular
      - 'refusals, each submitting nothing'
      - 'refuses an invoice whose bill-to contact has no address, in 1.0’s words'
      - refuses an organization’s invoice with no address named
      - 'refuses a draft: it has no number and no receivable'
      - >-
        refuses an e-mail without an idempotency key: 400
        idempotency_key_required
      - 'refuses an address that is not one: 400'
      - 'refuses more than ten addresses: 400'
      - 'refuses an invoice the tenant does not have: not_found'
      - 'the production sender: Platform’s queued communications'
      - >-
        answers the request’s derived id, the same for the repeated key, and one
        pointer row names it
      - >-
        stores the whole request under the derived key: caller-rendered content,
        the document attached, the copies, the sender
      - >-
        writes nothing, not even the event, when the sender’s statement fails:
        the command’s transaction rolls back
    imports:
      - const chai = require('chai');
      - chai.use(require('chai-as-promised'));
      - 'const { sql } = require(''drizzle-orm'');'
      - >-
        const { requestObjectKey } =
        require('@rhythmsoftware/macroservice-communications/communication-objects');
      - >-
        } =
        require('@rhythmsoftware/macroservice-communications/testing/in-memory-communication-object-store');
      - >-
        const { tenantOf } =
        require('@rhythmsoftware/macroservice-execution/execution-context');
      - 'const { stage } = require(''@rhythmsoftware/macroservice-testing/stage'');'
      - >-
        const { createDocumentTemplate } =
        require('../../../../../books/services/persistence/document-templates');
      - >-
        const { createDraft } =
        require('../../../../services/invoices/invoice-drafts');
      - >-
        const { issueInvoice } =
        require('../../../../services/invoices/invoice-issue');
      - >-
        const { updateDetails } =
        require('../../../../services/invoices/invoice-changes');
      - >-
        const { emailInvoice } =
        require('../../../../services/invoices/invoice-emails');
      - >-
        const { renderInvoice } =
        require('../../../../services/invoices/invoice-rendering');
      - '} = require(''../../../../services/ports/invoice-email-sender'');'
      - '} = require(''../../invoices-fixtures'');'
      - 'const { putContact } = require(''../../parties-fixtures'');'
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { noAddress } = seeded.require();"
      - "\t\t\tconst { noAddress } = seeded.require();"
      - "\t\t\tconst { organization } = seeded.require();"
      - "\t\t\tconst { draft } = seeded.require();"
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { house } = seeded.require();"
      - "\t\t\tseeded.require();"
      - "\t\t\t\tconst { house } = seeded.require();"
      - "\t\t\tconst { house, first, again, submissions } = submitting.require();"
      - "\t\t\tconst { house, first, rendered } = submitting.require();"
      - "\t\t\tconst { house } = seeded.require();"
    test_double_declarations:
      - ' * Real: PostgreSQL, the invoice''s template and render, CRM''s table shapes as the stand-in declares them, the invoice services,'
      - ' * the shared submission helper and Platform''s submissions table (the local stand-in). Doubled: the sender (a recording double,'
---

# Email invoice suite

## Verification Boundary

The suite exercises transaction commands and database protections in the local prepared test environment. Registration records the existing executable file; it does not report a run or establish product approval.

## What This Suite Proves

Its cited assertions contribute evidence for sending requires an issued bill and recipients; communication retry retains one submission; submission failure rolls back delivery evidence. The structured assertion bindings identify which parts of combined scenarios it executes.

## What It Does Not Prove

Local commands and route invocations do not establish deployed routing, live processor guarantees or the complete staff browser experience. An unbound feature chain remains proposed, and this registration carries no execution result.

## Real Dependencies and Test Doubles

The suite uses the imported commands, route handlers or persistence code against the local test database. Fixture helpers supply accounting context; any stepped clock, dummy processor or substituted external client is retained in the evidence declarations.

## Must Not Be Doubled

The command or persistence behavior under assertion and its relevant database protections must remain real. Substituting the final financial result removes evidence of transactional effects.
