Skip to main content

PBJ Templates — Changelog

Current release: 1.13.1 (September 2026)

1.13.1 — 6 September 2026

  • The designer opens from the campaign screen: “Designed email (HTML)” on a marketing email gives that email a design of its own — made from its name, its subject and anything already written in it — and edits it in place; an email made from a template opens on that same template. Answers CRM contract 37, and unlocks the CRM’s HTML email gate while active.
  • Answers the CRM’s two doorway questions (contract 36): every Templates settings screen leads with the designer and the matching templates, and a person’s own settings open their signature in the designer.

1.13.0 — September 2026

  • What somebody owes, in a marketing email. A marketing email, a letter and a document can now use fields that are answered for a PERSON rather than for a document: their latest unpaid invoice, what they still owe across all of them, when the latest one is due, a link to pay it and the late fees on it. They arrive under their own heading — The money · Invoicing — beside the person’s own details, and they only appear when Invoicing is here and has said so. A marketing email still has no ticket and no visit behind it, and the card says so in full: “A marketing email has no ticket behind it, so those fields would come out blank.”
  • Your signature, as a block. Drag Your signature into a marketing email, a letter or a document and it fills in with the sign-off of whoever the email goes out as — the person who wrote the campaign at a send, you while you are looking at it. With nobody’s signature to draw it puts nothing there rather than an empty panel. It is not offered on a signature template (that IS the sign-off) or on a portal notice (nobody sends one). Putting the block in is the choice, so it does not wait on the Ticket replies / Invoice emails / Visit reminders switches — those still govern the sign-off the suite adds by itself, and a campaign nobody put the block into still signs off as the company.
  • See the real values before you send. “Show the real values for” sits at the top of the Customer data card: pick a person or a business and every field says what it will actually say for them — including the money — with “nothing on their record” where a box is empty. It reads only what you are allowed to read: a customer outside your own book is refused in one sentence.
  • Every block tile says where it came from. A small CRM, INV, DISP, HD or RAW on the corner of each tile, so a rail of fifteen blocks is readable at a glance.
  • The library says more, and offers the next thing. The type chips are counted properly — “Marketing emails 4”, “Notices 3” — and a card now carries the one fact that matters for its kind (“Used by 2 campaigns”, “Fills in from each agent’s own profile · 4 people”, “prints on letterhead, one page”) with the one thing you would do next beside Open and Copy: Send a test, Print sample, Who uses it.
  • The seeded invoice layout puts your phone number on the letterhead beside your address, and the pay button now says which ways to pay it will offer — read from the gateways you have actually switched on, not a fixed list.
  • Consumes PBJ CRM services contract 35 (per_contact token contexts, pbj_crm_contact_token_values, sender_user_id) entirely behind guards: on an older CRM nothing new appears and everything else is exactly as it was. DB stays 2. GET templates/{id}/tokens takes an optional contact_id.

1.12.0 — September 2026

  • Who may change a template follows your CRM’s groups. Managers, owners and administrators change anything. Everybody else changes a template their own group owns, or one they made themselves — an ungrouped template with your name on it stays yours when a manager takes it out of a group, or takes you out of one. A published default (the invoice layout, a notice) is a manager’s whatever the group: it is not one template, it is every invoice on the site. Deleting follows changing, exactly as it did.
  • Anyone can copy one. The library card now offers Copy where Open would be on a template you cannot change, and the copy comes back as yours — filed in your own group when it can be, and otherwise as an ungrouped template with your name on it, so you can open it straight away. Copying somebody else’s template is never refused; refusing it only meant the same email got retyped, badly, somewhere else.
  • One sentence, three doors. The builder, the preview and the settings form all say the same thing on a template you may not change: “You can copy this template, but only the person who made it or a manager can change it.” A default says why it is different. can_copy rides on every template the API answers with, beside can_edit and can_delete.
  • The signing page, on a phone. A real 390px pass on /pbj-sign/<token>: the document itself is fluid — an email table’s width="600" no longer forces a sideways scroll — with the side padding pulled in under 720px and every image capped to the screen. The draw pad keeps touch-action: none and nothing else on the page does, so the page still scrolls while the pad still draws; the pad has a floor of 56px for a short landscape window; and the confirm sheet scrolls inside the viewport instead of putting Yes, sign it off the bottom of it. Type it / Draw it, Clear, Download the PDF and Print it were already 44px and stay there.
  • On a phone the builder is one column — Blocks and Preview across the top (and Send, once a draft campaign exists for the template; before that, “Send it as a marketing email” is on the Preview screen), a 44px up and down on every block instead of a drag, editing in place, and Take one for a photo from the van. That screen is the CRM’s own, drawn from this module’s block descriptors; nothing here changed to get it.
  • DB stays 2. No new settings, no new routes.

1.11.0 — September 2026

  • Signing links are never tracked. Declares its /pbj-sign/ route to the CRM’s click tracking (services contract 33, filter pbj_crm_tracking_skip_paths), so a marketing email carrying a document link keeps that link untouched: it is never rewritten through the click redirect and the signing token never lands in the CRM’s links table. Declaration only; DB stays 2.

1.10.0 — September 2026

  • Send it as a marketing email. A published marketing template’s preview gains the button; it makes a draft campaign in PBJ CRM from the template (subject, body and blocks carried over, the campaign remembering which template it came from) and takes you to it — or, when a draft already exists for the template, the builder and the preview show Go to send instead and land on that one. Nothing is made twice.
  • The campaign’s own checks. The template’s lint rows (a photo with no description, a subject a phone cuts, and so on) appear under “Before it goes” on the CRM’s Send & schedule screen for a campaign made from one of our templates — appended to whatever else the CRM has found, never in place of it.
  • CRM services contract 32 (reply_mailboxes(), create_campaign(), campaign_lint(), campaign_for_template()) is consumed behind method_exists(); on an older CRM the button is absent and the preview’s Take it away card explains why. Route: POST templates/{id}/campaign (staff, marketing kind only). DB stays 2.

