Download the PHP package rlnks/php-mail-tree without Composer

On this page you can find all versions of the php package rlnks/php-mail-tree. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package php-mail-tree

rlnks/php-mail-tree

A PHP library for building HTML emails using an intuitive object-tree nesting approach. Structure your email exactly the way you think it — each node is a named property on its parent, and the hierarchy of your PHP code mirrors the hierarchy of the rendered HTML.

Installation

Requires PHP 8.2+.


The concept

Most email builders make you concatenate strings or call sequential methods. This library lets you build an object tree instead. Assign child nodes as named properties — the name you choose becomes documentation.

Each node knows its place in the tree. Call $email->build() once and the entire tree renders itself recursively, cascading styles top-down.


Structure-first pattern

Because nodes are just PHP object properties, you can declare the email skeleton first, then fill in the content separately. This separates layout decisions from copy decisions — exactly like drag-and-drop in a visual builder, but in code.

Why this matters:


Node management

Every node supports a full set of tree-management methods. All methods return $this (or the affected node) so they can be chained.

Visibility — hide() / show()

A hidden node is completely skipped during rendering. It stays in the tree, so it can be revealed again or toggled conditionally.

Reordering — moveUp() / moveDown() / moveToIndex()

Nodes track their parent automatically when assigned via ->. Move methods operate on the parent's child list immediately.

moveUp/moveDown accept an optional $steps argument (default 1). Positions clamp at boundaries — no wrap-around.

Relative positioning — insertBefore() / insertAfter()

Position a node relative to a named sibling instead of an absolute index.

The sibling is identified by the property name it was assigned under.

Lifecycle — detach() / replaceWith() / duplicate()

Inspection — getChildren()

Returns a snapshot of all direct children, keyed by their property name.

Note: Image supports hide()/show() but not the tree-manipulation methods (move*, insertBefore/After, detach, replaceWith, duplicate, getChildren), since it cannot have children.


Quick start with StyleSheet + Presets


Complete example

A production-ready multi-file structure that demonstrates every major pattern at once: separated styles and translations, skeleton-first construction, images and links defined as named variables, a single build() call, and multiple output variants from that one render.


styles.php — named section styles


translations.php — translatable strings


email.php — the template

What this demonstrates:


StyleSheet

StyleSheet is the central style brain of the email. It does two independent things:

  1. Theme → style arrays — you store brand tokens once; generator methods translate them into the CSS property arrays that each node class expects.
  2. Named-style registry — a define / get system that replaces repeating raw style arrays across your template.

Vocabulary 1 — Theme variables (design tokens)

The StyleSheet constructor takes design tokens, not CSS properties. Tokens are PHP camelCase names that represent a brand concept rather than a specific CSS rule:

Token Default What it represents
primaryColor #333333 Brand accent (headings, links, buttons)
textColor #444444 Body copy color
bgColor #f0f0f0 Outer page/body background
containerBg #ffffff Inner email content background
borderColor #dddddd Dividers, borders
fontFamily Arial, … Font stack
baseFontSize 14px Default paragraph size
lineHeight 150% Default line height
containerWidth 600 Email width in px (integer)
marginWidth 30 Left/right gutter width in px
spacerHeight 20px Default vertical spacer height
spacerBg '' Spacer background — empty falls back to containerBg
buttonRadius 4px Button corner radius
buttonHeight 50 Button height in px (integer)
buttonWidth 200 Button width in px (integer)
buttonFontSize 16px Button label size
darkBgColor #1a1a1a Dark mode outer background
darkContainerBg #2d2d2d Dark mode container background
darkTextColor #dddddd Dark mode body copy color
darkPrimaryColor '' Dark mode accent — empty falls back to primaryColor
darkBorderColor #444444 Dark mode divider/border color

Why camelCase tokens instead of CSS properties directly?

A single token like primaryColor maps to CSS in many different places — the color of an h1, the background-color of a button, the border-color of a divider. If you stored CSS properties directly you'd have to repeat and keep them in sync. The token is defined once; each generator method puts it in the right property for the right context.


Vocabulary 2 — Style keys (semantic node keys)

Style arrays throughout the library are always nested under a semantic key that identifies which node type or HTML element receives the styles. The value is a flat array of standard CSS properties (background-color, font-size, etc.).

Why container and column instead of table and td?

Email HTML wraps a logical "container" in multiple elements (<table><tbody><tr>) and Outlook sometimes adds MSO conditional wrappers around it. Using the key container means you're styling the concept rather than a specific tag. It also avoids confusion with the CSS table and td display values. The library owns the mapping; you use the semantic vocabulary.


