Design Tool · Email Platform · Live Product

Two editors,one workspace.

A design canvas with a Photoshop-grade image tier, an email builder that lints a message before it can be sent, a bring-your-own-key AI layer, and a 293-template content library — all under one account and one asset library. Acetrum built it end to end.

445

TypeScript files

113,539

Lines of application code

293

Templates shipped

Memo Editor — template gallery with posters, banners and social designs

Canvas presets, in real dimensions

Logo · 500 × 500Banner · 1600 × 400Social post · 1080 × 1080Poster · A2, 420 × 594 mmStory · 1080 × 1920Presentation · 1920 × 1080Business card · 1050 × 600Flyer · A5, 148 × 210 mmCustom · any

Print sizes are given in millimetres and converted at 96 dpi, so A2 and A5 land at their true 1:√2 and are visibly the same shape as each other. The preview cards take their proportion from the real numbers — draw all three as an abstract mark in an identical box and the row tells you nothing the labels did not.

The product

One account,one asset library.

Most tools make you choose: a canvas editor that cannot send, or an email builder that cannot design. Memo Editor is both, sharing one set of uploads, brand logos and saved blocks — so the artwork made for a poster is already in the library when the campaign needs it.

01Design editorA fabric.js 7 canvas driven by MobX stores — posters, logos, banners, cards and social artwork. Twenty-eight tools, a full File / Edit / Image / Layer / View / Filter menu, and export to PNG, JPG, SVG, JSON or HTML.
02Email builderAn MJML document model, so what leaves the builder is responsive email that survives Outlook — not a web page pushed into an inbox and hoped for.
03DashboardProjects, templates, uploads, email and settings. One account and one asset library, so an upload made for a poster is already there when the email needs it.
04Public surfaceLanding page, share links and published designs. A share link is a property of a design rather than a copy of it — cloning on every share had filled dashboards with duplicates carrying no canvas.

Image editing

The part most canvas editorsstop short of.

Fourteen adjustment sliders, twenty filters and four blur falloffs — gaussian, linear, radial and mirrored, rather than one slider. Several adjustments are composed rather than mapped one-to-one onto a canvas filter: exposure is a gentler brightness curve, clarity and highlights/shadows combine brightness and contrast, and sharpness runs a convolution matrix.

Adjustments — 14

BrightnessExposureContrastClarityHighlightsShadowsWhitesBlacksTemperatureSaturationVibranceHueSharpnessGamma

Filters — 20

Black & whiteBlend colourBlurBrightnessContrastEmbossGammaGreyscaleHue rotateInvertKodachromePixelatePolaroidRemove colourSaturationSepiaSharpenTechnicolorVibranceVintage

The Photoshop tier

Twenty-seven tools, written from scratch.

Selection, retouching and paths are the tools a designer reaches for when the supplied photograph is nearly right. Building them is the difference between a template filler and something you can finish a job in.

SelectionMagic wand, quick selection, object selection, slice, slice-select
RetouchingHealing brush, spot healing, patch tool, content-aware move, red eye, colour replacement
ErasingEraser, background eraser, magic eraser
PathsPen, free pen, curvature pen, anchor add / delete / convert
PaintingBrush, pencil
SamplingEyedropper, colour sampler
TransformCrop, perspective crop, ruler

Import & generate

A file comes back as layers, not a picture.

PSD, AI and SVG files come back as named, editable fabric layers — not a flattened picture — and conversion warnings are reported rather than swallowed

Image trace turns a dropped bitmap into layers: the server produces SVG, and the SVG path that PSD and AI already share turns that into named objects. Nothing new converts anything; it joins two things that were already there

tesseract.js reads the text out of an image on the client, so an OCR pass costs no round trip and no key

Nine barcode formats — Code 128, EAN-13, EAN-8, UPC-A, Code 39, ITF-14, MSI, Pharmacode, Codabar — each with its own validation, because EAN-8 wants seven or eight digits and ITF-14 an even count, and one generic check made switching format throw

The stack

Chosen for reasons,not for fashion.

FrameworkNext.js 16 (App Router) + React 19Server Components by default, file-based routing, and API routes next to the pages that call them — 48 of them
LanguageTypeScript, end to end445 files under one type system; the 28-member ActiveTool union is the editor's whole vocabulary in a single type
Canvasfabric.js 7 + MobXEight stores split by what changes together — editor, workspace, layers, history, rulers, UI — under one root store, rather than one store per feature
EmailMJML document modelThe compiler that will render the message is the same thing that validates it, so a broken email is caught by its own renderer
DatabasePostgreSQL + PrismaThirteen models — User, Design, DesignRevision, Template, Asset, BrandLogo, SavedBlock, MailSender, AiProvider and four more
AuthJWT with a tokenVersion claimA valid signature is necessary but not sufficient — bumping the column ends every outstanding session, which nothing could do before
EncryptionAES-256-GCM for provider keysAuthenticated, so a tampered ciphertext fails to decrypt rather than quietly yielding garbage that gets sent to a provider
StorageThe plain S3 APIThe same code runs on Cloudflare R2, AWS S3 or a local MinIO — and with the variables absent the app inlines images, so storage stays optional
Drawingperfect-freehandPressure-sensitive strokes over a brush preset system, rather than a constant-width line
OCRtesseract.jsReads text out of an image in the browser — no upload, no key, no per-call cost
Type97 self-hosted Google font familiesA deploy that depends on the machine having Arial renders differently on a Linux server than on the designer's Mac

