
PLUGIN EXTENSIONS & CUSTOM FEATURES
An email-to-PDF archive built inside FluentCRM
Every custom email FluentCRM sends becomes a PDF the contact receives and the team can find again — built inside FluentCRM’s own admin, with no database tables, and an 87 MB font set that never touches the plugin zip.
12
days to 1.0.0
50
paper formats
87 MB
of fonts, fetched on demand
0
custom tables
| Client | A business running FluentCRM Pro on WordPress (name and sector withheld) |
|---|---|
| Challenge | FluentCRM has no concept of a document: once a personalised email goes out there is no file — nothing attached, nothing on the contact record, nothing to hand to an auditor a month later. |
| Delivery | A FluentCRM Pro add-on: an automation step with 50 paper formats, a profile-send hook, a PDF tab on every contact with search and delete, a settings screen with per-source permissions, a resumable 70-font installer with a PHP preflight, packaging, training and handover. |
| Stack | WordPress, PHP, FluentCRM, Vue, JavaScript |
| Timeline | 30 May to 10 June 2024 — beta in 9 days, feature-complete 1.0.0 in 12 |
The challenge
FluentCRM sends personalised email well — an automation can send a custom email to each contact as they move through a funnel, and a team member can send one by hand from the contact’s profile. What FluentCRM does not have is any notion of a document. Once the email goes out there is no file: nothing attached to the message, nothing on the contact record, nothing to hand to the contact, a colleague or an auditor a month later. The client needed every such email as a PDF — attached for the contact, filed against the record, searchable and deletable from where the team already works, with paper size chosen per automation step, and an administrator able to switch the whole thing off.
Three constraints made it hard. The rendered email only exists inside FluentCRM’s pipeline — merge tags, block templates, footer text and global styles resolve at send time, so re-rendering the template anywhere else would eventually drift from what the contact actually received; the add-on had to pick the email up from inside FluentCRM’s own job flow, after the send. HTML-to-PDF in PHP means mPDF, and mPDF is heavy: 82 font files totalling 86.8 MB against a plugin zip of 2.66 MB, plus five PHP extensions shared hosts do not always enable — and the whole thing had to install through the normal WordPress uploader with no shell access and no Composer.
And the UI had to be a tab, not a page. FluentCRM’s admin is a single-page Vue application with its own router, permission model and REST layer; a separate WordPress admin page would have meant a second place to look for the same data. The PDF list belonged on the contact profile, and the settings under FluentCRM’s own navigation, behind FluentCRM’s own permission.
What ScalarWP built
Both sources end in one pipeline. An automation step — droppable into any funnel after a send-custom-email step, with 50 paper formats from A10 to press sheets and defaults of A4 portrait so an untouched step still produces a page — or FluentCRM’s contact-jobs hook after a profile send. Either way the add-on reads the email record FluentCRM has just written, renders it with a pinned mPDF 8.1.2, writes the PDF into the contact’s folder, and emails the contact the same message with the PDF attached. Visual parity came the honest way: two CSS-inlining libraries were added during the build and both were removed before 1.0.0, because FluentCRM’s rendered email already carries its block styles inline — what the PDF lacked was three global values applied outside the body, and a three-rule stylesheet built from FluentCRM’s own settings closed the gap.
The archive is files with sidecars, no tables. Each PDF lands next to a JSON sidecar carrying the creation time, the source and the file name — and the PDF is written only after its sidecar, so the contact tab never lists a file it cannot describe. Naming earned its own lesson: early names used the email subject plus a minute-precision timestamp, and two PDFs in the same minute silently overwrote each other. The fix — contact name plus second-precision timestamp — then broke the delete endpoint, whose path packed the contact hash and file name around an underscore the new names now contained. The separator became a hyphen, which neither a hash nor a generated name can contain. A file name is an API; changing its shape needs the same care as changing an endpoint.
The team works where it already works. A PDF tab on every contact profile, rendered with FluentCRM’s own Vue and Element UI: file name, human-readable time, source, search, per-source filter, pagination, download, and a confirm-before-delete that removes the PDF and its sidecar together. A settings entry in FluentCRM’s navigation, visible only with FluentCRM’s settings permission, holds the master switch and one checkbox per source — each with an image tooltip showing where that source lives — and when a source is off its handler is never registered, so nothing runs by accident. Per-contact folders are keyed by the contact’s hash, not its numeric ID, so the archive cannot be enumerated by counting.
The 87 MB problem got its own machinery. The release ships mPDF without its font directory; a fonts screen runs a five-check PHP preflight first — each failure worded for a hosting support ticket, installation blocked until they pass, because without mbstring or GD, mPDF fails mid-render inside an automation where nobody is watching. Install then fetches 70 fonts three per request until the server reports the set complete, computing the missing set from disk on every call so an interrupted install resumes and a re-install fetches only what is absent, each file streaming to disk so memory stays flat. The result installs anywhere a 3 MB plugin installs. Around it: a FluentCRM Pro dependency notice with one-click activate, eight REST endpoints under one namespace, a release script with an allow-list producing the 2.66 MB zip, and training plus handover to the team that owns it today.
The outcome
- Beta 1.0.0 in 9 days, feature-complete in 12 — 16 commits across 6 merged pull requests.
- About 1,200 lines of PHP across 14 files and 1,000 lines of Vue and JavaScript across 6 components; 8 REST endpoints; 2 WordPress options; 0 custom tables.
- 50 paper formats in 2 orientations; 70 fonts (86.7 MB) installed on demand; a plugin zip of 2.66 MB.
- PDF and inbox agree by construction — the add-on renders the email record FluentCRM wrote, through FluentCRM’s own renderer.
- Owned and operated by the client’s team since handover.
What ScalarWP delivered
- The automation funnel step with paper format and orientation, and the profile-send hook.
- The mPDF pipeline with script-fallback fonts and the global-styles stylesheet that matches the email.
- The per-contact PDF archive — files with JSON sidecars, hash-keyed folders, no tables.
- The contact tab and settings screen inside FluentCRM’s own admin, behind its own permission.
- The resumable font installer with PHP preflight, release packaging, training and handover.
Why it mattered
Document generation usually arrives as a second system — another admin page, another table, another queue to reconcile. Building it as a FluentCRM organ meant the team got a PDF archive without getting a new product to learn: one permission model, one place to look, an archive that restores with the uploads folder in any backup. And the engineering that matters most is the part the client never sees — fonts that arrive in resumable batches instead of bloating the zip, preflights that fail where an administrator is looking instead of inside a funnel, and a sidecar written before its PDF so the list can always be trusted.
Your CRM sends it. Can it file it?
Documents, archives and attachments built inside the tools your team already runs — so the record exists the moment the send does.

