## ADDED Requirements

### Requirement: Batched manual send to a selection of guests (P0)
An admin holding the send-messages permission SHALL be able to send invitation emails to a
chosen set of guests belonging to one event in a single action. Every message in the batch
SHALL be recorded as a message job like any other send, so each appears in the message log,
carries its own delivery and bounce receipts, and is retried and reported on identically to
an automated send. The batch SHALL be written to the audit log once, against the acting
admin, with its event and its counts — not once per recipient.

#### Scenario: Sending to a selection
- **WHEN** an admin sends invitations to a selection of guests
- **THEN** each selected, sendable guest receives their own invitation email
- **AND** a message job for each of their invitations records the send

#### Scenario: Appears in the message log
- **WHEN** a batch has been sent
- **THEN** every message in it is listed in the admin message log with the same statuses as automated sends
- **AND** a later delivery or bounce receipt updates each one the same way

#### Scenario: One audit entry per batch
- **WHEN** a batch of two hundred messages is sent
- **THEN** the audit log records one entry naming the acting admin, the event and the counts

#### Scenario: Permission required
- **WHEN** an admin without the send-messages permission requests a batched send
- **THEN** the request is refused by the API regardless of what the interface offered

#### Scenario: Selection confined to one event
- **WHEN** a batched send names guests who do not all belong to the addressed event
- **THEN** the request is refused and nothing is queued

### Requirement: Server-composed defaults for both invitation types (P0)
For a given event the system SHALL compose two default messages on the server — one for the
single invitation type and one for the family/multiple type — each returned as four editable
parts: a subject, a header addressing the guest by name, a body, and a footer naming the
host. Each body SHALL be the invitation message that applies to that type — resolved through
the event's message, then the wedding-wide message, then the built-in default — followed by
the guest's personal invitation link. Composition SHALL NOT fall back across locales.

#### Scenario: Two compositions returned
- **WHEN** an admin opens the composer for an event
- **THEN** the response carries a complete composition for the single type and a complete composition for the family type

#### Scenario: Event override respected
- **WHEN** the event stores its own invitation message for a type
- **THEN** that type's composed body uses the event's message rather than the wedding-wide one

#### Scenario: Nothing customised
- **WHEN** neither the event nor the wedding stores a message for a type and locale
- **THEN** that composition uses the built-in default and is never empty

#### Scenario: Composition matches the single-guest panel
- **WHEN** a composition is made for an invitation type and locale
- **THEN** its body carries the same invitation sentence a single-guest composition for a guest of that type and locale would carry

#### Scenario: Selection spanning locales
- **WHEN** the selection contains guests of more than one preferred locale
- **THEN** a composition pair is available for each locale present, and a guest is only ever sent the pair for their own locale

### Requirement: Edited text is a per-recipient template (P0)
Every part of both compositions SHALL be editable before sending, and the edited text SHALL be
what is delivered. The text SHALL be treated as a template in which `{guest_name}` is
replaced by each recipient's name and `{invitation_link}` by that recipient's personal
invitation link, so no two recipients receive the same link. Edits SHALL apply to that one
batch only and SHALL NOT change the event's or the wedding's stored invitation messages. Each
delivered message SHALL carry the unsubscribe line regardless of what the admin edited.

#### Scenario: Per-recipient substitution
- **WHEN** a batch is sent from a body containing the name and link placeholders
- **THEN** each guest receives a message carrying their own name and their own invitation link

#### Scenario: Edited text is delivered
- **WHEN** an admin edits a composition and sends
- **THEN** the guests of that invitation type receive the edited text, not the composed default

#### Scenario: Edits do not persist
- **WHEN** an admin edits a composition, sends it, and then reopens the composer for the same event
- **THEN** the compositions shown are the stored defaults, unaffected by the previous edit

#### Scenario: Missing link placeholder rejected
- **WHEN** an admin sends a composition whose body no longer contains the invitation-link placeholder
- **THEN** the send is refused with a message naming what is missing, and nothing is queued

#### Scenario: Unknown placeholder rejected
- **WHEN** an admin sends a composition containing a placeholder the system does not recognise
- **THEN** the send is refused, naming the unrecognised placeholder, rather than delivering it literally

#### Scenario: Empty message rejected
- **WHEN** an admin sends a composition with an empty subject or an empty body while guests of that type are selected
- **THEN** the send is refused naming the empty part, and nothing is queued

#### Scenario: Unsubscribe survives editing
- **WHEN** an admin removes the unsubscribe wording while editing and sends
- **THEN** every delivered email still carries a working unsubscribe link for that event

