Offer Review, Amendment & Landlord Escalation
Offer Review, Amendment & Landlord Escalation
Available from v0.3.12
This feature gives agents a structured workflow to review incoming tenancy offers, request changes from tenants, escalate to landlords, and record final decisions — all with automated email notifications at each step.
How It Works
Offers move through a defined state machine. Agents drive transitions using the Action Panel on the offer detail page.
Status Flow
with_agent
├─► awaiting_amendments ──► (tenant resubmits) ──► with_agent
└─► sent_to_landlord
└─► landlord_reviewed
├─► accepted (terminal)
├─► rejected (terminal, no resubmission)
└─► awaiting_amendments
[any non-terminal status] ──► cancelled (terminal)
Step-by-Step
- An offer arrives at
with_agent. The agent sees two primary action buttons: Request Amendment and Send to Landlord. - Request Amendment → status becomes
awaiting_amendments. The tenant receives an email containing the agent's notes and a link back to their offer form. - When the tenant resubmits, the offer returns to
with_agentand the cycle repeats. - Send to Landlord → status becomes
sent_to_landlord. The landlord receives a branded email with offer details and any agent notes. - Record Landlord Decision → status becomes
landlord_reviewed, recording the landlord's feedback. - From
landlord_reviewed, the agent can Accept, Reject, or Request Amendment again. - Cancel is available from any non-terminal status at any point.
The Action Panel
The Offer Action Panel appears on the offer detail page for all non-terminal offers. It surfaces only the actions that are valid for the offer's current status.
Available Actions by Status
| Current Status | Available Actions |
|---|---|
with_agent | Request Amendment, Send to Landlord, Cancel |
sent_to_landlord | Record Landlord Decision, Cancel |
landlord_reviewed | Accept Offer, Reject Offer, Request Amendment, Cancel |
awaiting_amendments | Cancel |
accepted / rejected / cancelled | (no actions — terminal) |
Using an Action
- Click an action button to expand its inline form.
- Enter any required notes or reason in the textarea (up to 2,000 characters).
- Click the confirm button (e.g. Send Amendment Request, Accept Offer).
- The page refreshes automatically on success.
Note: Some fields are required before you can confirm. Rejection requires a reason; amendment requests require amendment notes. Optional note fields are labelled accordingly.
Email Notifications
Every status transition that affects an external party triggers an automated, agency-branded email.
| Trigger | Recipient | Content |
|---|---|---|
| Amendment requested | Tenant | Agent's amendment notes + link to offer form |
| Sent to landlord | Landlord | Full offer details + agent notes |
| Landlord decision recorded | Agent | Internal notification of landlord feedback |
| Offer accepted | Tenant | Congratulations notification |
| Offer rejected | Tenant | Rejection notification with reason |
Emails are dispatched via Inngest event-driven functions and rendered using branded HTML templates that inherit your agency's branding configuration.
Offer Detail Page Changes
- Status description — A plain-language description of the current status is displayed below the status badge to give agents immediate context.
- Transition timeline — A full history of every status change is shown, with timestamps and reasons, so agents can see at a glance what has happened to an offer.
- Negotiation Thread — The comments section has been renamed "Negotiation Thread" to better reflect its role in the offer lifecycle.
API Reference
All offer workflow actions are exposed as tRPC mutations under the offer router.
offer.requestAmendment
mutation({ offerId: string, notes: string })
// notes are required
Transitions the offer to awaiting_amendments and sends the amendment email to the tenant.
offer.sendToLandlord
mutation({ offerId: string, notes?: string })
Transitions the offer to sent_to_landlord and sends the offer summary email to the property's landlord.
offer.recordLandlordDecision
mutation({ offerId: string, notes?: string })
Transitions the offer to landlord_reviewed, recording the landlord's feedback.
offer.acceptOffer
mutation({ offerId: string, reason?: string })
Terminal transition to accepted. Sends a congratulations email to the tenant.
offer.rejectOffer
mutation({ offerId: string, reason: string })
// reason is required
Terminal transition to rejected. No resubmission is possible. Sends a rejection notification to the tenant.
offer.cancelOffer
mutation({ offerId: string, reason?: string })
Terminal transition to cancelled. Available from any non-terminal status.
Audit & History
Every action performed via the offer router:
- Writes a row to
offer_status_transitions(previous status, new status, notes, timestamp, acting user) - Emits an audit log event for compliance and traceability
The transition timeline on the offer detail page reads directly from this history.