Generator methods — theme → style arrays

These methods read your tokens and produce a ready-to-use style array with the correct semantic keys and standard CSS properties. Pass the return value directly to the matching constructor.

emailStyle() — used internally by new EmailDocument($sheet)

Pass $sheet directly to EmailDocument — it calls emailStyle() internally and also registers itself as the global default so all preset make() calls in the same script auto-resolve it without needing sheet: $sheet.

Produces (for the theme above):

This style array becomes the root of the cascade — every child node in the tree inherits from it.

Generator method Pass to What it produces
emailStyle() called internally by new EmailDocument($sheet) body bg, global font, heading + link + highlight colors
containerStyle() new Container(…) container key with width, bg, table resets
outerContainerStyle() new Container(…, mso: true) same + no bg (transparent outer wrapper)
marginColumnStyle() new Column(…) column key with gutter width
bodyColumnStyle() new Column(…) column key with inner content width
twoColStyle() new Column(…) column key with half inner width
threeColStyle() new Column(…) column key with one-third inner width
spacerStyle($height) new Column(…) font-size/line-height/height triple for Outlook
buttonContainerStyle() new Container(…) bg-color, border-radius, center alignment

All generators accept an optional $overrides array so you can tweak one property without redefining the whole block:


Named-style registry

Store reusable style combinations under a name and apply them via get(). Keys inside the stored style follow the same semantic vocabulary (container, column, h1, div, etc.) and values are standard CSS properties.

Method Description
define(string $name, array $style) Store a named style; returns $this for chaining
get(string $name, array $overrides = []) Retrieve — optionally merged with one-off overrides
extend(string $name, array $extra) Merge additional keys into an existing definition
has(string $name) Check whether a name is registered
addWebFont(string $url, string $fontName = ''): static Register a web font URL; when $fontName is provided it is prepended to fontFamily in the theme automatically. EmailDocument injects the matching <link> tags in <head>.
webFontLinks(): string Return the <link> HTML tags (including Google Fonts preconnect) for all registered web fonts. Called automatically by EmailDocument.
addResponsiveRule(string $selector, array $properties): static Add a custom rule that will be emitted inside the @media block of responsiveCss().
responsiveCss(int $breakpoint = 620, bool $darkMode = false): string Return the full <style> string for Body::setCSS().
darkModeCss(): string Return only the dark-mode CSS block (@media (prefers-color-scheme: dark) + Outlook.com overrides). Called automatically by responsiveCss(darkMode: true).
useAsDefault(): static Register this instance as the process-wide default — all presets resolve it automatically when no $sheet is passed. Called automatically by new EmailDocument($sheet). Returns $this for chaining.
static getDefault(): ?static Return the current default, or null if none set
static clearDefault(): void Clear the default (useful in tests)

Shared styles file

The named-style registry is designed to live in a dedicated file you require at the top of each template — the PHP equivalent of a CSS stylesheet. This keeps all brand-specific colours, backgrounds, and typography rules out of your template logic.

my_styles.php

email.php

applyStyle() — targeting specific children

When a named style needs to address a specific named child of a section rather than the section itself, use applyStyle() instead of setStyle(). The optional 'children' key maps child property names to their own style arrays — other children are unaffected.

All keys except 'children' are applied to the node itself via setStyle(). The 'children' map recurses — if a child also has HasChildren, its entry can contain another 'children' key.


responsiveCss(int $breakpoint = 620)

Returns a complete <style> string to inject via $email->body->setCSS(…). It is the only place in the package that outputs a <style> block — everything else is inline CSS.

What it includes:


Translator

Translator handles all text placeholder resolution — multi-language versions, template tag output, PHP snippet generation, and runtime variable injection.

There are two workflows. Choose based on whether you need per-section **...** highlight overrides.


Workflow A — attached translator (recommended)

Attach $t to EmailDocument. Each build() call resolves placeholders and processes **...** highlighting in the same pass, so per-section highlight CSS overrides work correctly.

Since $t is an object, setLocale() propagates to the attached reference — no re-attachment needed between renders.


Workflow B — post-processor (build once, resolve many)

Build the tree once with {{placeholders}} intact, then resolve into as many variants as needed. **...** in translation values won't be highlighted (only static **...** in Text nodes is), but this workflow lets you produce tags, php, and custom ESP formats cheaply.

You can mix both: use workflow A for the rendered locales, and workflow B for tags/php exports from the same base.


