Skip to main content

Blueprint: document actions and email

blueprint.yaml is the tenant's control file for Edge Docs: it declares which document actions users see in Salesforce, when each is enabled, what each hides or shows, how documents are named, when they expire, and what the delivery emails say. This page walks through each block as you configure it.

Anatomy of a document action

Every entry in create_document_actions follows the same shape:

create_document_actions:
- id: create_quote # stable action identifier
create_label: Create Proposal # button label in Salesforce
doc_type: option # one of the nine canonical types
doc_type_label: Proposal # user-facing type label
schema_version: '1.0' # pins the data contract
template_version: v1.0 # pins the template
default_name: '{{tour_name}} - Proposal {{date}}'
expiry_days: 30
expiry_help: Proposal valid for 30 days from creation
is_default: true
is_enabled: true
hide_sections:
- hold_until
enable_conditions:
record_type: Quote
status_not_contains: Option
disabled_message: Available for itineraries without Option status
email_template:
format: html
subject: Your proposal — {{tour_name}}
body: *email_body # shared body via YAML anchor

The pieces that matter most:

  • default_name supports placeholders ({{tour_name}}, {{booking_reference}}, {{date}}) resolved through the data transformation's cross_references block.
  • expiry_days sets the document validity window; expiry_help explains it to the user creating the document.
  • enable_conditions gate the action on the itinerary's record_type, status (status_contains / status_not_contains), and channel (channel_contains). When conditions are not met the action appears disabled with disabled_message as the explanation — users see why, rather than wondering where the button went.
  • schema_version / template_version pin the contract, so you can introduce option-2.0 without touching in-flight actions.

Section visibility: the hide_sections/show_sections_display mapping

Each action's hide_sections and show_sections lists are translated into _display flags injected into the document data at generation time; templates check the flags. hide_sections injects the flag as false; show_sections injects it as true (needed for sections whose default is hidden).

Flow: a blueprint action's hide and show section lists become display flags injected at generation time, which determine which registered sections render in the document
Section token_display flagDefault when flag is absent
hold_untilshowHoldBannerHidden
payment_buttonshowPaymentButtonShown
pricingshowPricingShown
travel_protectionshowTravelProtectionShown
termsshowTermsShown
price_linesshowPriceLinesHidden
agent_bannershowAgentBannerHidden
price_button(template-level token, no flag)

Sections with no backing data collapse regardless of flags. The expected flag combination per action is encoded in the tenant's QA manifest, so the test harness verifies every action's visibility set on every change.

The pricing ⊃ payment-CTA coupling

One interaction deserves explicit attention. In the proposal template, the in-page payment call to action is nested inside the pricing section — hiding pricing removes the in-page payment CTA along with the price panel. The floating action dock's pay button, however, is not inside the pricing section and is governed solely by payment_button (showPaymentButton).

Consequence: an action that hides pricing but not payment_button ships a document whose only payment affordance is the dock button — with no visible amounts anywhere. Unless that is exactly what you intend, hide payment_button alongside pricing. And because travel-protection copy ("From £x per person") and payment terms ("A deposit of £x") carry amounts too, a genuinely no-price document must hide those as well:

- id: create_no_price_quote
# No-price documents must not leak amounts anywhere.
hide_sections:
- pricing
- price_button
- payment_button
- travel_protection
- terms

The shipped actions

The baseline ships ten actions covering the full document type canon:

ActionDocumentEnabled whenNotes
create_quoteProposalQuote, not in Option statusDefault action; hides the hold banner
create_optionProposalQuote, in Option statusShows the hold banner; hides the payment button; expiry pre-filled from the option hold end date
create_no_price_quoteProposalQuote, not in Option statusHides every amount-bearing section (see above)
create_agent_proposalProposalTravel Agent channelsTrade variant; shows price lines and the agent banner
create_final_docsTravel DocumentsBooking, status Booking Confirmed365-day expiry
create_invoiceInvoiceBooking — no channel gateInvoices are offered on every channel by design
create_commission_invoiceCommission InvoiceBooking, Travel Agent channelsTrade-gated
create_commission_statementCommission StatementTravel Agent channelsTrade-gated, pro-forma
create_cancel_invoiceCancellation InvoiceBooking, status CancelledShipped disabled
create_supporting_documentCertificateBookingShipped disabled — regulatory slot