1.9.0 — September 2026

  • Documents a customer signs. Two new blocks — Signature box (whose signature it is, customer or us; what it is labelled; whether it asks for a printed name and a date) and Initials box — and a document with at least one signature box, published, can be sent to a person or a business from their own record. “Both of us sign” is two separate boxes: the customer signs theirs on their link, one of us signs ours from the record, in either order, and the document counts as signed only when every box is signed.
  • Send it from the record. “Send a document to sign” sits with the other Add work actions on a person or a business. Pick the document, see it filled in before it goes, write a line or two, choose which mailbox it leaves from, and press Email it to sign. It refuses in plain words when the template is not published, when it has no signature box, and when the record has no email — on a record with no email address the action is drawn struck through with the reason on it, and pressing it does nothing.
  • What the customer gets is one email with one button, Read and sign, opening a page of its own at /pbj-sign/<token> — no login, no account, nothing to install. The document is there filled in with their details, and under it every box they have to sign: Type it (their name in a handwriting face already on their phone) or Draw it (a finger on the glass, with Clear), their printed name, the date, their initials where you asked for them, and one button — Sign and send it back — which asks once more before it sends. A page that has been signed, called off, or left thirty days answers with one sentence and your phone number, and gives nothing else away.
  • What happens when they sign. The signature is drawn into the document where the box was and that copy is frozen — the template stays editable, the sent copy never changes. With PBJ Invoicing 7.04 present it is also written as a PDF, filed against the customer, tagged Signed document, and named so the file itself carries the audit (“Service agreement — signed by Marcus Webb 2026-09-05.pdf”). Without invoicing there is no PDF: the signed copy is the module’s own page, opened from the tab and printed with Print it. You and every manager who wants the notice get an email with a link straight to the record, the customer gets their own copy with the PDF on it, and the record’s timeline gets the line.
  • The Documents tab on a person or a business lists everything sent — WAITING, WAITING ON US, SIGNED, CALLED OFF, EXPIRED — with the date, the audit line (“Signed by Marcus Webb on 5 Sep 2026 at 6:43 pm from a phone (IP 203.0.113.9)”) and what you can do about it: Countersign (our box, opens the signing page as us), Resend (a fresh link, the old one dead), Call it off (both links dead), Open, and Delete — managers and owners only, and only after typing DELETE, because there is no undo. The file it made is unpinned, not destroyed.
  • A waiting signature shows up in the Inbox as something needing attention (“Waiting for a signature — <title> · <customer>”), and When a customer signs a document is a notice you can turn on or off per person like every other.
  • The link closes itself. Thirty days by default — one number, sign_link_days, on the Templates & Notifications panel, one to three hundred and sixty-five.
  • The seam is the CRM’s one seam: services contract 31 adds send_email_from() (so the email leaves from the mailbox you chose, with the signed PDF on it) and sender_choices(), and record_actions gains requires: 'email'|'phone' so a row the record cannot use is drawn struck through with its reason instead of failing when pressed.
  • REST: GET/POST documents, GET documents/options, POST documents/{id}/resend, POST documents/{id}/cancel, DELETE documents/{id}, GET documents/{id}/open, GET documents/{id}/countersign.
  • Database 1 → 2: one new table, pbj_tpl_documents, one row per document sent. It is declared to the CRM’s backup manifest keeping its ids, because the file and the links are tied to them. Nothing else changed.

1.8.0 — September 2026

  • Invoice and estimate layouts — rules, not HTML. A layout is built like every other template, and PBJ_TPL_Paper::rules() DERIVES from the design and the Paper card what the paper must obey: size (Letter or A4), margins, whether the top repeats on page 2, whether the pages are numbered, which line-item columns show and in what order and under what heading, the colours, the font, the logo, the business name and the words after the totals. PBJ Invoicing 7.04 obeys the rules and builds, itself, the parts only it can build — the lines, the totals, the pay button, the stamp, the payments, the notes and the terms.
  • Three new blocks. Line items — its columns are the block’s own setting (what we did, qty, each, tax per line, details under each line, total; the description and the total are always shown and say so by having nothing to press) and it renders as {items_table}. Totals, with or without the payments taken. Pay button, with its own wording (“Pay this invoice” to start with). The 1.3.0 note saying an invoice-lines block could not be built is gone — {items_table} arrived with 1.7.0 and the block hands the whole repeating table over under one name. The “Only show if…” rule block is still not built and still says why.
  • A new layout opens as a whole invoice, seeded as seven blocks — letterhead, INVOICE and its number, BILL TO beside DUE, the line items, the totals, Pay this invoice, and a closing line — on Letter with 0.6 in margins, the top repeated and the pages numbered.
  • Four cards on the right, in the order the paper reads. Paper (size, margins, “Repeat the top on page 2”, “Page numbers”, and the honest sentence “The PDF prints in Helvetica; the web page and email use your font.”). Line items block (drag a column into place, rename it, show or hide it — the same control as the block’s own settings, so there is one of it, not two). This layout is used by (Every invoice, Every estimate, Recurring billing, with the count on each and “Saving changes it for all of them. Copy it first if you only want it on one.”). Check it on paper (“Make a sample PDF” opens your longest real invoice in a new tab, “Use a real invoice” lets you pick one, “Open the print view”, and one sentence saying which invoice the sample used — or “Make an invoice first, then check the layout on it.” when there is nothing to draw).
  • Preview shows a layout as an invoice, not as an email — Computer, Phone and PDF, drawn from a real document of yours, with the PDF in the frame.
  • One published default decides it. The default layout owned by Invoices, published and not binned, is what every invoice, estimate and recurring bill prints through; a draft, a binned one, or none at all, and invoicing prints exactly what it printed before — page, email and PDF alike.
  • Letters get the paper too — size and margins, and “Open the print view”.
  • The seam is the CRM’s one seam (services contract 30, filters pbj_crm_layout, pbj_crm_layout_uses, pbj_crm_layout_sample). Nothing to say is a correct answer, and it means “print what you print today”.
  • REST: GET/PUT templates/{id}/paper, GET templates/{id}/paper/sample?invoice=<id>&format=pdf|html, GET templates/{id}/paper/print; paper folded into GET templates/{id}; GET templates/{id}/records for a layout answers with your invoices rather than your contacts.
  • Paper lives in pbj_tpl_template_meta. No database change.