Translations file

A plain PHP file that returns a keyed array. Each key maps to an array of locale → value pairs. Only include the locales you actually have — a missing locale keeps the {{key}} placeholder intact in the output.

Load it:


Using placeholders in nodes

Write {{key}} anywhere in a text string. Keys support dots (customer.first_name) and optional spaces inside braces ({{ key }}).

With an attached translator (workflow A) placeholders are resolved inside build(). With workflow B they survive build() and are resolved by resolve() afterward.


Runtime values — bind()

bind() injects a value that applies to every locale (same across languages — client name, order ID, URL, etc.). Bindings take priority over translations for all non-php locales.


Built-in modes

Mode Behaviour
'fr', 'en', any locale Replace {{key}} with the stored translation; keep tag if absent
'tags' Identity — HTML returned unchanged, all {{key}} intact
'php' Replace {{key}} with php entry from data, or generated snippet

php mode is designed for generating PHP template files (.php mail templates for other systems). It is protected: php entries never appear in toXml() output and php is excluded from locales().

The default PHP snippet is configurable:


XML export

toXml() produces an XML file of all translations — useful for handing off to a translation service or storing in a CMS. The php locale is always excluded.

Output format:


Complete workflow example


API reference — Translator

Method Description
__construct(array $data = [], string $open = '{{', string $close = '}}', string $phpSnippet = …) Initialize with optional data and delimiters
load(array $data): static Merge a full translations array (from require 'translations.php')
define(string $key, array $values): static Define or extend a single key
bind(string $key, string $value): static Runtime value — same across all locales, overrides translations
bindMany(array $values): static Batch bind
setLocale(string $locale): static Set the active locale; propagates immediately if $t is attached to EmailDocument
getLocale(): string Return the current locale
get(string $key, ?string $locale = null): string Resolve a single key
resolve(string $html, ?string $locale = null): string Replace all {{key}} placeholders in an HTML string (workflow B)
locales(): array All registered locales excluding protected ones (php)
toXml(array $locales = []): string Export to XML; php always excluded
useAsDefault(): static Register as process-wide default — called automatically by EmailDocument::build()
static getDefault(): ?static Return the current default, or null
static clearDefault(): void Clear the default (useful in tests)

Placeholder syntax: Keys support word characters, dots, and optional surrounding spaces — {{key}}, {{ key }}, {{customer.first_name}} are all valid.


Preset components

All presets are static factories that return fully configured node trees. Use deepclone() to reuse a preset multiple times.

Spacer

Every spacer sets font-size, line-height, and height to the same value + mso-line-height-rule: exactly — the only reliable way to enforce pixel-perfect height in Outlook.

Background color: when a $sheet is provided, the spacer inherits spacerBg from the theme. If spacerBg is empty (the default), it falls back to containerBg — so spacers visually blend with the surrounding sections without any extra configuration. Pass bg: explicitly to override per-instance. Without a sheet, the default is transparent.

Divider

Renders a border-bottom on a collapsed <td> (font-size:0; line-height:0; height:0) — the only approach that works reliably across Outlook, Gmail, and Apple Mail. The container is bounded by max-width: containerWidth so it stays within the email column on wide viewports.

Button — VML hybrid

What it generates:

VML arcsize formula: round(borderRadiusPx / min(width, height) * 100), capped at 50.

FullWidthImage

All parameters are optional — $src, $alt, and $href all default to ''. The tree structure is always col → link (Anchor) → img (Image) regardless of whether a link is used, so ->col->link and ->col->link->img are always accessible for deferred assignment.

When href is empty the Anchor renders transparently — its children output without any <a> wrapper. Set a link later via ->col->link->setLink($url).

No margin columns — image runs edge-to-edge. Uses width="600" HTML attribute alongside CSS width:100% so Outlook (which ignores max-width) still constrains the image. display:block eliminates the 4px baseline gap beneath inline images.

Section — 1 column with margins

Structure: Container(600px) > Column(30px) + Column(540px, section-body) + Column(30px).
The section-body class is targeted in responsiveCss() for full-width on mobile.

TwoColumn — responsive

Columns tagged col-2display:block; width:100% on mobile via responsiveCss().

ThreeColumn — responsive

Columns tagged col-3 → stack to full-width on mobile. If (containerWidth − margins) is not divisible by 3, the remainder pixels go to col1 to avoid sub-pixel Outlook overflow.

NColumn — N-column responsive grid

Columns tagged col-N (e.g. col-4) → display:block; width:100% on mobile. Remainder pixels after integer division are added to the first column.