Structured-disabled capability

Capability you are not using yet ships as real YAML with is_enabled: false — never as commented-out configuration. A disabled action is visible to tooling, diffable, and carries working enable conditions and placeholder tokens, so enabling it later is a one-flag change rather than an archaeology exercise:

- id: create_cancel_invoice
create_label: Create Cancellation Invoice
doc_type: cancel_invoice
is_enabled: false # flip when the cancellation workflow goes live
enable_conditions:
record_type: Booking
status_contains: Cancelled

The regulatory certificate slot (create_supporting_document) follows the same convention, with one extra requirement: flipping is_enabled is not sufficient on its own, because the example document type has no data-transformation file. Enabling it for a real scheme also means renaming the doc_type to the scheme, replacing the schema and template with the regulator's prescribed layout, and adding a data-transformation file for the renamed type.

Auto-generated documents

Two document types are produced by platform triggers rather than user actions:

auto_generated_documents:
enabled: true
types:
- doc_type: booking_confirmation
trigger: booking_confirmed
recipient_preference: primary_contact
send_immediately: true
default_name: '{{booking_reference}} - Confirmation'
- doc_type: financial_summary
trigger: payment_received
recipient_preference: primary_contact
default_name: '{{tour_name}} - Financial Summary'

The booking confirmation is generated and emailed the moment a booking is confirmed; the financial summary is regenerated whenever a payment is received, keeping the guest's statement current. Each carries its own email template with bindings such as {{recipient.name}}, {{booking.tourName}}, and {{viewerUrl}}.

Email templates

Every user-triggered action references a single shared HTML email body, defined once under a YAML anchor and reused with body: *email_body. The body is fully brand-free: every brand-specific value resolves through {{branding.*}} bindings, and every document-specific value through document bindings:

<img src="{{branding.logo_url}}" alt="{{branding.logo_alt}}" height="40">
<p>Dear {{contact_name}},</p>
<p>Thank you for planning your journey with {{branding.footer.company_name}}.</p>
<p>Your personalised document for <strong>{{tour_name}}</strong> ({{tour_dates}})
is ready to view at the link below.</p>
<a href="{{viewerUrl}}">View Your Document</a>
<p>This link is valid until {{expiry_date}}.</p>
<p>Kind regards,<br><strong>{{itinerary_owner_name}}</strong></p>

Available bindings include {{contact_name}}, {{tour_name}}, {{tour_dates}}, {{viewerUrl}}, {{expiry_date}}, and {{itinerary_owner_name}}, plus the {{branding.*}} family (logo, company name, phone, website, tagline) resolved from the blueprint's email_branding block. Rebranding email means editing email_branding — never the HTML. Per-action subject lines are the one place actions differ.

Document validity and viewer banners

The document_validity block defines per-action wording for the validity statement rendered in the document (price_validity) and the banner the viewer shows after expiry (proposal_expired):

document_validity:
create_option:
price_validity: >-
The reservation is on hold until ${expiryDate}. After that date,
pricing and availability are subject to change.
proposal_expired:
message: >-
This proposal is no longer valid. Please contact your travel
specialist at {{itinerary_owner_email}} for an updated proposal.
contact_action: 'mailto:hello@meridiantravel.example?subject=Updated%20proposal%20request'

${expiryDate} resolves from the document's expiry parameters; {{itinerary_owner_email}} is substituted at send time. The baseline also carries a configuration slot for superseded-version wording (expired_banner_superseded_message): because every document records meta.revision and meta.supersedesDocumentId, your copy is ready for when superseded-banner selection is enabled for your tenant.

Beyond per-action banners, viewer_lifecycle defines the tenant-wide expired banner and, after a grace period (60 days in the baseline), a full-page inactive overlay with a contact call to action.

PDF settings

pdf_settings controls the print rendition: page format, margins, and — most importantly — page-break rules that keep logical units intact:

pdf_settings:
format: A4
margin: { top: 0.5in, right: 0.5in, bottom: 0.5in, left: 0.5in }
inject_print_css: true
page_break_rules:
- selector: .itinerary__day
action: avoid-break
- selector: .pricing__panel, .price-lines__table
action: avoid-break
- selector: .accommodation-card, .stay-units__card
action: avoid-break

avoid-break selectors target the CSS classes of the registered sections, so an itinerary day or a pricing panel is never split across a page boundary.