PHP code example of rlnks / php-mail-tree

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);

// ── 1. Skeleton — declare every section up front ──────────────────────────
$email = new EmailDocument($sheet);   // sets theme + registers $sheet as global default
$email->body = new Body();
$email->body->setCSS($sheet->responsiveCss());

$email->body->banner   = FullWidthImage::make();  // src/href filled in step 2
$email->body->gap1     = Spacer::make();
$email->body->intro    = Section::make();
$email->body->gap2     = Spacer::make();
$email->body->features = TwoColumn::make();
$email->body->divider  = Divider::make();
$email->body->footer   = Section::make();

// ── 2. Content — fill each section independently ──────────────────────────
$email->body->banner->col->link->setLink($heroUrl);
$email->body->banner->col->link->img->setSrc($heroSrc, 'Hero');

$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);

$email->body->features->left->img    = new Image($img1, 'Feature A');
$email->body->features->right->title = new Text('Feature **A**', 'h2');   // ** highlights in primaryColor
$email->body->features->right->desc  = new Text('Best feature ever.', 'div');

$email->body->footer->body->copy = new Text('© 2025 Acme Corp', 'div');

// ── 3. Render ──────────────────────────────────────────────────────────────
echo $email->build();

$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();


// Runs in the same scope as email.php; $sheet is already created there.

$sheet->define('header', [
    'container' => ['background-color' => '#ffffff'],
    'img'       => ['max-width' => '160px', 'margin' => 'auto', 'display' => 'block'],
]);

$sheet->define('hero', [
    'container' => ['background-color' => '#003366'],
    'h1'        => ['color' => '#ffffff', 'font-size' => '32px', 'font-weight' => 'bold'],
    'div'        => ['color' => '#aac4ff', 'line-height' => '170%'],
]);

$sheet->define('intro', [
    'container' => ['background-color' => '#ffffff'],
    'h2'        => ['color' => '#003366'],
    'div'        => ['color' => '#444444', 'line-height' => '160%'],
]);

$sheet->define('feature', [
    'container' => ['background-color' => '#f9f9f9'],
    'h3'        => ['color' => '#003366', 'font-size' => '18px'],
    'div'        => ['color' => '#555555'],
]);

$sheet->define('footer', [
    'container' => ['background-color' => '#eeeeee'],
    'div'        => ['color' => '#888888', 'font-size' => '12px', 'text-align' => 'center'],
    'a'          => ['color' => '#888888', 'text-decoration' => 'underline'],
]);


// Returns an associative array: key → [locale => value, …]
// Omit a locale to keep {{key}} intact for that language.
// 'php' key: override the generated PHP snippet for dynamic server-side values.

return [

    // ── Email meta ────────────────────────────────────────────────────────────
    'subject' => [
        'fr'  => 'Votre commande est confirmée!',
        'en'  => 'Your order is confirmed!',
        'php' => " echo \$email->subject(); 


use Rlnks\MailTree\Anchor;
use Rlnks\MailTree\Body;
use Rlnks\MailTree\EmailDocument;
use Rlnks\MailTree\Image;
use Rlnks\MailTree\StyleSheet;
use Rlnks\MailTree\Text;
use Rlnks\MailTree\Translator;
use Rlnks\MailTree\Preset\Button;
use Rlnks\MailTree\Preset\Divider;
use Rlnks\MailTree\Preset\Section;
use Rlnks\MailTree\Preset\Spacer;
use Rlnks\MailTree\Preset\TwoColumn;

// ── 1. Theme & named styles ───────────────────────────────────────────────────

$sheet = new StyleSheet([
    'primaryColor'   => '#003366',
    'textColor'      => '#444444',
    'bgColor'        => '#f0f0f0',
    'containerBg'    => '#ffffff',
    'fontFamily'     => 'Arial, sans-serif',
    'baseFontSize'   => '15px',
    'containerWidth' => 600,
    'marginWidth'    => 30,
]);

}}');
$email->addLink('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700', 'stylesheet');

$email->body = new Body();
$email->body->setCSS($sheet->responsiveCss());

