1. Go to this page and download the library: Download rlnks/php-mail-tree library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
rlnks / php-mail-tree example snippets
$email->body = new Body();
$email->body->header = new Container();
$email->body->header->logo_col = new Column();
$email->body->header->logo_col->logo = new Image($src, $alt);
$email->body->promo = Section::make(sheet: $sheet);
if (!$user->hasPromoAccess()) {
$email->body->promo->hide();
}
// Chainable at assignment time:
$email->body->notice = (new Text('Beta feature', 'div'))->hide();
$email->body->intro = Section::make(sheet: $sheet);
$email->body->features = TwoColumn::make(sheet: $sheet);
$email->body->cta = Section::make(sheet: $sheet);
$email->body->features->moveUp(); // swap features above intro
$email->body->cta->moveToIndex(0); // jump to first position
$email->body->cta->moveDown(2); // multi-step shift
// Skeleton built in order A → B → C
$email->body->intro = Section::make(sheet: $sheet);
$email->body->features = TwoColumn::make(sheet: $sheet);
$email->body->footer = Section::make(sheet: $sheet);
// Inject a new divider between features and footer:
$email->body->divider = Divider::make(sheet: $sheet);
$email->body->divider->insertBefore('footer');
// Or move features after the footer:
$email->body->features->insertAfter('footer');
// Remove a section from the tree entirely (returns the detached node):
$removed = $email->body->promo->detach();
// Swap a node with a new one, keeping the same key and position:
$email->body->intro->body->title->replaceWith(new Text('New Title', 'h1'));
// Deep-clone a node and insert it immediately after itself:
$copy = $email->body->card->duplicate();
$copy->body->title->replaceWith(new Text('Card 2', 'h2'));
// Duplicate key is auto-generated: "card_2", "card_3", …
foreach ($email->body->getChildren() as $key => $node) {
if (!$node->isHidden()) {
echo "$key is visible\n";
}
}
use Rlnks\MailTree\Body;
use Rlnks\MailTree\EmailDocument;
use Rlnks\MailTree\Image;
use Rlnks\MailTree\StyleSheet;
use Rlnks\MailTree\Text;
use Rlnks\MailTree\Preset\Button;
use Rlnks\MailTree\Preset\FullWidthImage;
use Rlnks\MailTree\Preset\Section;
use Rlnks\MailTree\Preset\Spacer;
use Rlnks\MailTree\Preset\TwoColumn;
// 1. Define your theme once
$sheet = new StyleSheet([
'primaryColor' => '#e63946',
'fontFamily' => "Poppins, Arial, sans-serif",
'containerWidth' => 600,
'marginWidth' => 30,
]);
// 2. Bootstrap the email — passing $sheet directly sets the theme AND registers
// it as the global default so every preset resolves it automatically.
$email = new EmailDocument($sheet);
$email->setSubject('Your order is confirmed');
$email->addLink('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700', 'stylesheet');
$email->body = new Body();
$email->body->setCSS($sheet->responsiveCss());
// 3. Build the tree with presets — no sheet: $sheet needed anywhere
$email->body->banner = FullWidthImage::make($heroSrc, 'Hero', href: $heroUrl);
$email->body->gap1 = Spacer::make();
$email->body->intro = Section::make();
$email->body->intro->body->title = new Text('Order confirmed!', 'h1');
$email->body->intro->body->desc = new Text('Thanks for your purchase.', 'div');
$email->body->intro->body->cta = Button::make('View order', $orderUrl);
// Override specific CSS on a preset without touching the theme:
$email->body->gap2 = Spacer::make(bg: '#f0f0f0'); // explicit bg, height from sheet
$email->body->features = TwoColumn::make();
$email->body->features->left->img = new Image($img1, 'Feature A');
$email->body->features->right->title = new Text('Feature A', 'h2');
echo $email->build();
// All CSS properties inside the keys are standard CSS, written exactly as in a stylesheet.
new Container([
'container' => [
'background-color' => '#ffffff', // standard CSS — goes on the <table>
'width' => '600px',
'border-collapse' => 'collapse',
],
]);
new Column([
'column' => [
'width' => '270px', // standard CSS — goes on the <td>
'padding' => '0 20px',
],
]);
new Text('Hello', 'h1', [
'text' => ['font-family' => 'Arial'], // base — applies to all tags in this node
'h1' => ['color' => '#e63946'], // tag-specific — merged on top of 'text'
]);
// Override just the background on an otherwise standard container:
$c = new Container($sheet->containerStyle([
'container' => ['background-color' => '#fffbe6'],
]));
// ── Define once, usually at the top of your template ──────────────────────
$sheet->define('hero', [
'container' => ['background-color' => '#003366'], // standard CSS on the <table>
'h1' => ['color' => '#ffffff', 'font-size' => '32px'],
'div' => ['color' => '#aac4ff', 'line-height' => '170%'],
]);
$sheet->define('highlight', [
'container' => [
'background-color' => '#fff8e1',
'border-left' => '4px solid #e63946',
],
'div' => ['color' => '#333333', 'padding' => '0 0 0 12px'],
]);
$sheet->define('footer', [
'container' => ['background-color' => '#f0f0f0'],
'div' => ['color' => '#888888', 'font-size' => '12px', 'text-align' => 'center'],
'a' => ['color' => '#888888'],
]);
// ── Apply to sections ──────────────────────────────────────────────────────
$email->body->hero->setStyle($sheet->get('hero'));
$email->body->note->setStyle($sheet->get('highlight'));
$email->body->foot->setStyle($sheet->get('footer'));
// Retrieve with per-call overrides (does not modify the stored definition):
$email->body->alt_hero->setStyle($sheet->get('hero', [
'h1' => ['font-size' => '24px'], // smaller h1, everything else from 'hero'
]));
// Extend a definition in-place (modifies the stored definition):
$sheet->extend('hero', [
'container' => ['border-bottom' => '3px solid #e63946'],
]);
use Rlnks\MailTree\Translator;
$t = new Translator(irstName]);
$email = new EmailDocument($sheet, $t); // $t attached here
// … build tree …
$t->setLocale('fr');
$html_fr = $email->build(); // resolves + highlights in one pass
$t->setLocale('en');
$html_en = $email->build();
$base = $email->build(); // HTML with {{...}} intact
$html_fr = $t->resolve($base, 'fr'); // French text
$html_en = $t->resolve($base, 'en'); // English text
$html_tags = $t->resolve($base, 'tags'); // placeholders untouched (for Mailchimp etc.)
$html_php = $t->resolve($base, 'php'); // PHP snippet template
// translations.php
return [
'welcome_title' => [
'fr' => 'Bienvenue!',
'en' => 'Welcome!',
// 'de' absent → {{welcome_title}} stays intact when rendering in German
],
'cta_btn' => [
'fr' => 'Voir ma commande',
'en' => 'View my order',
],
// 'php' key: override the generated PHP snippet for complex expressions
'client_name' => [
'fr' => 'Client', // fallback for static renders
'en' => 'Client',
'php' => ' echo htmlspecialchars($customer->firstName);
$t = new Translator(:
$t->load(=> 'Extra', 'en' => 'Extra']);
$section->body->title = new Text('{{welcome_title}}', 'h1');
$section->body->desc = new Text('Bonjour {{customer.first_name}}, commande #{{order_id}}', 'div');
$section->body->cta = Button::make('{{cta_btn}}', $url);
$t->bind('client_name', $customer->firstName);
$t->bind('order_id', (string) $order->id);
// Or in batch:
$t->bindMany([
'client_name' => $customer->firstName,
'order_id' => (string) $order->id,
]);
$t = new Translator($data, phpSnippet: " echo \$vars['{key}'];
$xml = $t->toXml(); // all locales
$xml = $t->toXml(['fr', 'en']); // specific locales only (php still excluded)
file_put_contents('translations.xml', $xml);
$t = new Translator('customer.first_name' => $customer->firstName,
'order_id' => (string) $order->id,
]);
$email = new EmailDocument($sheet, $t); // attach translator
// … build tree …
$email->body->intro->body->title = new Text('**{{welcome_title}}**', 'h1');
$email->body->intro->body->desc = new Text('Bonjour **{{customer.first_name}}**, commande #{{order_id}}.', 'div');
$email->body->intro->body->cta = Button::make('{{cta_btn}}', $orderUrl);
$email->body->foot->body->unsub = new Text('<a href="{{unsub_link}}">{{unsub_text}}</a>', 'div');
// ── Workflow A — per-locale build (highlight works with translations) ──────────
$t->setLocale('fr');
$html_fr = $email->build(); // resolves + highlights in one pass
$t->setLocale('en');
$html_en = $email->build();
// ── Workflow B — post-build resolve (for ESP formats, php template) ───────────
$base = $email->build(); // {{placeholders}} intact
$template = $t->resolve($base, 'tags'); // for Mailchimp, Klaviyo, etc.
$php_tmpl = $t->resolve($base, 'php'); // dynamic PHP template
$mc_tmpl = $t->resolve($base, 'mailchimp'); // Mailchimp merge tags
// Export for translation service
file_put_contents('translations.xml', $t->toXml(['fr', 'en']));
use Rlnks\MailTree\Preset\VideoBlock;
$video = VideoBlock::make(
thumbnail: 'https://cdn/thumb.jpg',
href: 'https://youtube.com/watch?v=…',
alt: 'Watch the product demo',
sheet: $sheet,
);
use Rlnks\MailTree\ConditionalBlock;
// Outlook-only content:
$block = new ConditionalBlock('mso');
$block->notice = new Text('Please view this email in a modern client.', 'div');
$email->body->notice = $block;
// Non-Outlook content (hidden in Outlook):
$block = new ConditionalBlock('!mso');
$block->hero = FullWidthImage::make($src, 'Hero');
$email->body->hero = $block;
use Rlnks\MailTree\ForLoopBlock;
// Simple text list:
$section->body->features = ForLoopBlock::make(
['Fast', 'Cross-client', 'Accessible'],
fn($item, $i) => new Text("• {$item}", 'div', ['div' => ['margin' => '0 0 6px 0']]),
);
// Dynamic product rows from a data source:
$section->body->items = ForLoopBlock::make(
$orderLines,
fn($line, $i) => new Text("{$line['name']} — {$line['price']}", 'div'),
);
use Rlnks\MailTree\RawHtml;
$col->overlay = RawHtml::make('<table role="presentation" style="…">…</table>');
use Rlnks\MailTree\HtmlImporter;
$php = HtmlImporter::convert(file_get_contents('legacy.html'));
file_put_contents('rebuilt.php', $php);
// Custom output filename (embedded in the generated build call):
$php = HtmlImporter::convert($html, outputFile: 'output.html');
$email = new EmailDocument([
'text' => ['font-size' => '14px', 'color' => '#333'],
'h1' => ['font-size' => '28px', 'color' => '#c00'],
]);
// This node overrides h1 color only — font-size still inherited
$section->body->title = new Text('Hello', 'h1', ['h1' => ['color' => '#00c']]);
new Container(['container' => ['background-color' => '#fff', 'width' => '600px']]);
new Column (['column' => ['width' => '540px', 'padding' => '0 10px']]);
new Text('…', 'div', ['div' => ['font-size' => '16px']]);
new Image($src, $alt, ['img' => ['max-width' => '200px']]);
new Anchor($href, ['a' => ['color' => '#c00']]);