rlnks / php-mail-tree-builder
Framework-agnostic PSR-7/PSR-15 API handler for the php-mail-tree visual builder
v0.1.1
2026-05-14 02:43 UTC
Requires
- php: >=8.2
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- rlnks/php-mail-tree: ^1.0
Requires (Dev)
- nyholm/psr7: ^1.8
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