$email->body->header  = Section::make();
$email->body->gap1    = Spacer::make();
$email->body->hero    = Section::make();
$email->body->gap2    = Spacer::make();
$email->body->intro   = Section::make();
$email->body->gap3    = Spacer::make();
$email->body->feature = TwoColumn::make();
$email->body->gap4    = Spacer::make();
$email->body->divider = Divider::make();
$email->body->footer  = Section::make();

// ── 4. Section styles ─────────────────────────────────────────────────────────

$email->body->header->setStyle($sheet->get('header'));
$email->body->hero->setStyle($sheet->get('hero'));
$email->body->intro->setStyle($sheet->get('intro'));
$email->body->feature->setStyle($sheet->get('feature'));
$email->body->footer->setStyle($sheet->get('footer'));

// ── 5. Images ─────────────────────────────────────────────────────────────────

$img_logo    = new Image(
    'https://cdn.example.com/logo.png',
    'Example Corp',
    ['img' => ['max-width' => '160px', 'margin' => 'auto', 'display' => 'block']],
);

$img_feature = new Image(
    'https://cdn.example.com/portal-preview.jpg',
    '{{feature_title}}',
    ['img' => ['width' => '100%', 'display' => 'block']],
);

// ── 6. Links ──────────────────────────────────────────────────────────────────

$link_logo  = new Anchor('https://example.com');
$link_unsub = new Anchor('https://example.com/unsubscribe?id={{order_id}}');
$link_rlnks = new Anchor('https://rlnks.com');

// ── 7. Assemble — insert images, links and text into the skeleton ─────────────

// Header — logo wrapped in a link
$link_logo->logo = $img_logo;
$email->body->header->body->logo = $link_logo;

// Hero — headline + description
$email->body->hero->body->title = new Text('{{hero_title}}', 'h1');
$email->body->hero->body->desc  = new Text('{{hero_desc}}',  'div');

// Intro — order summary + CTA button
$email->body->intro->body->title = new Text('{{intro_title}}', 'h2');
$email->body->intro->body->desc  = new Text('{{intro_desc}}',  'div');
$email->body->intro->body->cta   = Button::make(
    '{{intro_cta}}',
    'https://example.com/order/{{order_id}}',
);

// Feature — two-column: image left, text + link right
$email->body->feature->left->img    = $img_feature;
$email->body->feature->right->title = new Text('{{feature_title}}', 'h3');
$email->body->feature->right->desc  = new Text('{{feature_desc}}',  'div');
$email->body->feature->right->cta   = Button::make(
    '{{feature_cta}}',
    'https://example.com/portal',
);

// Footer — legal, unsubscribe link, powered-by
$email->body->footer->body->legal = new Text('{{legal}}', 'div');

$link_unsub->text = new Text('{{unsub_text}}', 'span');
$email->body->footer->body->unsub = $link_unsub;

$link_rlnks->text = new Text('{{powered_by}}', 'span');
$email->body->footer->body->brand = $link_rlnks;

// ── 8. Render per locale (workflow A) ────────────────────────────────────────
// build() resolves placeholders and **...** highlighting in one pass.
// setLocale() on the attached $t propagates immediately — no re-attachment.

$t->setLocale('fr');
$version_fr = $email->build();

$t->setLocale('en');
$version_en = $email->build();

// ── 9. ESP / PHP exports (workflow B) ────────────────────────────────────────
// Build without a locale to get raw {{placeholders}}, then resolve for each format.

$base         = $email->build();
$version_tags = $t->resolve($base, 'tags');   // {{key}} intact — for Mailchimp, Klaviyo, etc.
$version_php  = $t->resolve($base, 'php');    // PHP snippet template

// Export translations for review or handoff to a translation service
$xml = $t->toXml(['fr', 'en']);
file_put_contents(__DIR__ . '/translations.xml', $xml);

$sheet = new StyleSheet([
    'primaryColor'   => '#e63946',
    'textColor'      => '#333333',
    'bgColor'        => '#f4f4f4',
    'containerBg'    => '#ffffff',
    'fontFamily'     => "Poppins, Arial, sans-serif",
    'baseFontSize'   => '15px',
    'lineHeight'     => '160%',
    'containerWidth' => 600,
    'marginWidth'    => 30,
]);