1.7.0 — September 2026

  • Notices, authored in the builder. Make a template → Notice → which part of the CRM sends it → which email it is (required; the list comes from what that module actually sends — nine help-desk emails, eight visit emails, five invoice messages). The new notice opens SEEDED with the words that email sends today, one paragraph per blank-line chunk, and its subject line.
  • It sends by itself. Publish it and PBJ Helpdesk 7.02, PBJ Events 7.02 or PBJ Invoicing 7.03 sends your design in place of its text — subject included when you set one — through the CRM’s one seam (services contract 29, filters pbj_crm_notice_templates / pbj_crm_notice). Unpublished, binned or absent → today’s email verbatim. There is deliberately no “default notice for a whole module” — that would send the wrong email.
  • Library cards and the builder header for a notice read “<Module> · <which email> · sends by itself”.
  • Portal notices show on client-facing pages only (your answer): the client help-desk portal and its sign-in, the invoice / estimate page, and the CRM sign-in page — drawn by the CRM’s one renderer (pbj_crm_portal_notices); never inside the staff CRM. Published = shown.
  • Invoices now has fields. PBJ Invoicing 7.03 declares invoice_email (customer name, their business, number, total, balance, due date, the pay link, your business name, the lines as a table), so an invoice notice or layout offers real fields instead of “Invoices has not told the CRM its fields yet” — that sentence stays for a site whose invoicing has not updated.
  • REST: GET kinds lists each notice owner’s events; POST templates takes event (refused by name when missing or unknown); GET/PUT templates/{id} carry it.
  • Event lives in pbj_tpl_template_meta. No database change.

1.6.0 — September 2026

  • Preview — a Preview pill in the builder opens #/templates/<id>/preview: Computer, Phone, Side by side and Plain text views, “Showing it as” a real customer from the CRM (only records you may see), Copy the HTML.
  • Dark mode, simulated honestly — the same email rendered with a dark background, dark paper and light words; a button darker than the page would vanish, so it flips to white and the screen says so in one sentence. One thing to check, not a setting.
  • Plain text version — the automatic twin, or Write my own; your own words are kept and never regenerated; “Use the automatic one” puts it back. Sent alongside — some people only get this.
  • Subject line — a field above the preview for the kinds that have one (marketing, notices, portal notices); 250 characters at most, refused by name beyond that.
  • Things worth fixing — photos with no description, the subject line’s length against a phone’s ~40 characters, every merge field checked against the customer you are showing it as (a field with nothing behind it and no fallback is named; a field no module on this site fills in is called out), and the unsubscribe note for marketing.
  • Take it away — Download the HTML (merge fields still in it, as a whole document), Copy it to the clipboard, Send a test to me (to your own account email, filled in for the customer you picked).
  • REST: GET templates/{id}/preview?as=, GET templates/{id}/records?q=, PUT templates/{id}/plain-text, PUT templates/{id}/subject, GET templates/{id}/download, POST templates/{id}/test-send. Needs pbj-crm 7.09 (services contract 28: contact_tokens, send_email).
  • Subject and your own plain text live in pbj_tpl_template_meta. No database change.

1.5.0 — September 2026

  • Signatures. A signature template’s words carry the staff-profile fields ({agent_name}, {agent_title}, {agent_phone}, {agent_email}, {agent_photo} …) and fill themselves in per person — change a phone number once on the profile and every signature follows.
  • Showing it as — a switcher over the canvas previews the signature as any staff member, with their photo (or a dashed “photo” circle when the profile has none). The stored template keeps the field names; only the preview changes.
  • Who signs with this — assign people (any CRM staff member; an id off the staff list is refused by name), remove them, “Add somebody”. A copy of a signature signs for nobody until somebody says so.
  • Where it gets used — Ticket replies, Invoice emails, Visit reminders, each on or off; Marketing emails always read “No — those sign off as the company” and cannot be switched (an unknown place is refused by name).
  • Copy it out — “Copy <name>’s” puts that person’s finished HTML on the clipboard for Gmail or Outlook; “Download all <N>” is one HTML file with everybody’s, headed by their names.
  • The seam. PBJ_CRM_Services::signature_for( user, context ) (pbj-crm 7.09, services contract 27, filter pbj_crm_signature) answers with the signature assigned to that person and switched on for that place — an explicit assignment beats the kind’s default, drafts and binned templates are ignored. PBJ Helpdesk 7.01, PBJ Invoicing 7.02 and PBJ Events 7.01 ask it, guarded, and fall back to what they do today.
  • A picture block may now hold {agent_photo} as its address (pbj-crm 7.09 lets one whole token stand as a URL); a signature rendered for somebody with no photo simply leaves that block out.
  • REST: GET/PUT templates/{id}/signature, GET templates/{id}/signature/render?user=, GET templates/{id}/signature/download (staff; the download link carries the REST nonce because a browser navigation has no header to put it in). GET templates/{id} folds signature in so the builder opens in one request.
  • Assignments live in pbj_tpl_template_meta (signers, uses) — the table 1.0.0 already made. No database change.

1.4.0 — September 2026

  • Photos panel on a Photo block: three tabs — CRM files, WordPress media, Upload — a search box, one chip per customer and per tag, a thumbnail grid with a DROP TO UPLOAD tile, the picked file’s name, size and pixel size, and the honest note “Will be shrunk to 1200px wide for email” when it will be.
  • “What it shows (for people who can’t see images)” is required before “Put it in” — an email never goes out with a picture nobody can describe.
  • Pin to this customer: pick a customer chip and a photo that is not yet in their files, and one press pins it there. A photo already in their files says so instead. Under “All” the button is not drawn.
  • Uploads from the builder go into the CRM’s ONE files store, pinned to the template (this plugin registers the template file target with PBJ_CRM_Files::register_target()), through the CRM’s own chunked uploader — no second store, no second uploader.
  • The email copy of a CRM photo is a public, unguessable address served by the CRM (files/{id}/email/{token}), re-encoded at up to 1200 pixels wide with the camera’s metadata stripped; the original never changes. WordPress media is already public and is used as it is (the largest size up to 1200px).
  • A Photo block now remembers which CRM file it came from (file_id), so a later release can lint it. Needs pbj-crm 7.09 (services contract 26).
  • No database change.

