rlnks/php-mail-tree-builder

Framework-agnostic PSR-7/PSR-15 API handler for the php-mail-tree visual builder

Maintainers

Package info

github.com/rlnks/php-mail-tree-builder

pkg:composer/rlnks/php-mail-tree-builder

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-05-14 02:43 UTC

This package is auto-updated.

Last update: 2026-05-14 02:45:18 UTC


README

Framework-agnostic PSR-7/PSR-15 API handler for the php-mail-tree visual builder.

Installation

composer require rlnks/php-mail-tree-builder

Requires PHP 8.2+ and a PSR-7/PSR-17 implementation (e.g. nyholm/psr7).

Endpoints

Method Path Description
POST {prefix}/render/node Render a single node → HTML snippet
POST {prefix}/render/document Render the full email → complete HTML
GET {prefix}/document List saved documents
POST {prefix}/document Create a new document
GET {prefix}/document/{id} Load a document
PUT {prefix}/document/{id} Save / update a document
DELETE {prefix}/document/{id} Delete a document

Quick start

use Nyholm\Psr7\Factory\Psr17Factory;
use Rlnks\MailTreeBuilder\BuilderConfig;
use Rlnks\MailTreeBuilder\BuilderHandler;

$factory = new Psr17Factory();

$handler = new BuilderHandler(
    config: new BuilderConfig(
        pathPrefix:  '/builder',
        corsOrigins: ['http://localhost:3000'],
        storage:     new MyStorage(),   // implements StorageInterface
    ),
    responseFactory: $factory,
);

// Slim 4
$app->any('/builder/{path:.*}', $handler);

// Vanilla PHP
$request  = $factory->createServerRequest($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
$response = $handler->handle($request);
http_response_code($response->getStatusCode());
foreach ($response->getHeaders() as $name => $values) {
    header($name . ': ' . implode(', ', $values));
}
echo $response->getBody();

POST /render/node

Render a single node from a JSON tree node + StyleSheet config. The builder calls this whenever a node is added or modified, caching the HTML for canvas assembly.

{
  "sheet": {
    "primaryColor": "#003366",
    "fontFamily": "Poppins, Arial, sans-serif",
    "containerWidth": 600,
    "webFonts": [{ "url": "https://fonts.googleapis.com/…", "name": "Poppins" }],
    "styles": {
      "hero": { "container": { "background-color": "#003366" } }
    }
  },
  "node": {
    "type": "Container",
    "_tag": "Section",
    "style": { "container": { "background-color": "#ffffff" } },
    "hidden": false,
    "children": { "body": { "type": "Column", "children": {} } }
  }
}

Response: { "html": "<table>…</table>" }

POST /render/document

Render the full email. Used for the Preview button. Returns the complete <!DOCTYPE html> string.

{
  "sheet": { "…": "" },
  "tree": { "type": "EmailDocument", "…": "" },
  "locale": "fr",
  "translations": {
    "hero_title": { "fr": "Bienvenue!", "en": "Welcome!" }
  }
}

Response: { "html": "<!DOCTYPE html>…" }

Storage

Implement StorageInterface to persist documents in your preferred backend:

use Rlnks\MailTreeBuilder\StorageInterface;

class DatabaseStorage implements StorageInterface
{
    public function save(?string $id, array $document): string
    {
        $id ??= uniqid('doc_', true);
        DB::table('builder_documents')->upsert(['id' => $id, 'data' => json_encode($document)], ['id']);
        return $id;
    }

    public function load(string $id): array
    {
        $row = DB::table('builder_documents')->find($id)
            ?? throw new \RuntimeException("Document {$id} not found.");
        return json_decode($row->data, true);
    }

    public function delete(string $id): void
    {
        DB::table('builder_documents')->delete($id);
    }

    public function list(): array
    {
        return DB::table('builder_documents')
            ->select('id', 'updated_at')
            ->orderByDesc('updated_at')
            ->get()
            ->toArray();
    }
}

License

MIT