Skip to content
Gaurav TiwariDocs

Customer Emails and Order Invoices

Version 0.2.0 Updated November 15, 2026

How customer emails are sent and styled, every template and its merge tags, reminders and checkout recovery, and the order invoice and credit note pages and PDFs. Client invoices have their own page: Client Invoices.

How email is sent

Every customer email goes through EmDash’s ctx.email, so you need an email provider plugin installed. Vendwick can’t set the From address, BCC or attachments; ctx.email takes one recipient, optional CC and reply-to, and a text and HTML body.

Every message is written to the mailQueue collection first. That gives you an email log in the admin (Emails page), retries (up to 5 attempts, 15 minutes apart, flushed by the 5-minute cron tick) and a once-only key so the same notice is never sent twice. On a site with no email provider, messages are kept with the status no_provider.

Plain or branded

The Customer emails and reminders module (emails toggle, off by default) decides how emails look:

  • Module off: each email is the built-in template rendered as plain text plus a minimal HTML version (heading, paragraphs, one link). Unknown merge tags are left as written.
  • Module on: your saved subject and body edits, the branded HTML layout (store name, accent color, logo, footer, support link), order lines when the email is about an order, and the text part. Unknown merge tags are removed. You can turn single templates off; a turned-off template sends nothing.

Branding settings (module on): emails_reply_to, emails_brand_color, emails_logo_media (a media ID) or emails_logo_url (absolute https URL), emails_footer, emails_support_url. The admin can preview a template and send a test.

Reminders and checkout recovery only run when the module is on.

Templates

Tags listed are the ones each default template uses. With the module on, {site_name}, {site_url}, {customer_email} and {recipient} are always available.

TemplateSent whenTags in the default
purchaseAn order is paidsite_name, order_number, customer_name, next_steps, order_items (0.2.0: the item list, empty for one item of quantity 1), license_keys, dashboard_url
free_confirm (0.2.0)A free checkout waits for the buyer’s email confirmationsite_name, product, customer_name, hours, confirm_url
accessA customer asks for an account linksite_name, dashboard_url
renewalA renewal is paidproduct, customer_name, expires_at, dashboard_url
upgradedAn upgrade is paidproduct, customer_name, plan, sites, dashboard_url
admin_orderAn order is paid, to admin_order_email (skipped when blank)site_name, order_number, customer_email, customer_name, order_answers, admin_orders_url
refundedAn order is fully refundedorder_number, customer_name
refunded_partial (0.2.0)A refund that doesn’t complete the order settlesorder_number, customer_name, refund_amount, refund_note, refunded_total, order_total, dashboard_url
admin_dispute (0.2.0)A dispute opens, to admin_order_emailsite_name, order_number, customer_email, dispute_amount, provider, dispute_reason, admin_orders_url
admin_refund_failed (0.2.0)The provider fails a refund it had accepted, to admin_order_emailsite_name, order_number, customer_email, provider, refund_amount, refund_outcome, admin_orders_url
order_shipped (0.2.0)A shipment leaves (Physical Goods and Shipping)site_name, order_number, customer_name, shipment_items, carrier, tracking_number, tracking_url, shipping_address
order_delivered (0.2.0)A shipment is marked delivered (when the delivered email is on in Shipping settings)site_name, order_number, customer_name, dashboard_url
reminder_3030 days or less before expiry (more than 7)product, expires_at, customer_name, renew_url
reminder_71 to 7 days before expirysame
reminder_0Within a day of expirysame
expired3 to 4 days after expirysame
renewal_upcomingWithin 7 days of an automatic renewalproduct, expires_at, customer_name, dashboard_url
payment_failedA subscription renewal payment failsproduct, customer_name, payment_url
subscription_cancelledAutomatic renewal is turned offproduct, customer_name, expires_at, dashboard_url
checkout_recoveryAn unfinished checkout (recovery on)product, plan, customer_name, coupon_line, checkout_url
page_access_linkA visitor asks for a page access linksite_name, access_url
invoice_issued, invoice_reminder, invoice_overdue, invoice_paid, invoice_void, invoice_refundedClient invoicessee Client Invoices