// 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'
]);

$email = new EmailDocument($sheet);

[
    'body' => [
        'background-color'         => '#f4f4f4',   // ← from bgColor token
        'margin'                   => 0,
        '-webkit-text-size-adjust' => '100%',
    ],
    'text' => [
        'font-family' => 'Poppins, Arial, sans-serif',  // ← from fontFamily
        'font-size'   => '15px',                        // ← from baseFontSize
        'line-height' => '160%',                        // ← from lineHeight
        'color'       => '#333333',                     // ← from textColor
    ],
    'h1'  => ['font-size' => '28px', 'color' => '#e63946', …],  // ← from primaryColor
    'h2'  => ['font-size' => '22px', 'color' => '#e63946', …],
    'a'   => ['color' => '#e63946', 'text-decoration' => 'none'],
    // …
]

// 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'],
]);


// All section styles in one place.
// Semantic keys follow the library vocabulary: container, column, text, h1, div, img, a, …

$sheet->define('logo', [
    'img' => ['max-width' => '150px', 'margin' => 'auto', 'display' => 'block'],
]);

$sheet->define('hero', [
    'container' => ['background-color' => '#003366'],
    'h1'        => ['color' => '#ffffff', 'font-size' => '32px'],
    'div'        => ['color' => '#aac4ff', 'line-height' => '170%'],
]);

$sheet->define('promo', [
    'container' => ['background-color' => '#fff8e1'],
    'h2'        => ['color' => '#e63946'],
    'div'        => ['color' => '#333333'],
]);

$sheet->define('credits', [
    'container' => ['background-color' => '#f0f0f0'],
    'div'        => ['color' => '#888888', 'font-size' => '12px', 'text-align' => 'center'],
    'a'          => ['color' => '#888888'],
]);


$sheet = new StyleSheet(['primaryColor' => '#e63946', /* … */]);
al default
$email->body = new Body();
$email->body->setCSS($sheet->responsiveCss());

// ── Structure ──────────────────────────────────────────────────────────────
$email->body->logo    = Section::make();   // no sheet: needed — default auto-resolved
$email->body->hero    = Section::make();
$email->body->promo   = Section::make();
$email->body->credits = Section::make();

// ── Styles — one line per section ─────────────────────────────────────────
$email->body->logo->setStyle($sheet->get('logo'));
$email->body->hero->setStyle($sheet->get('hero'));
$email->body->promo->setStyle($sheet->get('promo'));
$email->body->credits->setStyle($sheet->get('credits'));

// ── Content ────────────────────────────────────────────────────────────────
$email->body->logo->body->img      = new Image($logoSrc, 'Acme');
$email->body->hero->body->title    = new Text('Order confirmed!', 'h1');
$email->body->hero->body->desc     = new Text('Thanks for your purchase.', 'div');
$email->body->promo->body->title   = new Text('You may also like', 'h2');
$email->body->credits->body->copy  = new Text('© 2025 Acme Corp · Unsubscribe', 'div');

echo $email->build();

$sheet->define('sectionPromo', [
    'container' => ['background-color' => '#003366'],   // applied to this node
    'children'  => [
        'body' => [                                      // applied to ->body only
            'column' => ['background-color' => '#00264d'],
        ],
    ],
]);

$email->body->promo->applyStyle($sheet->get('sectionPromo'));

// Chaining:
$email->body->hero
    ->applyStyle($sheet->get('hero'))
    ->applyStyle(['h2' => ['font-size' => '20px']]);   // one-off tweak

$email->body->setCSS($sheet->responsiveCss());                // breakpoint: 620px (default)
$email->body->setCSS($sheet->responsiveCss(breakpoint: 480)); // tighter breakpoint
$email->body->setCSS($sheet->responsiveCss(darkMode: true));  // + dark mode block

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\Spacer;

$frame->gap1 = Spacer::make('20px');
$frame->gap2 = Spacer::make(sheet: $sheet);          // uses sheet's spacerHeight + spacerBg
$frame->gap3 = Spacer::make('40px', sheet: $sheet);  // explicit height, sheet bg
$frame->gap4 = Spacer::make('20px', bg: '#f0f0f0');  // explicit bg override