The AI layer

Six graphs,and your own key.

The user connects a provider in Settings. The model is for judgement; arithmetic is for verification — and the two are kept apart on purpose.

01Design graphA whole design from a description — laid out, not a pile of centred boxes
02Edit graph“Do this to the thing I have selected”, returned as a description of the change and never as a mutated canvas
03Resize graphThe same design re-cut to another size, with every layer then checked against the new page for off-page, collapsed and overlapping results
04Critique graphThe half of a design review that arithmetic cannot do — focal point, hierarchy, whether the copy is finished
05Email graphA whole email in three steps: plan the sections, source the photographs, then write and compile
06Image generationText to image across Stability, Google, Replicate and DeepAI, behind one interface

What the model is not trusted with

Measure first. Ask second.

The key is the user's own. It is encrypted at rest, decrypted server-side at the moment of a call, and never crosses back to the browser

Critique measures contrast ratios, font sizes and page bounds in the browser first — free, and no key needed. The model is only handed the judgement

It is also asked to throw out measured findings that are wrong in context: a deliberately faint watermark is not a contrast bug, and a review that cries wolf gets closed and never opened again

Email planning happens before writing because that is what makes the result look designed, and photographs are sourced before the copy so the model cannot invent addresses that 404

An edit that fails leaves the document untouched, because the client owns the fabric objects and the model never held them

The email builder

Email is the one mediumwith no recall.

A missing alt attribute is a shrug on a web page and a blank rectangle for the third of readers whose client blocks images. A document over 102 KB is silently truncated by Gmail, mid-sentence, behind a link most people never click. None of it is visible in a preview, because a preview shows the good case.

01One document, three dialectsEvery ESP invented its own spelling for “put the reader's name here”, and none reads another's. The document is always Liquid; the dialect is decided at the last possible moment, on export, against a copy — and where a platform has no equivalent for an operator, the conversion says so rather than emitting something that looks right and evaluates to nothing.
02PSD to emailA PSD is coordinates; an email is a stack. The import does the mechanical half — pull the layers, scale them to the width an email actually is, flatten overlapping ornament into one picture — and works out rows, columns and backgrounds from the coordinates alone. Pictures are uploaded rather than inlined, because a data URL in an email is stripped by most clients.
03A block library and ten inspectorsSaved blocks, 21 layout shapes, 32 sender identities and 153 email templates across 17 groups — so a message starts from something shaped like the message, not from an empty canvas.

Before send — four checks that refuse to overstate themselves

Audit603A lint, not a renderer. Every check is something that has gone wrong in a real send, phrased as what to do about it and pointed at the block that caused it.
Accessibility259A score out of 100 that is deliberately blunt: a number that moves when you fix something, not a certification.
Spam195No word sends an email to spam on its own. It counts signals, and says nothing until several land together.
Compatibility219The free half of the check, over the Can I email dataset. It names the paid half — a screenshot in a real Outlook on a real Windows machine — and who sells it.

Platform

Sessions you can end,data you can take.

Auth & abuse

A signature is not enough.

Sessions are JWTs carrying a tokenVersion claim, cached for ten seconds — checking on every request would put a database round trip in front of every API call, and checking never meant a leaked “remember me” token was good for thirty days

Login is timing-equalised: a throwaway hash is compared when the account does not exist, so the response takes the same time either way

Login carries two rate-limit budgets, because they stop different attacks — per-address slows someone guessing one account's password, per-IP slows someone spraying one password across many

Only wrong answers are charged. A correct sign-in is refunded, or a busy office behind one IP would lock itself out

Storage & ownership

Optional infrastructure, portable work.

Rate limiting is deliberately not Redis: this runs as a single Node process, so a map is the whole of what a shared store would buy — and a limiter that exists today beats a distributed one that is still a ticket

Storage is written against the plain S3 API, so R2, S3 and a local MinIO are the same code path, and the absence of the variables is a supported configuration rather than a crash

A share link is a property of a design, not a copy of it

The account export resolves canvas JSON including designs offloaded to object storage — an export that lists your work without containing it is not an export

The template rebuild

Fourteen commits.239 files.