The receipt, renewal and upgrade emails also get {invoice_url} and {invoice_pdf_url} (0.2.0) when the order has an invoice; the refund emails (refunded, refunded_partial) get {invoice_pdf_url}. They aren’t in the default text. With the emails module on, the branded layout adds "View your invoice · Download your invoice (PDF)" under any email that has an invoice link, in the HTML and the text part. EmDash email can’t carry attachments, so the PDF is always a link.

Reminders

A daily job looks at licenses that expire within 30 days or expired up to 4 days ago and sends the one stage that’s due:

  • Licenses on an automatic provider renewal (active subscription with auto-renew on) only get renewal_upcoming, within 7 days.
  • Daily, weekly and one-month terms skip reminder_30, so a new monthly license doesn’t get a 30-day reminder on day one.
  • Each stage is sent once per license and expiry date. A renewal moves the expiry date and starts a fresh set.
  • Erased customers and invalid addresses are skipped.

Checkout recovery

Off until you turn on recovery_enabled, because it emails people who didn’t buy. Make sure your privacy policy covers it. An hourly job then emails:

  • purchase checkouts that are pending, failed or expired, created between 72 hours ago and recovery_delay_hours ago (1 to 72, default 2);
  • only while the product and plan are still active;
  • once per checkout, at most once a week per person, and never if that email has since paid for the same product.

The link goes back to checkout for the same product and plan. When recovery_coupon is set, it’s added as ?coupon= and described in {coupon_line}. There’s no unsubscribe link.

Order invoices

Turned on by the Invoices module (invoices toggle, off by default).

  • Every paid order with a total above zero gets one invoice number. Free orders get none.
  • A fully refunded order that has an invoice number gets one credit note number.
  • Since 0.2.0 every refund gets its own credit note (see Refunds and Disputes); the one that completes the refund is the order’s own credit note.
  • Numbers come from atomic counters, so parallel orders never share one. Order invoices and client invoices share one invoice series.
  • A number can be skipped (a gap in the series) when a run dies between allocating and recording it, or when two deliveries of the same order race.

Settings:

SettingDefault
invoice_prefixINV-{year}- ({year} and {month} are filled in when the number is issued)
credit_note_prefixCN-{year}-
invoice_padding5 digits (1 to 10)
invoice_seller_name, invoice_seller_address, invoice_seller_tax_id, invoice_footerblank (the seller name falls back to the store name)

The invoice page

GET /_vendwick/invoice?order=<id>&key=<key> shows the printable invoice. Add &doc=credit-note for the credit note, and &refund=<refundId> for one refund’s credit note. Add &format=pdf for any of them as a PDF file.

  • Access: the keyed link from emails and the portal, the portal session cookie when the browser sends it, or a signed-in EmDash admin. Any failure shows the same 404 page.
  • The page is HTML with a print stylesheet, a "Download PDF" link and a "Print or save as PDF" button.
  • It’s sent with Cache-Control: private, no-store, X-Robots-Tag: noindex, nofollow, Referrer-Policy: no-referrer and a strict Content Security Policy.
  • When the order paid a client invoice, the page redirects to that client invoice, which carries the same number.

Admins can assign a number or credit note by hand, copy the keyed link and preview the page from the Invoices admin screen. The order screen has an Invoice documents section with the View and PDF links of the invoice and every credit note.

PDF downloads

Added in 0.2.0. Order invoices, credit notes (one per refund) and client invoices download as PDF:

DocumentURL
Order invoice/_vendwick/invoice?order=<id>&key=<key>&format=pdf
Credit note...&doc=credit-note[&refund=<refundId>]&format=pdf
Client invoice/_vendwick/pay/<code>?key=<key>&format=pdf
  • Same access as the page. The keyed link, the owner’s portal session or an EmDash admin; any failure is the same 404 page as the HTML. A paid client invoice’s order redirects to the client invoice PDF, as the page does.
  • Headers: Content-Type: application/pdf, Content-Disposition: attachment; filename="INV-2026-00042.pdf" (the number, with anything outside letters, digits, ., _ and - replaced), Cache-Control: private, no-store, X-Robots-Tag: noindex, nofollow, Referrer-Policy: no-referrer.
  • Where the links are: "Download PDF" beside "Print" on the invoice page and the client invoice pay page, "Download PDF" and "Credit note … (PDF)" in the portal order view, {invoice_pdf_url} in emails, the order screen’s Invoice documents section and the client invoice screen’s PDF button.
  • What it shows: the same document as the page, from the same data, so the numbers always match: seller block (with the Appearance logo and accent color when you saved an Appearance), buyer block, title and number, dates, a status stamp (Paid, Refunded, Partly refunded; for client invoices Awaiting payment, Overdue, Paid, Void, Refunded, Draft), the lines with quantity, unit price and (when the order stored it per line) tax, bundle contents under their bundle, subtotal, discount, shipping, tax with rate, the reverse-charge note, the total, refund notes, the payment reference, your footer and "Page 1 of N". Long invoices continue on more pages with the table header repeated.