ProductCard

E-commerce product card: image + title + description + price + optional CTA button. All parameters are optional — omit any you don't need. Designed to sit inside a TwoColumn, ThreeColumn, or NColumn cell (pass ->col to plug into the column slot), or standalone inside a Section for a full-width featured product.


AlertBar

Full-width colored banner with an optional icon prefix. Color and icon default to the type. Pass bgColor: / textColor: / icon: to override any part.

BulletList

Table-based bullet list compatible with all email clients. Each item renders as a two-cell row: bullet character + text. Place inside a Section->body — it is a content component, not a standalone layout block.

Coupon

Centered promo code block with a dashed border and a copyable code. Designed to stand alone at the email body level.

DataTable

Structured data table with header row. Carries the datatable CSS class, which responsiveCss() targets to reduce padding and font size on narrow viewports. Place inside a Section->body — it is a content component, not a standalone layout block.

Quote

Blockquote-style callout with a left border accent and optional author attribution. Uses primaryColor for the border.

PricingTable

Side-by-side pricing tier cards. The highlighted column ('highlight' => true) renders with a filled primaryColor background and no border; other columns use a 1px border. Column widths are computed to exactly fill the container without overflow.

StepIndicator

Horizontal step-progress tracker with numbered circles and connector lines. Steps before $current are "completed" (filled + checkmark), $current is "active" (filled), steps after are "upcoming" (hollow). Circle border-radius is applied to the <td> (not the <table>) for consistent 32×32px rendering across clients. Outlook renders the circles as squares — acceptable fallback.

SocialBar

Row of social icon links. Built-in platforms: facebook, twitter, instagram, linkedin, youtube, pinterest, tiktok, github. Each icon is an <a>-wrapped <img> with border="0" and moz-do-not-send="true". Replace the placeholder icon URLs with CDN-hosted images for production.

VideoBlock

Clickable video thumbnail with a centered play-button overlay. Since video cannot play inline in email, this renders a linked image that opens the video in a browser. Uses FullWidthImage internally.


Advanced nodes

ConditionalBlock

Wraps children in an MSO/IE conditional comment block. Use it to show or hide content specifically in Outlook 2007–2019, or to show content in all clients except Outlook.

Common condition strings: 'mso', '!mso', 'gte mso 9', '(gte mso 9)|(IE)'. When the condition starts with !, the inverted syntax (<!--[if !mso]><!-->…<!--<![endif]-->) is used automatically.


ForLoopBlock

Renders a list of items using a builder callback. The builder receives each item and its zero-based index and must return a Renderable node or a raw HTML string. No wrapper element is added.

Use ForLoopBlock when the node structure itself varies per item. For uniform repetition (same structure, different data), prefer DataTable, BulletList, or PricingTable.


RawHtml

Escape hatch that emits a pre-built HTML string verbatim as a Renderable node. The style cascade is intentionally bypassed.

Use sparingly — only when the tree primitives cannot express a particular email-safe structure.


HtmlImporter

Converts an existing HTML email into ready-to-run MailTree PHP code. The converter parses the HTML bottom-up, extracts theme tokens, groups columns into section rows, propagates common inline styles upward, and generates a full PHP template.

What the generated file includes:

The output is a starting point — review and adjust structure, styles, and placeholder keys before using in production.


Style cascade

Styles flow top-down through the tree at render time. Each node receives its parent's full style array and merges its own overrides on top.

Style arrays are always keyed by element type:


deepclone

The deepclone() helper (auto-loaded globally) deep-copies any node tree. Use it to safely reuse pre-built blocks:


API reference

EmailDocument

Method Description
__construct(?StyleSheet $sheet = null, ?Translator $t = null) $sheet applies the theme and registers itself as the global default. $t attaches a translator — placeholders and **...** are resolved on every build() call.
setSubject(string $title) <title> tag and email subject
addLink(string $href, string $rel) <link> in <head> (Google Fonts, etc.)
setStyle(array $style) Merge additional styles
getStyle(): array Full resolved style array
build(array $style = [], int $indent = 0, ?Translator $t = null, ?string $locale = null): string Render the document. With an attached translator the locale set on $t is used. Pass $t/$locale explicitly to override for a single call.

The methods below marked are available on all nodes that can have children (Body, Container, Column, Text, Anchor). Image only supports hide()/show().

Body