use Rlnks\MailTree\Preset\Divider;

$frame->rule = Divider::make();
$frame->rule = Divider::make(color: '#cccccc', width: '2px', paddingY: '20px');
$frame->rule = Divider::make(sheet: $sheet);

use Rlnks\MailTree\Preset\Button;

$section->body->cta = Button::make('Subscribe', 'https://example.com');
$section->body->cta = Button::make('Buy now', $url,
    bgColor:      '#e63946',
    width:        240,
    height:       52,
    borderRadius: '26px',   // pill shape
    sheet:        $sheet,
);

use Rlnks\MailTree\Preset\FullWidthImage;

// Inline — everything in one call:
$frame->hero = FullWidthImage::make($src, 'Hero image');
$frame->hero = FullWidthImage::make($src, $alt, href: 'https://…');

// Skeleton-first — declare structure now, fill content later:
$email->body->hero = FullWidthImage::make();
// … later in the content section …
$email->body->hero->col->link->setLink($heroUrl);       // empty href → no <a> wrapper
$email->body->hero->col->link->img->setSrc($heroSrc, $heroAlt);

use Rlnks\MailTree\Preset\Section;

$s = Section::make(sheet: $sheet);
$s->body->title = new Text('Hello!', 'h1');
$s->body->desc  = new Text('Body copy.', 'div');
$s->body->cta   = Button::make('Go', $url, sheet: $sheet);
$s->setStyle(['container' => ['background-color' => '#f9f9f9']]);

use Rlnks\MailTree\Preset\TwoColumn;

$row = TwoColumn::make(sheet: $sheet);
$row->left->img    = new Image($src1, $alt1);
$row->right->title = new Text('Feature name', 'h2');
$row->right->desc  = new Text('Description…', 'div');

// Add gutter between columns via padding
$row->left->setStyle(['column'  => ['padding-right' => '10px']]);
$row->right->setStyle(['column' => ['padding-left'  => '10px']]);

use Rlnks\MailTree\Preset\ThreeColumn;

$row = ThreeColumn::make(sheet: $sheet);
$row->col1->img = new Image($src1, '');
$row->col2->img = new Image($src2, '');
$row->col3->img = new Image($src3, '');

use Rlnks\MailTree\Preset\NColumn;

$row = NColumn::make(4, sheet: $sheet);           // 4 equal columns
$row->col1->img = new Image($src1, '');
$row->col2->img = new Image($src2, '');

// Asymmetric widths (percentages, must sum to 100):
$row = NColumn::make(2, widths: [40, 60], sheet: $sheet);

use Rlnks\MailTree\Preset\ProductCard;
use Rlnks\MailTree\Preset\TwoColumn;

// Standalone featured product:
$email->body->featured = ProductCard::make(
    imageSrc:    'https://cdn.example.com/product.jpg',
    title:       'Widget Pro',
    description: 'The best widget on the market.',
    price:       '$49.99',
    ctaLabel:    'Buy now',
    ctaUrl:      'https://example.com/widget-pro',
    sheet:       $sheet,
);

// Inside a multi-column layout (use ->col to plug into the column slot):
$row = TwoColumn::make(sheet: $sheet);
$row->left  = ProductCard::make('https://cdn/a.jpg', 'Widget A', price: '$9.99',  ctaUrl: $url1, sheet: $sheet)->col;
$row->right = ProductCard::make('https://cdn/b.jpg', 'Widget B', price: '$19.99', ctaUrl: $url2, sheet: $sheet)->col;

use Rlnks\MailTree\Preset\AlertBar;

$alert = AlertBar::make('Your account is pending verification.', sheet: $sheet);
$alert = AlertBar::make('Shipped!', type: 'success', icon: '✓', sheet: $sheet);
// types: 'info' (default) | 'success' | 'warning' | 'error'

use Rlnks\MailTree\Preset\BulletList;