Settings

On the Invoices admin screen, under PDF downloads:

SettingDefault
invoice_paper_sizea4a4 or letter (US Letter)
invoice_pdf_engineautoauto, builtin or browser
invoice_browser_account_idblankCloudflare account ID (32 hex characters)
invoice_browser_api_tokenblankCloudflare API token with the Browser Rendering edit permission (a secret setting, encrypted)

Preview PDF renders a sample invoice with your seller block, branding, paper size and engine, without storing it.

Engines

  • Built-in (always available): a small PDF writer inside Vendwick, no dependencies, works on Workers and Node. It uses the standard PDF fonts (Helvetica, Helvetica-Bold, Courier), which every PDF reader has, so files are small (a one-page invoice is a few kilobytes) but the text is limited to the Windows-1252 (WinAnsi) character set: English and Western European letters, curly quotes, dashes, the euro, pound, yen and cent signs.
    • It can’t draw other scripts (Hindi, Arabic, Chinese, Cyrillic, Greek and so on), emoji, or currency symbols outside that set. Letters with marks it lacks lose the mark ("ł" becomes "l", "ő" becomes "o"), currency symbols become their ISO code ("₹" becomes "INR"), and anything else becomes "?". Vendwick’s own amounts are always written with ISO codes ("INR 1,000.00"), so totals are never affected; names, addresses and item titles can be.
    • The logo is drawn when the Appearance logo is a media library image in JPEG or PNG (8-bit, not interlaced; transparency is kept). A logo given as a URL, or in another format, is left out of the built-in PDF.
  • Cloudflare Browser Rendering (optional): Cloudflare prints the same HTML invoice page in a real browser, so every script and the logo render. It needs a Cloudflare account with Browser Rendering, the account ID and an API token. Vendwick calls https://api.cloudflare.com/client/v4/accounts/<id>/browser-run/pdf (falling back to the older /browser-rendering/pdf path) through EmDash’s outbound HTTP, which is why the plugin asks for the network:request capability and lists api.cloudflare.com as an allowed host. Cloudflare bills Browser Rendering by browser time. The request shape follows Cloudflare’s documentation and hasn’t been tested against the live API yet.
  • Automatic (auto, the default) uses Browser Rendering only when it’s configured and the document has characters the built-in engine can’t draw. browser uses it for every document. builtin never calls Cloudflare. Without credentials every setting renders with the built-in engine.
  • If Browser Rendering fails (an error status, a timeout, something that isn’t a PDF), the built-in engine renders the document instead and the audit log gets an invoice_pdf_fallback event with the reason.

Cache

A generated PDF is stored in private files (the same private storage as release files, never public) at invoices/<kind>/<number>-<hash>.pdf. The hash covers everything on the document plus the paper size, engine and logo, so a changed order, refund, seller setting or Appearance gets a new file and the old one is deleted when the new one is written; a second download of an unchanged document streams the stored file without rendering. Drafts are never stored. When the site has no private files configured, PDFs are rendered on every request. Customer erase deletes the customer’s stored PDFs (see Data, Privacy, Export and Erase).

Limits

  • Rendering runs in the request. A one-page invoice takes a few milliseconds of CPU with the built-in engine; very long invoices (hundreds of lines) take longer, and the first download pays that cost once because of the cache. An invoice of 200 or more lines takes about 10 ms of CPU on its first render: fine on Workers Paid, close to the 10 ms per request that the Workers Free plan allows, so a store on the free plan with very long invoices should expect the occasional failed first download (the next one is served from the cache).
  • PDFs are rate limited to 30 per minute per visitor IP.
Share

Was this helpful?

Still stuck? Open a support ticket