Method Description
__construct(array $style = []) Style overrides for <body>
setCSS(string $css) Inject <style> block — use $sheet->responsiveCss()
setStyle(array $style) Merge additional styles
getStyle(): array Full resolved style array
hide() / show() Toggle visibility in the rendered output
moveUp(int $steps = 1) Move earlier in parent's child list
moveDown(int $steps = 1) Move later in parent's child list
moveToIndex(int $index) Jump to absolute position (0 = first)
insertBefore(string $key) Reposition just before the named sibling
insertAfter(string $key) Reposition just after the named sibling
detach() Remove from parent; returns self for re-attachment
replaceWith(Renderable $node) Swap out in-place; returns the replaced node
duplicate() Clone + insert after self; returns the duplicate
getChildren(): array Snapshot of direct children keyed by property name
applyStyle(array $style) Apply semantic keys to self + optionally target named children via 'children' key; returns $this

Container

Renders as <table><tbody><tr>. Children should be Column instances.

Method Description
__construct(array $style = [], bool $mso = false) mso: true wraps with Outlook conditional comments
setClass(string $class) CSS class on <table>
setID(string $id) id on <table>
setStyle(array $style) Merge style overrides
getStyle(): array Full resolved style array
hide() / show() Toggle visibility in the rendered output
moveUp / moveDown / moveToIndex See Body above
insertBefore / insertAfter See Body above
detach / replaceWith / duplicate / getChildren See Body above
applyStyle(array $style) See Body above

Column

Renders as <td>.

Method Description
__construct(array $style = []) ['column' => [...]]
setClass(string $class) CSS class on <td> (used for responsive: col-2, col-3, section-body)
setStyle(array $style) Merge style overrides
getStyle(): array Full resolved style array
hide() / show() Toggle visibility in the rendered output
moveUp / moveDown / moveToIndex See Body above
insertBefore / insertAfter See Body above
detach / replaceWith / duplicate / getChildren See Body above
applyStyle(array $style) See Body above

Text

Wraps content in any inline or block tag. With $tag = null acts as a transparent wrapper. Supports inline **...** markup — see Text highlighting.

Method Description
__construct(string $text, ?string $tag = null, array $style = []) $tag: h1, h2, h3, div, p, span, etc. **...** in $text is highlighted at render time
setStyle(array $style) Merge style overrides
getStyle(): array Full resolved style array
hide() / show() Toggle visibility in the rendered output
moveUp / moveDown / moveToIndex See Body above
insertBefore / insertAfter See Body above
detach / replaceWith / duplicate / getChildren See Body above
applyStyle(array $style) See Body above

Image

Method Description
__construct(string $src = '', string $alt = '', array $style = [])
setSrc(string $src, ?string $alt = null) Update src (and optionally alt) after construction
setStyle(array $style) Merge style overrides
getStyle(): array Full resolved style array
hide() / show() Toggle visibility in the rendered output

Emits width/height HTML attributes only when the CSS value is a plain integer (not %, auto).

Anchor

Method Description
__construct(string $href = '', array $style = []) href = '' → renders transparently (no <a> tag, children output directly)
setLink(string $href) Update href after construction; set to '' to remove the link wrapper
setStyle(array $style) Merge style overrides
getStyle(): array Full resolved style array
hide() / show() Toggle visibility in the rendered output
moveUp / moveDown / moveToIndex See Body above
insertBefore / insertAfter See Body above
detach / replaceWith / duplicate / getChildren See Body above
applyStyle(array $style) See Body above

Text highlighting

Any Text node supports inline **...** markup. At render time, content wrapped in double asterisks is output as a <span> with the highlight style from the cascade — by default font-weight:bold + primaryColor.

With an attached translator (workflow A), {{customer.first_name}} is resolved first, then **...** is applied with the section's CSS — so per-section highlight overrides work correctly:

`...` in translation values — also works with workflow A, because placeholders and highlighting are resolved in the same build() pass:

With workflow B ($t->resolve() post-build) the highlight style from the cascade is no longer available, so **...** that arrives inside a translation value won't be processed. Use workflow A, or write HTML directly in the translation value.

Customizing the highlight style — override the highlight key in your StyleSheet. It follows the same base-style pattern as h1, container, etc.:

Notes:


Email HTML best practices applied

Every class and preset follows current best practices:


Using as a back-end for a visual builder

Because the tree is plain PHP objects, it maps naturally to a JSON payload from a drag-and-drop front-end:


License

MIT


All versions of php-mail-tree with dependencies

PHP Build Version
Package Version
Requires php Version >=8.2
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package rlnks/php-mail-tree contains the following files

Loading the files please wait ...