$list = BulletList::make([
    'Free shipping on all orders',
    'Cancel anytime',
    '30-day money-back guarantee',
], sheet: $sheet);

// Custom bullet character:
$list = BulletList::make($items, bullet: '→', sheet: $sheet);

use Rlnks\MailTree\Preset\Coupon;

$coupon = Coupon::make(code: 'SAVE20', label: 'Use code at checkout', sheet: $sheet);
$coupon = Coupon::make(code: 'PROMO', bgColor: '#003366', textColor: '#ffffff', sheet: $sheet);

use Rlnks\MailTree\Preset\DataTable;

$table = DataTable::make(
    headers: ['Item', 'Qty', 'Price'],
    rows: [
        ['Widget A', '2', '$9.99'],
        ['Widget B', '1', '$14.99'],
    ],
    sheet: $sheet,
);
$section->body->table = $table;

use Rlnks\MailTree\Preset\Quote;

$q = Quote::make(
    text:   '"This product changed my workflow completely."',
    author: 'Jane D., Designer',
    sheet:  $sheet,
);

use Rlnks\MailTree\Preset\PricingTable;

$pricing = PricingTable::make([
    ['name' => 'Basic',  'price' => '$9',  'features' => ['1 user', '5 GB']],
    ['name' => 'Pro',    'price' => '$29', 'features' => ['5 users', '50 GB'], 'highlight' => true],
    ['name' => 'Enterprise', 'price' => '$99', 'features' => ['Unlimited', '500 GB']],
], sheet: $sheet);

use Rlnks\MailTree\Preset\StepIndicator;

$steps = StepIndicator::make(
    steps:   ['Order placed', 'Processing', 'Shipped', 'Delivered'],
    current: 1,   // zero-based; 0 = first active
    sheet:   $sheet,
);
$email->body->progress = $steps;

use Rlnks\MailTree\Preset\SocialBar;

$bar = SocialBar::make([
    ['platform' => 'facebook',  'url' => 'https://facebook.com/acme'],
    ['platform' => 'twitter',   'url' => 'https://twitter.com/acme'],
    ['platform' => 'instagram', 'url' => 'https://instagram.com/acme'],
    // Custom icon:
    ['platform' => 'custom', 'url' => 'https://…', 'icon' => 'https://cdn/icon.png', 'alt' => 'Podcast'],
], sheet: $sheet);
$email->body->footer->body->social = $bar;

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']]);

$spacer = Spacer::make('20px');

$frame->before_header = deepclone($spacer);
$frame->after_header  = deepclone($spacer);
$frame->before_footer = deepclone($spacer);

$section->body->intro = new Text('Bonjour **{{customer.first_name}}**, votre commande est confirmée.', 'p');
$section->body->price = new Text('Total: **$49.99**', 'div');

// translations.php
'greetings' => [
    'fr' => 'Salut **{{ customer.first_name }}**,',
    'en' => 'Hello **{{ customer.first_name }}**,',
],

// template
$section->body->greeting = new Text('{{greetings}}', 'p');
// build() resolves {{greetings}} → 'Salut **Philippe**,' → <span style="SECTION_CSS">Philippe</span>

$sheet = new StyleSheet([
    'primaryColor' => '#e63946',
    'highlight' => [
        'color'            => '#e63946',
        'font-weight'      => 'bold',
        'background-color' => '#fff0f0',
        'padding'          => '1px 3px',
    ],
]);

function buildFromJson(array $node): Renderable {
    return match($node['type']) {
        'section'    => tap(Preset\Section::make(),    fn($s) => buildChildren($s->body, $node['children'] ?? [])),
        'two-column' => tap(Preset\TwoColumn::make(), fn($s) => buildChildren($s, $node['children'] ?? [])),
        'text'       => new Text($node['content'], $node['tag'] ?? null, $node['style'] ?? []),
        'image'      => new Image($node['src'], $node['alt'] ?? ''),
        'button'     => Preset\Button::make($node['label'], $node['href']),
        'spacer'     => Preset\Spacer::make($node['height'] ?? '20px'),
        default      => throw new \InvalidArgumentException("Unknown node: {$node['type']}"),
    };
}
bash
composer