1.3.0 — September 2026

  • Customer data card in the builder, under the block settings: every field this kind of template is allowed to use, grouped by where it comes from (the person · CRM; the visit · Events; the ticket · Help desk), in plain words with the field name on hover, and a search box. Custom fields are drawn grey — “your own boxes on the record.”
  • Not in this kind of template: the fields every OTHER installed module declares, struck through, with the reason in one sentence — “A marketing email has no ticket behind it.” A kind whose module has declared nothing says so honestly (“Invoices has not told the CRM its fields yet.”) and never invents a field.
  • Click a field and {name} goes in at the caret of the box you were typing in; drag one in; or type {{ and a “Pull in customer data” picker opens — choosing replaces the {{, Esc leaves it alone.
  • If it’s empty: one row per field the template uses — say "…" or leave it out. Stored once in the design as fallbacks; the saved HTML and plain-text copies carry the engine’s {name|text} form, the stored blocks keep the bare {name}. An unknown name is refused by name; {, } and | are refused, not stripped.
  • Per-kind field contexts (PBJ_TPL_Kinds::contexts_for()): marketing, letters, portal notices and documents use the CRM’s person fields; a help-desk notice the ticket’s; a visit notice the customer half of the visit; a signature only the staff-profile rows. Read from the CRM’s palette at request time — nothing is hard-coded.
  • GET templates/{id} now also returns tokens (groups, disallowed, empty_reason) and palette_crm (which CRM blocks this kind may use), so the builder opens in one request. New GET templates/{id}/tokens for callers that want only the fields. design.fallbacks is always an object.
  • Four new blocks declared to the CRM’s engine: Customer card (name, business, email from the record), Hand-written HTML (an email-safe allow-list — tables, paragraphs, links, pictures survive; scripts do not), Visit & arrival window (declared only when a visit kind exists on the site; offered only to visit notices) and Ticket & link (help-desk notices only). The server says which tiles a kind gets; the screen never guesses.
  • Not built, on purpose, and said out loud: an invoice-lines block (pbj-invoicing declares no fields yet) and an “only show if…” rule block (no engine in the suite has a conditional syntax).
  • No database change.

1.2.0 — September 2026

  • The builder, at #/templates/<id> in the CRM portal: a block palette on the left, a 600px canvas in the middle, block settings and whole-template settings on the right, and the blocks-in-order list as the reorder path for anybody who would rather not drag.
  • Nine block types. Six come from the CRM’s own email engine (heading, text, photo, button, divider, space); three are declared by this plugin through the CRM’s pbj_crm_email_block_types seam — letterhead, two columns and three columns.
  • Columns hold ordinary blocks, one level deep. A column with more columns in it is refused by name — “A column cannot hold more columns.” — never silently flattened. Twenty blocks to a column.
  • Whole-template look: background, paper, word colour, button colour, accent and a body font from the six stacks every email program already has. Width is 600 and is forced to 600 on save.
  • Use my brand colours — one button, reading the accent and the paper/ink pair the CRM itself is wearing.
  • Saving is a round trip through the server: every block is cleaned by the CRM’s own engine, refused by name if it is wrong, and the HTML and plain-text copies are rewritten from what was stored. A stale cache is not possible.
  • GET templates/{id} now returns the whole design; PUT accepts it as an object or as JSON. New GET brand returns the site’s colours as a template theme.
  • The name-and-permissions form moved to #/templates/<id>/settings, reachable from the Settings link in the builder’s header. A template you may only look at still says so in one sentence.
  • The card sketch learned the three new blocks — a letterhead is a band, columns are that many boxes side by side.
  • No database change — 1.0.0 already declared design_json, html_cache and text_cache.

1.1.0 — September 2026

  • The template library, in the CRM portal under Templates: one shelf, every kind, with the kind as a filter chip rather than a separate screen.
  • Seven kinds registered — marketing email, notice, invoice & estimate layout, signature, portal notice, letter, document to sign. A kind whose module is not installed cannot be created; a kind a newer release adds still shows up, labelled “Other”, instead of vanishing.
  • Make, rename, publish, copy and bin a template. The bin holds it for 30 days, and a daily job empties the old half.
  • Group permissions: managers and owners change anything, everybody else changes what their group owns, an ungrouped template is managers-and-owners only. Refusals say who can do it instead.
  • One default per kind, protected: the default cannot be binned until another one has taken over, and setting a new default clears the old one in the same request.
  • A drawn sketch on every card — the template’s shape in its own colours, built server-side with no browser and no image files.
  • New REST namespace pbj-templates/v1: kinds, groups, templates (list, read, make, change, copy, bin, restore). Staff only, and every write re-checks the rule on the row rather than trusting the screen.
  • wp-admin gets one “Open Templates in the CRM ↗” button on both doorways. The library is deliberately not mirrored into the back end.
  • No database change — 1.0.0 already declared every column this release uses. Updating changes nothing you have.

1.0.0 — September 2026

  • First release — the foundation for the template builder.
  • Registers as a module of PBJ CRM: the Templates portal section, an embedded admin screen, and a settings panel on the CRM’s Templates & Notifications tab.
  • Declares its two tables and its options to the CRM’s one backup engine.
  • Turns on the CRM’s template_builder capability. The existing campaign-builder capability is deliberately untouched — nothing anyone has today changes.
  • Database version 1: pbj_tpl_templates and pbj_tpl_template_meta.
  • Self-hosted licence and update channel through pbj.tech.
  • Requires PBJ CRM 7.09 or newer; refuses to load below that floor with one plain notice.
  • No builder screens yet. The library is the next release.
September 6, 2026

Current release: 1.13.1 (September 2026)

1.13.1 — 6 September 2026

  • The designer opens from the campaign screen: “Designed email (HTML)” on a marketing email gives that email a design of its own — made from its name, its subject and anything already written in it — and edits it in place; an email made from a template opens on that same template. Answers CRM contract 37, and unlocks the CRM’s HTML email gate while active.
  • Answers the CRM’s two doorway questions (contract 36): every Templates settings screen leads with the designer and the matching templates, and a person’s own settings open their signature in the designer.

1.13.0 — September 2026

  • What somebody owes, in a marketing email. A marketing email, a letter and a document can now use fields that are answered for a PERSON rather than for a document: their latest unpaid invoice, what they still owe across all of them, when the latest one is due, a link to pay it and the late fees on it. They arrive under their own heading — The money · Invoicing — beside the person’s own details, and they only appear when Invoicing is here and has said so. A marketing email still has no ticket and no visit behind it, and the card says so in full: “A marketing email has no ticket behind it, so those fields would come out blank.”
  • Your signature, as a block. Drag Your signature into a marketing email, a letter or a document and it fills in with the sign-off of whoever the email goes out as — the person who wrote the campaign at a send, you while you are looking at it. With nobody’s signature to draw it puts nothing there rather than an empty panel. It is not offered on a signature template (that IS the sign-off) or on a portal notice (nobody sends one). Putting the block in is the choice, so it does not wait on the Ticket replies / Invoice emails / Visit reminders switches — those still govern the sign-off the suite adds by itself, and a campaign nobody put the block into still signs off as the company.
  • See the real values before you send. “Show the real values for” sits at the top of the Customer data card: pick a person or a business and every field says what it will actually say for them — including the money — with “nothing on their record” where a box is empty. It reads only what you are allowed to read: a customer outside your own book is refused in one sentence.
  • Every block tile says where it came from. A small CRM, INV, DISP, HD or RAW on the corner of each tile, so a rail of fifteen blocks is readable at a glance.
  • The library says more, and offers the next thing. The type chips are counted properly — “Marketing emails 4”, “Notices 3” — and a card now carries the one fact that matters for its kind (“Used by 2 campaigns”, “Fills in from each agent’s own profile · 4 people”, “prints on letterhead, one page”) with the one thing you would do next beside Open and Copy: Send a test, Print sample, Who uses it.
  • The seeded invoice layout puts your phone number on the letterhead beside your address, and the pay button now says which ways to pay it will offer — read from the gateways you have actually switched on, not a fixed list.
  • Consumes PBJ CRM services contract 35 (per_contact token contexts, pbj_crm_contact_token_values, sender_user_id) entirely behind guards: on an older CRM nothing new appears and everything else is exactly as it was. DB stays 2. GET templates/{id}/tokens takes an optional contact_id.

1.12.0 — September 2026

  • Who may change a template follows your CRM’s groups. Managers, owners and administrators change anything. Everybody else changes a template their own group owns, or one they made themselves — an ungrouped template with your name on it stays yours when a manager takes it out of a group, or takes you out of one. A published default (the invoice layout, a notice) is a manager’s whatever the group: it is not one template, it is every invoice on the site. Deleting follows changing, exactly as it did.
  • Anyone can copy one. The library card now offers Copy where Open would be on a template you cannot change, and the copy comes back as yours — filed in your own group when it can be, and otherwise as an ungrouped template with your name on it, so you can open it straight away. Copying somebody else’s template is never refused; refusing it only meant the same email got retyped, badly, somewhere else.
  • One sentence, three doors. The builder, the preview and the settings form all say the same thing on a template you may not change: “You can copy this template, but only the person who made it or a manager can change it.” A default says why it is different. can_copy rides on every template the API answers with, beside can_edit and can_delete.
  • The signing page, on a phone. A real 390px pass on /pbj-sign/<token>: the document itself is fluid — an email table’s width="600" no longer forces a sideways scroll — with the side padding pulled in under 720px and every image capped to the screen. The draw pad keeps touch-action: none and nothing else on the page does, so the page still scrolls while the pad still draws; the pad has a floor of 56px for a short landscape window; and the confirm sheet scrolls inside the viewport instead of putting Yes, sign it off the bottom of it. Type it / Draw it, Clear, Download the PDF and Print it were already 44px and stay there.
  • On a phone the builder is one column — Blocks and Preview across the top (and Send, once a draft campaign exists for the template; before that, “Send it as a marketing email” is on the Preview screen), a 44px up and down on every block instead of a drag, editing in place, and Take one for a photo from the van. That screen is the CRM’s own, drawn from this module’s block descriptors; nothing here changed to get it.
  • DB stays 2. No new settings, no new routes.

1.11.0 — September 2026

  • Signing links are never tracked. Declares its /pbj-sign/ route to the CRM’s click tracking (services contract 33, filter pbj_crm_tracking_skip_paths), so a marketing email carrying a document link keeps that link untouched: it is never rewritten through the click redirect and the signing token never lands in the CRM’s links table. Declaration only; DB stays 2.

1.10.0 — September 2026

  • Send it as a marketing email. A published marketing template’s preview gains the button; it makes a draft campaign in PBJ CRM from the template (subject, body and blocks carried over, the campaign remembering which template it came from) and takes you to it — or, when a draft already exists for the template, the builder and the preview show Go to send instead and land on that one. Nothing is made twice.
  • The campaign’s own checks. The template’s lint rows (a photo with no description, a subject a phone cuts, and so on) appear under “Before it goes” on the CRM’s Send & schedule screen for a campaign made from one of our templates — appended to whatever else the CRM has found, never in place of it.
  • CRM services contract 32 (reply_mailboxes(), create_campaign(), campaign_lint(), campaign_for_template()) is consumed behind method_exists(); on an older CRM the button is absent and the preview’s Take it away card explains why. Route: POST templates/{id}/campaign (staff, marketing kind only). DB stays 2.

1.9.0 — September 2026

  • Documents a customer signs. Two new blocks — Signature box (whose signature it is, customer or us; what it is labelled; whether it asks for a printed name and a date) and Initials box — and a document with at least one signature box, published, can be sent to a person or a business from their own record. “Both of us sign” is two separate boxes: the customer signs theirs on their link, one of us signs ours from the record, in either order, and the document counts as signed only when every box is signed.
  • Send it from the record. “Send a document to sign” sits with the other Add work actions on a person or a business. Pick the document, see it filled in before it goes, write a line or two, choose which mailbox it leaves from, and press Email it to sign. It refuses in plain words when the template is not published, when it has no signature box, and when the record has no email — on a record with no email address the action is drawn struck through with the reason on it, and pressing it does nothing.
  • What the customer gets is one email with one button, Read and sign, opening a page of its own at /pbj-sign/<token> — no login, no account, nothing to install. The document is there filled in with their details, and under it every box they have to sign: Type it (their name in a handwriting face already on their phone) or Draw it (a finger on the glass, with Clear), their printed name, the date, their initials where you asked for them, and one button — Sign and send it back — which asks once more before it sends. A page that has been signed, called off, or left thirty days answers with one sentence and your phone number, and gives nothing else away.
  • What happens when they sign. The signature is drawn into the document where the box was and that copy is frozen — the template stays editable, the sent copy never changes. With PBJ Invoicing 7.04 present it is also written as a PDF, filed against the customer, tagged Signed document, and named so the file itself carries the audit (“Service agreement — signed by Marcus Webb 2026-09-05.pdf”). Without invoicing there is no PDF: the signed copy is the module’s own page, opened from the tab and printed with Print it. You and every manager who wants the notice get an email with a link straight to the record, the customer gets their own copy with the PDF on it, and the record’s timeline gets the line.
  • The Documents tab on a person or a business lists everything sent — WAITING, WAITING ON US, SIGNED, CALLED OFF, EXPIRED — with the date, the audit line (“Signed by Marcus Webb on 5 Sep 2026 at 6:43 pm from a phone (IP 203.0.113.9)”) and what you can do about it: Countersign (our box, opens the signing page as us), Resend (a fresh link, the old one dead), Call it off (both links dead), Open, and Delete — managers and owners only, and only after typing DELETE, because there is no undo. The file it made is unpinned, not destroyed.
  • A waiting signature shows up in the Inbox as something needing attention (“Waiting for a signature — <title> · <customer>”), and When a customer signs a document is a notice you can turn on or off per person like every other.
  • The link closes itself. Thirty days by default — one number, sign_link_days, on the Templates & Notifications panel, one to three hundred and sixty-five.
  • The seam is the CRM’s one seam: services contract 31 adds send_email_from() (so the email leaves from the mailbox you chose, with the signed PDF on it) and sender_choices(), and record_actions gains requires: 'email'|'phone' so a row the record cannot use is drawn struck through with its reason instead of failing when pressed.
  • REST: GET/POST documents, GET documents/options, POST documents/{id}/resend, POST documents/{id}/cancel, DELETE documents/{id}, GET documents/{id}/open, GET documents/{id}/countersign.
  • Database 1 → 2: one new table, pbj_tpl_documents, one row per document sent. It is declared to the CRM’s backup manifest keeping its ids, because the file and the links are tied to them. Nothing else changed.

1.8.0 — September 2026

  • Invoice and estimate layouts — rules, not HTML. A layout is built like every other template, and PBJ_TPL_Paper::rules() DERIVES from the design and the Paper card what the paper must obey: size (Letter or A4), margins, whether the top repeats on page 2, whether the pages are numbered, which line-item columns show and in what order and under what heading, the colours, the font, the logo, the business name and the words after the totals. PBJ Invoicing 7.04 obeys the rules and builds, itself, the parts only it can build — the lines, the totals, the pay button, the stamp, the payments, the notes and the terms.
  • Three new blocks. Line items — its columns are the block’s own setting (what we did, qty, each, tax per line, details under each line, total; the description and the total are always shown and say so by having nothing to press) and it renders as {items_table}. Totals, with or without the payments taken. Pay button, with its own wording (“Pay this invoice” to start with). The 1.3.0 note saying an invoice-lines block could not be built is gone — {items_table} arrived with 1.7.0 and the block hands the whole repeating table over under one name. The “Only show if…” rule block is still not built and still says why.
  • A new layout opens as a whole invoice, seeded as seven blocks — letterhead, INVOICE and its number, BILL TO beside DUE, the line items, the totals, Pay this invoice, and a closing line — on Letter with 0.6 in margins, the top repeated and the pages numbered.
  • Four cards on the right, in the order the paper reads. Paper (size, margins, “Repeat the top on page 2”, “Page numbers”, and the honest sentence “The PDF prints in Helvetica; the web page and email use your font.”). Line items block (drag a column into place, rename it, show or hide it — the same control as the block’s own settings, so there is one of it, not two). This layout is used by (Every invoice, Every estimate, Recurring billing, with the count on each and “Saving changes it for all of them. Copy it first if you only want it on one.”). Check it on paper (“Make a sample PDF” opens your longest real invoice in a new tab, “Use a real invoice” lets you pick one, “Open the print view”, and one sentence saying which invoice the sample used — or “Make an invoice first, then check the layout on it.” when there is nothing to draw).
  • Preview shows a layout as an invoice, not as an email — Computer, Phone and PDF, drawn from a real document of yours, with the PDF in the frame.
  • One published default decides it. The default layout owned by Invoices, published and not binned, is what every invoice, estimate and recurring bill prints through; a draft, a binned one, or none at all, and invoicing prints exactly what it printed before — page, email and PDF alike.
  • Letters get the paper too — size and margins, and “Open the print view”.
  • The seam is the CRM’s one seam (services contract 30, filters pbj_crm_layout, pbj_crm_layout_uses, pbj_crm_layout_sample). Nothing to say is a correct answer, and it means “print what you print today”.
  • REST: GET/PUT templates/{id}/paper, GET templates/{id}/paper/sample?invoice=<id>&format=pdf|html, GET templates/{id}/paper/print; paper folded into GET templates/{id}; GET templates/{id}/records for a layout answers with your invoices rather than your contacts.
  • Paper lives in pbj_tpl_template_meta. No database change.

1.7.0 — September 2026

  • Notices, authored in the builder. Make a template → Notice → which part of the CRM sends it → which email it is (required; the list comes from what that module actually sends — nine help-desk emails, eight visit emails, five invoice messages). The new notice opens SEEDED with the words that email sends today, one paragraph per blank-line chunk, and its subject line.
  • It sends by itself. Publish it and PBJ Helpdesk 7.02, PBJ Events 7.02 or PBJ Invoicing 7.03 sends your design in place of its text — subject included when you set one — through the CRM’s one seam (services contract 29, filters pbj_crm_notice_templates / pbj_crm_notice). Unpublished, binned or absent → today’s email verbatim. There is deliberately no “default notice for a whole module” — that would send the wrong email.
  • Library cards and the builder header for a notice read “<Module> · <which email> · sends by itself”.
  • Portal notices show on client-facing pages only (your answer): the client help-desk portal and its sign-in, the invoice / estimate page, and the CRM sign-in page — drawn by the CRM’s one renderer (pbj_crm_portal_notices); never inside the staff CRM. Published = shown.
  • Invoices now has fields. PBJ Invoicing 7.03 declares invoice_email (customer name, their business, number, total, balance, due date, the pay link, your business name, the lines as a table), so an invoice notice or layout offers real fields instead of “Invoices has not told the CRM its fields yet” — that sentence stays for a site whose invoicing has not updated.
  • REST: GET kinds lists each notice owner’s events; POST templates takes event (refused by name when missing or unknown); GET/PUT templates/{id} carry it.
  • Event lives in pbj_tpl_template_meta. No database change.

1.6.0 — September 2026

  • Preview — a Preview pill in the builder opens #/templates/<id>/preview: Computer, Phone, Side by side and Plain text views, “Showing it as” a real customer from the CRM (only records you may see), Copy the HTML.
  • Dark mode, simulated honestly — the same email rendered with a dark background, dark paper and light words; a button darker than the page would vanish, so it flips to white and the screen says so in one sentence. One thing to check, not a setting.
  • Plain text version — the automatic twin, or Write my own; your own words are kept and never regenerated; “Use the automatic one” puts it back. Sent alongside — some people only get this.
  • Subject line — a field above the preview for the kinds that have one (marketing, notices, portal notices); 250 characters at most, refused by name beyond that.
  • Things worth fixing — photos with no description, the subject line’s length against a phone’s ~40 characters, every merge field checked against the customer you are showing it as (a field with nothing behind it and no fallback is named; a field no module on this site fills in is called out), and the unsubscribe note for marketing.
  • Take it away — Download the HTML (merge fields still in it, as a whole document), Copy it to the clipboard, Send a test to me (to your own account email, filled in for the customer you picked).
  • REST: GET templates/{id}/preview?as=, GET templates/{id}/records?q=, PUT templates/{id}/plain-text, PUT templates/{id}/subject, GET templates/{id}/download, POST templates/{id}/test-send. Needs pbj-crm 7.09 (services contract 28: contact_tokens, send_email).
  • Subject and your own plain text live in pbj_tpl_template_meta. No database change.

1.5.0 — September 2026

  • Signatures. A signature template’s words carry the staff-profile fields ({agent_name}, {agent_title}, {agent_phone}, {agent_email}, {agent_photo} …) and fill themselves in per person — change a phone number once on the profile and every signature follows.
  • Showing it as — a switcher over the canvas previews the signature as any staff member, with their photo (or a dashed “photo” circle when the profile has none). The stored template keeps the field names; only the preview changes.
  • Who signs with this — assign people (any CRM staff member; an id off the staff list is refused by name), remove them, “Add somebody”. A copy of a signature signs for nobody until somebody says so.
  • Where it gets used — Ticket replies, Invoice emails, Visit reminders, each on or off; Marketing emails always read “No — those sign off as the company” and cannot be switched (an unknown place is refused by name).
  • Copy it out — “Copy <name>’s” puts that person’s finished HTML on the clipboard for Gmail or Outlook; “Download all <N>” is one HTML file with everybody’s, headed by their names.
  • The seam. PBJ_CRM_Services::signature_for( user, context ) (pbj-crm 7.09, services contract 27, filter pbj_crm_signature) answers with the signature assigned to that person and switched on for that place — an explicit assignment beats the kind’s default, drafts and binned templates are ignored. PBJ Helpdesk 7.01, PBJ Invoicing 7.02 and PBJ Events 7.01 ask it, guarded, and fall back to what they do today.
  • A picture block may now hold {agent_photo} as its address (pbj-crm 7.09 lets one whole token stand as a URL); a signature rendered for somebody with no photo simply leaves that block out.
  • REST: GET/PUT templates/{id}/signature, GET templates/{id}/signature/render?user=, GET templates/{id}/signature/download (staff; the download link carries the REST nonce because a browser navigation has no header to put it in). GET templates/{id} folds signature in so the builder opens in one request.
  • Assignments live in pbj_tpl_template_meta (signers, uses) — the table 1.0.0 already made. No database change.

1.4.0 — September 2026

  • Photos panel on a Photo block: three tabs — CRM files, WordPress media, Upload — a search box, one chip per customer and per tag, a thumbnail grid with a DROP TO UPLOAD tile, the picked file’s name, size and pixel size, and the honest note “Will be shrunk to 1200px wide for email” when it will be.
  • “What it shows (for people who can’t see images)” is required before “Put it in” — an email never goes out with a picture nobody can describe.
  • Pin to this customer: pick a customer chip and a photo that is not yet in their files, and one press pins it there. A photo already in their files says so instead. Under “All” the button is not drawn.
  • Uploads from the builder go into the CRM’s ONE files store, pinned to the template (this plugin registers the template file target with PBJ_CRM_Files::register_target()), through the CRM’s own chunked uploader — no second store, no second uploader.
  • The email copy of a CRM photo is a public, unguessable address served by the CRM (files/{id}/email/{token}), re-encoded at up to 1200 pixels wide with the camera’s metadata stripped; the original never changes. WordPress media is already public and is used as it is (the largest size up to 1200px).
  • A Photo block now remembers which CRM file it came from (file_id), so a later release can lint it. Needs pbj-crm 7.09 (services contract 26).
  • No database change.

1.3.0 — September 2026

  • Customer data card in the builder, under the block settings: every field this kind of template is allowed to use, grouped by where it comes from (the person · CRM; the visit · Events; the ticket · Help desk), in plain words with the field name on hover, and a search box. Custom fields are drawn grey — “your own boxes on the record.”
  • Not in this kind of template: the fields every OTHER installed module declares, struck through, with the reason in one sentence — “A marketing email has no ticket behind it.” A kind whose module has declared nothing says so honestly (“Invoices has not told the CRM its fields yet.”) and never invents a field.
  • Click a field and {name} goes in at the caret of the box you were typing in; drag one in; or type {{ and a “Pull in customer data” picker opens — choosing replaces the {{, Esc leaves it alone.
  • If it’s empty: one row per field the template uses — say "…" or leave it out. Stored once in the design as fallbacks; the saved HTML and plain-text copies carry the engine’s {name|text} form, the stored blocks keep the bare {name}. An unknown name is refused by name; {, } and | are refused, not stripped.
  • Per-kind field contexts (PBJ_TPL_Kinds::contexts_for()): marketing, letters, portal notices and documents use the CRM’s person fields; a help-desk notice the ticket’s; a visit notice the customer half of the visit; a signature only the staff-profile rows. Read from the CRM’s palette at request time — nothing is hard-coded.
  • GET templates/{id} now also returns tokens (groups, disallowed, empty_reason) and palette_crm (which CRM blocks this kind may use), so the builder opens in one request. New GET templates/{id}/tokens for callers that want only the fields. design.fallbacks is always an object.
  • Four new blocks declared to the CRM’s engine: Customer card (name, business, email from the record), Hand-written HTML (an email-safe allow-list — tables, paragraphs, links, pictures survive; scripts do not), Visit & arrival window (declared only when a visit kind exists on the site; offered only to visit notices) and Ticket & link (help-desk notices only). The server says which tiles a kind gets; the screen never guesses.
  • Not built, on purpose, and said out loud: an invoice-lines block (pbj-invoicing declares no fields yet) and an “only show if…” rule block (no engine in the suite has a conditional syntax).
  • No database change.

1.2.0 — September 2026

  • The builder, at #/templates/<id> in the CRM portal: a block palette on the left, a 600px canvas in the middle, block settings and whole-template settings on the right, and the blocks-in-order list as the reorder path for anybody who would rather not drag.
  • Nine block types. Six come from the CRM’s own email engine (heading, text, photo, button, divider, space); three are declared by this plugin through the CRM’s pbj_crm_email_block_types seam — letterhead, two columns and three columns.
  • Columns hold ordinary blocks, one level deep. A column with more columns in it is refused by name — “A column cannot hold more columns.” — never silently flattened. Twenty blocks to a column.
  • Whole-template look: background, paper, word colour, button colour, accent and a body font from the six stacks every email program already has. Width is 600 and is forced to 600 on save.
  • Use my brand colours — one button, reading the accent and the paper/ink pair the CRM itself is wearing.
  • Saving is a round trip through the server: every block is cleaned by the CRM’s own engine, refused by name if it is wrong, and the HTML and plain-text copies are rewritten from what was stored. A stale cache is not possible.
  • GET templates/{id} now returns the whole design; PUT accepts it as an object or as JSON. New GET brand returns the site’s colours as a template theme.
  • The name-and-permissions form moved to #/templates/<id>/settings, reachable from the Settings link in the builder’s header. A template you may only look at still says so in one sentence.
  • The card sketch learned the three new blocks — a letterhead is a band, columns are that many boxes side by side.
  • No database change — 1.0.0 already declared design_json, html_cache and text_cache.

1.1.0 — September 2026

  • The template library, in the CRM portal under Templates: one shelf, every kind, with the kind as a filter chip rather than a separate screen.
  • Seven kinds registered — marketing email, notice, invoice & estimate layout, signature, portal notice, letter, document to sign. A kind whose module is not installed cannot be created; a kind a newer release adds still shows up, labelled “Other”, instead of vanishing.
  • Make, rename, publish, copy and bin a template. The bin holds it for 30 days, and a daily job empties the old half.
  • Group permissions: managers and owners change anything, everybody else changes what their group owns, an ungrouped template is managers-and-owners only. Refusals say who can do it instead.
  • One default per kind, protected: the default cannot be binned until another one has taken over, and setting a new default clears the old one in the same request.
  • A drawn sketch on every card — the template’s shape in its own colours, built server-side with no browser and no image files.
  • New REST namespace pbj-templates/v1: kinds, groups, templates (list, read, make, change, copy, bin, restore). Staff only, and every write re-checks the rule on the row rather than trusting the screen.
  • wp-admin gets one “Open Templates in the CRM ↗” button on both doorways. The library is deliberately not mirrored into the back end.
  • No database change — 1.0.0 already declared every column this release uses. Updating changes nothing you have.

1.0.0 — September 2026

  • First release — the foundation for the template builder.
  • Registers as a module of PBJ CRM: the Templates portal section, an embedded admin screen, and a settings panel on the CRM’s Templates & Notifications tab.
  • Declares its two tables and its options to the CRM’s one backup engine.
  • Turns on the CRM’s template_builder capability. The existing campaign-builder capability is deliberately untouched — nothing anyone has today changes.
  • Database version 1: pbj_tpl_templates and pbj_tpl_template_meta.
  • Self-hosted licence and update channel through pbj.tech.
  • Requires PBJ CRM 7.09 or newer; refuses to load below that floor with one plain notice.
  • No builder screens yet. The library is the next release.

Latest Articles