### Requirement: A batch is the duplicate boundary (P0)
A batched send SHALL be identified by a batch identifier supplied with the request, and
repeating that request with the same identifier SHALL NOT produce a second message to any
recipient. A deliberate second batch to the same guests SHALL be delivered rather than
silently discarded. Duplicate protection for automated and scheduled sends SHALL be
unaffected.

#### Scenario: Repeated request is harmless
- **WHEN** the same batched send request is submitted twice with the same batch identifier
- **THEN** each recipient has exactly one message from that batch, and the second request reports the existing batch

#### Scenario: Deliberate resend
- **WHEN** an admin sends a second batch to guests who already received one
- **THEN** those guests receive the second message, recorded as its own batch

#### Scenario: Partially overlapping batch
- **WHEN** a second batch includes guests from a previous batch and guests who are new
- **THEN** every guest in the second batch receives its message

#### Scenario: Automated duplicate protection intact
- **WHEN** a reminder wave is planned twice for the same invitation and schedule
- **THEN** exactly one reminder job exists, as before

### Requirement: Suppression is not overridable by a batched send (P0)
A guest with no email address, a guest whose address has hard-bounced, and a guest who has
opted out of that event SHALL be excluded from a batch. The exclusion SHALL be counted and
reported before the batch is recorded, and no confirmation SHALL cause a suppressed guest to
be mailed. Suppression arising after the batch is recorded SHALL still prevent delivery.

#### Scenario: Excluded before sending
- **WHEN** a selection contains guests who cannot be emailed
- **THEN** no message job is created for them and their exclusion is reported with its reason

#### Scenario: API is the authority
- **WHEN** a batched send names a suppressed guest by any means
- **THEN** the API creates no job for that guest

#### Scenario: Opting out mid-batch
- **WHEN** a guest unsubscribes after their job is recorded but before it is sent
- **THEN** their message is not delivered and the job records why it was skipped

### Requirement: One quiet-hours decision for a batch (P0)
A batched send landing inside quiet hours SHALL warn the admin once, naming the local time,
and on explicit confirmation SHALL be delivered immediately for the whole batch rather than
deferred. Without that confirmation the batch SHALL NOT be recorded. Automated and scheduled
sends SHALL keep deferring past quiet hours unchanged.

#### Scenario: Warned inside quiet hours
- **WHEN** an admin sends a batch at 23:30 local time
- **THEN** they are warned that it is outside sending hours, naming the local time, and nothing is queued until they confirm

#### Scenario: Confirmed once for the batch
- **WHEN** the admin confirms a batched send inside quiet hours
- **THEN** every message in that batch goes out then, and the admin is not asked again per recipient

#### Scenario: No warning outside quiet hours
- **WHEN** an admin sends a batch at 14:00 local time
- **THEN** no warning is shown and the batch is recorded on the first activation

#### Scenario: Automated waves still defer
- **WHEN** a reminder wave becomes due inside quiet hours
- **THEN** it is deferred to the next opening time, as before

### Requirement: Batch progress is observable (P0)
A batched send SHALL report, from the moment it is recorded until every message reaches a
terminal state, how many of its messages are still waiting, have been sent, have failed and
were skipped, together with the reason for each failure or skip. The response that records
the batch SHALL NOT wait for delivery, and SHALL return within the time an interactive
request allows for a batch covering the whole guest list. Progress SHALL remain retrievable
after the composer is closed and reopened.

#### Scenario: Progress while sending
- **WHEN** a batch of two hundred messages is being sent
- **THEN** its progress reports the counts of waiting, sent, failed and skipped messages as they change

#### Scenario: Recording does not block on delivery
- **WHEN** a batch covering the whole guest list is submitted
- **THEN** the request returns once the messages are recorded, without waiting for them to be delivered

#### Scenario: Failures are attributable
- **WHEN** a message in a batch fails or is skipped
- **THEN** the progress report identifies the guest and states the reason

#### Scenario: Progress after reopening
- **WHEN** an admin closes the composer during a send and asks for that batch again
- **THEN** its current progress is returned

#### Scenario: Dry run
- **WHEN** dry-run mode is enabled
- **THEN** nothing is sent and the progress report states plainly that this was a rehearsal, not a delivery

#### Scenario: Provider not configured
- **WHEN** no email provider credentials are configured and dry-run mode is off
- **THEN** the batch is refused with a message naming the missing configuration rather than a provider error, and nothing is queued

#### Scenario: Delivery begins without waiting for the next poll
- **WHEN** a batch is recorded outside quiet hours
- **THEN** its messages begin sending immediately rather than waiting for the next scheduled sender pass