The brief was that the email templates looked the same as each other. The causes turned out to be structural and measurable: one theme per group, two or three photographs per group, and a single hardcoded font across all 153.

Delivery, not group

What does this message owe the reader?

Marketing10.7Hero, navigation, trust row, CTA band, contact block — it is competing against a full inbox
Transactional4.0A small picture beside the copy; a receipt is competing for nothing
Security4.0Plain. No photography, no navigation, no closing sections — and a test now fails if a security template carries a photograph

Eight hue families

64 files instead of 256.

A PNG does not recolour. One flat illustration puts a blue drawing on an orange email; one per sender is 8 × 32 = 256 files. So each drawing renders once per hue family, and every sender is matched to the nearest by hue at build time.

Red

6

Orange

5

Gold

2

Green

2

Teal

6

Blue

5

Purple

3

Pink

3

Family, and how many of the 32 sender identities fall into it.

Ten shipped defects, and what found each

Almost none of these were visible in the source.

01Four templates sent SVG images — a broken picture in Gmail, Outlook desktop and Yahoo. Nothing flagged it: the file existed and the path was right.Reasoning
02A digest row sized itself from the picture pool rather than the copy: a three-card row became twelve, the last nine captioned “Product 4” to “Product 12”.Screenshot
03Twelve marketing templates had no imagery at all.Test
04A four-cell grid repeated its first picture in the fourth cell.Test
05A careless regex pointed the hero pool at the 560px card crops — soft heroes, not broken ones, which is why nobody had noticed.Test
06Six groups pinned the same photograph to two templates.Test
07Photographs with no alt text; a glyph at 3.8:1 on its own tint; eyebrow type at 10px; font stacks naming families the audit could not account for.Audit
0810.8 MB of poster photography stored as PNG with no transparency — 0.59 MB as JPEG.Metadata
09A poster panel covering exactly the subject of its photograph.Contact sheet
10Three unreferenced PNGs, 7 MB, whose JPEG siblings were what the templates actually loaded.Grep

Judgement calls

Three times we shippedless on purpose.

Two of these were features already built or already recommended. Removing a finished feature is harder than not writing it — which is exactly why it has to be someone’s job.

013,400 brand logos, curated down to twelveThe project once bundled the full Simple Icons catalogue as a general “insert a logo” feature. CC0 covers the SVG file; it does not cover the mark it depicts, and the feature let a user drop a pixel-accurate bank or beverage logo onto an exportable design. It was curated down to the social and messaging platforms, where using the icon to indicate a link is the recognised use — and the reasoning was written into a NOTICE file rather than left in a commit message.
02Fifty imported templates, declined after reading themA request to import 50 templates from a public repository was refused: fourteen of the fifty are verbatim copies of real companies' production email — an Uber gift-card receipt still carrying the archive site's footer, an Airbnb reservation confirmation, five with live tracking URLs. There is no licence file; the readme says MIT and the package manifest says ISC. Changing images and links does not fix what is being copied.
03A stock-illustration route, recommended then withdrawnAcross 48 candidates, “teamwork character” returned a cartoon rabbit, “celebration” a duck on a rocket, and the same magnifying glass came back for analytics, education and travel. Four were usable and no two shared a style. Eight illustrations were drawn instead — the recommendation was ours, and so was withdrawing it.

How we worked

Four habits,113,000 lines.

01

Comments name the bug that caused the rule

Not “this sets the token version” but the incident behind it. A rule with its incident attached survives a refactor; a rule without one gets optimised away by the next person who cannot see the point of it.

02

Render it and measure it before you believe it

Every built-in design is rendered and measured for text overflow, collisions, print safe areas and optical centring. Almost every defect above was invisible in the source and obvious in a measurement or a picture.

03

Look at the pictures anyway

A verifier cannot tell you a composition is ugly or that a panel is sitting on the subject of its own photograph. Contact sheets get looked at, by a person.

04

Say what the tool cannot do

The accessibility score refuses to be a certification, the spam checker opens by refusing its own premise, and the compatibility service names the half it does not do. A tool that overstates itself gets trusted once and ignored afterwards.

Numbers

Read from the source,not from memory.

TypeScript files445
Lines of source113,539
API routes48
Editor components58
Editor tools28
Photoshop-tier tools27
Image adjustments14 sliders · 20 filters · 4 blurs
Barcode formats9
Import formatsPSD · AI · SVG · raster
Self-hosted fonts97 families
Canvas presets9
Built-in design templates140
Email templates153 across 17 groups
Email layout shapes21
Sender identities32
Email asset library40 photos · 40 crops · 28 icons · 64 drawings
AI graphs6
Database models13
Test files / tests30 / 393
Generator scripts17

Next

Want a productbuilt like this one?

Tell us what you are building. We will tell you exactly how we would ship it.

Start a project
All case studies

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.