Appearance
Pocketbook's template system enables organizations to standardize workflows, automate document generation, and customize user experiences at scale.
Overview
Templates provide:
- Workflow automation: Standardize repeating processes
- Document generation: Create consistent, branded documents
- Email customization: Personalized communication templates
- Report templates: Automated reporting with custom formatting
- Invoice templates: Professional, branded invoices
- Integration templates: Pre-configured third-party integrations
Template Types
1. Document Templates
Create templates for invoices, receipts, and reports:
typescript
// src/templates/document.ts
interface DocumentTemplate {
id: string;
name: string;
type: 'invoice' | 'receipt' | 'report' | 'contract';
format: 'pdf' | 'html' | 'docx';
template: string;
variables: TemplateVariable[];
styling: TemplateStyle;
}
interface TemplateVariable {
name: string;
type: 'string' | 'number' | 'date' | 'currency';
required: boolean;
defaultValue?: any;
}
// Example invoice template
const invoiceTemplate: DocumentTemplate = {
id: 'tpl_invoice_001',
name: 'Standard Invoice',
type: 'invoice',
format: 'pdf',
template: `
<!DOCTYPE html>
<html>
<head>
<style>
.invoice { font-family: Arial, sans-serif; }
.header { background: {{brandColor}}; color: white; padding: 20px; }
.items { margin: 20px 0; }
.total { font-weight: bold; font-size: 18px; }
</style>
</head>
<body>
<div class="invoice">
<div class="header">
<h1>{{companyName}}</h1>
<p>Invoice #{{invoiceNumber}}</p>
</div>
<div class="customer">
<h3>Bill To:</h3>
<p>{{customerName}}</p>
<p>{{customerAddress}}</p>
</div>
<div class="items">
<table>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
{{#each items}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>{{price}}</td>
<td>{{total}}</td>
</tr>
{{/each}}
</table>
</div>
<div class="total">
Total: {{currency}} {{totalAmount}}
</div>
</div>
</body>
</html>
`,
variables: [
{ name: 'companyName', type: 'string', required: true },
{ name: 'invoiceNumber', type: 'string', required: true },
{ name: 'customerName', type: 'string', required: true },
{ name: 'totalAmount', type: 'currency', required: true },
],
styling: {
brandColor: '#3B82F6',
fontSize: '14px',
fontFamily: 'Arial, sans-serif',
},
};2. Email Templates
Standardize email communications:
typescript
// src/templates/email.ts
interface EmailTemplate {
id: string;
name: string;
subject: string;
body: string;
variables: TemplateVariable[];
attachments?: AttachmentConfig[];
}
const welcomeEmailTemplate: EmailTemplate = {
id: 'email_welcome_001',
name: 'Welcome Email',
subject: 'Welcome to {{companyName}}, {{userName}}!',
body: `
<html>
<body style="font-family: Arial, sans-serif;">
<div style="max-width: 600px; margin: 0 auto;">
<h1 style="color: {{brandColor}};">Welcome, {{userName}}!</h1>
<p>Thank you for joining {{companyName}}. We're excited to have you on board!</p>
<div style="background: #f5f5f5; padding: 20px; margin: 20px 0;">
<h3>Get Started:</h3>
<ul>
<li>Complete your profile</li>
<li>Verify your email</li>
<li>Explore our features</li>
</ul>
</div>
<a href="{{dashboardUrl}}" style="background: {{brandColor}}; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">
Go to Dashboard
</a>
<p style="margin-top: 30px; color: #666;">
Need help? Contact us at {{supportEmail}}
</p>
</div>
</body>
</html>
`,
variables: [
{ name: 'userName', type: 'string', required: true },
{ name: 'companyName', type: 'string', required: true },
{ name: 'dashboardUrl', type: 'string', required: true },
{ name: 'supportEmail', type: 'string', required: true },
],
};3. Workflow Templates
Automate business processes:
typescript
// src/templates/workflow.ts
interface WorkflowTemplate {
id: string;
name: string;
trigger: WorkflowTrigger;
steps: WorkflowStep[];
conditions?: WorkflowCondition[];
}
interface WorkflowStep {
id: string;
type: 'action' | 'notification' | 'approval' | 'delay';
config: any;
onSuccess?: string; // Next step ID
onFailure?: string; // Fallback step ID
}
const paymentWorkflow: WorkflowTemplate = {
id: 'wf_payment_001',
name: 'Payment Processing Workflow',
trigger: {
event: 'transaction.created',
conditions: [
{ field: 'amount', operator: 'gte', value: 1000 },
],
},
steps: [
{
id: 'step_1',
type: 'action',
config: {
action: 'verify_payment_method',
},
onSuccess: 'step_2',
onFailure: 'step_notify_failure',
},
{
id: 'step_2',
type: 'approval',
config: {
approvers: ['finance@company.com'],
timeout: 24 * 60 * 60, // 24 hours
message: 'Payment of {{amount}} requires approval',
},
onSuccess: 'step_3',
onFailure: 'step_notify_rejection',
},
{
id: 'step_3',
type: 'action',
config: {
action: 'process_payment',
},
onSuccess: 'step_notify_success',
onFailure: 'step_notify_failure',
},
{
id: 'step_notify_success',
type: 'notification',
config: {
template: 'payment_success',
recipients: ['{{userEmail}}', 'finance@company.com'],
},
},
{
id: 'step_notify_failure',
type: 'notification',
config: {
template: 'payment_failed',
recipients: ['{{userEmail}}', 'support@company.com'],
},
},
],
};Template Management API
Create Template
http
POST /api/v1/templates
Content-Type: application/json
{
"name": "Monthly Report",
"type": "document",
"format": "pdf",
"template": "<html>...</html>",
"variables": [...]
}List Templates
http
GET /api/v1/templates?type=invoice&format=pdfGet Template
http
GET /api/v1/templates/{template_id}Update Template
http
PATCH /api/v1/templates/{template_id}
Content-Type: application/json
{
"name": "Updated Monthly Report",
"template": "<html>...</html>"
}Delete Template
http
DELETE /api/v1/templates/{template_id}Rendering Templates
Render Document
typescript
// src/services/template-renderer.ts
import Handlebars from 'handlebars';
import puppeteer from 'puppeteer';
class TemplateRenderer {
async renderDocument(
templateId: string,
data: Record<string, any>
): Promise<Buffer> {
const template = await db.templates.findById(templateId);
if (!template) {
throw new Error('Template not found');
}
// Compile template
const compiled = Handlebars.compile(template.template);
const html = compiled(data);
// Convert to PDF if needed
if (template.format === 'pdf') {
return this.htmlToPdf(html);
}
return Buffer.from(html);
}
private async htmlToPdf(html: string): Promise<Buffer> {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html);
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
top: '20px',
right: '20px',
bottom: '20px',
left: '20px',
},
});
await browser.close();
return pdf;
}
async renderEmail(
templateId: string,
data: Record<string, any>
): Promise<{ subject: string; body: string }> {
const template = await db.emailTemplates.findById(templateId);
const subjectCompiled = Handlebars.compile(template.subject);
const bodyCompiled = Handlebars.compile(template.body);
return {
subject: subjectCompiled(data),
body: bodyCompiled(data),
};
}
}
export const templateRenderer = new TemplateRenderer();
// Usage
const invoice = await templateRenderer.renderDocument('tpl_invoice_001', {
companyName: 'Pocketbook Inc.',
invoiceNumber: 'INV-2024-001',
customerName: 'John Doe',
customerAddress: '123 Main St, City, ST 12345',
items: [
{ description: 'Premium Plan', quantity: 1, price: 99.99, total: 99.99 },
],
currency: 'USD',
totalAmount: 99.99,
brandColor: '#3B82F6',
});
// Save or send invoice
await saveInvoice(invoice);Send Templated Email
typescript
// src/services/email-sender.ts
import { templateRenderer } from './template-renderer';
export async function sendTemplatedEmail(
templateId: string,
to: string,
data: Record<string, any>
) {
const { subject, body } = await templateRenderer.renderEmail(templateId, data);
await emailClient.send({
to,
subject,
html: body,
});
}
// Usage
await sendTemplatedEmail('email_welcome_001', 'user@example.com', {
userName: 'John Doe',
companyName: 'Pocketbook',
dashboardUrl: 'https://app.pocketbook.studio/dashboard',
supportEmail: 'support@pocketbook.studio',
brandColor: '#3B82F6',
});Custom Helper Functions
typescript
// src/templates/helpers.ts
import Handlebars from 'handlebars';
// Register custom helpers
Handlebars.registerHelper('formatCurrency', function (amount: number, currency: string) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount / 100);
});
Handlebars.registerHelper('formatDate', function (date: Date, format: string) {
return new Intl.DateTimeFormat('en-US', {
dateStyle: format as any,
}).format(new Date(date));
});
Handlebars.registerHelper('uppercase', function (str: string) {
return str.toUpperCase();
});
Handlebars.registerHelper('if_eq', function (a: any, b: any, options: any) {
if (a === b) {
return options.fn(this);
}
return options.inverse(this);
});
// Usage in templates
// {{formatCurrency totalAmount "USD"}}
// {{formatDate createdAt "long"}}
// {{uppercase companyName}}
// {{#if_eq status "active"}}Active{{/if_eq}}Template Versioning
typescript
// src/models/template-version.ts
interface TemplateVersion {
id: string;
templateId: string;
version: number;
template: string;
variables: TemplateVariable[];
createdAt: Date;
createdBy: string;
changelog?: string;
}
export async function createTemplateVersion(
templateId: string,
changes: Partial<DocumentTemplate>,
userId: string
): Promise<TemplateVersion> {
const currentTemplate = await db.templates.findById(templateId);
const latestVersion = await db.templateVersions.findOne(
{ templateId },
{ sort: { version: -1 } }
);
const newVersion: TemplateVersion = {
id: crypto.randomUUID(),
templateId,
version: (latestVersion?.version || 0) + 1,
template: changes.template || currentTemplate.template,
variables: changes.variables || currentTemplate.variables,
createdAt: new Date(),
createdBy: userId,
changelog: changes.changelog,
};
await db.templateVersions.create(newVersion);
await db.templates.update(templateId, changes);
return newVersion;
}
// Rollback to previous version
export async function rollbackTemplate(
templateId: string,
version: number
): Promise<void> {
const targetVersion = await db.templateVersions.findOne({
templateId,
version,
});
if (!targetVersion) {
throw new Error('Version not found');
}
await db.templates.update(templateId, {
template: targetVersion.template,
variables: targetVersion.variables,
});
}Template Testing
typescript
// src/services/template-tester.ts
export async function testTemplate(
templateId: string,
testData: Record<string, any>
): Promise<{ success: boolean; output?: string; errors?: string[] }> {
try {
const output = await templateRenderer.renderDocument(templateId, testData);
return {
success: true,
output: output.toString('base64'),
};
} catch (error) {
return {
success: false,
errors: [error.message],
};
}
}
// Validate template syntax
export function validateTemplate(template: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
try {
Handlebars.compile(template);
} catch (error) {
errors.push(`Syntax error: ${error.message}`);
}
// Check for required variables
const variablePattern = /\{\{([^}]+)\}\}/g;
const matches = template.match(variablePattern) || [];
return {
valid: errors.length === 0,
errors,
};
}Template Marketplace
typescript
// Pre-built templates available for all users
export const templateMarketplace = [
{
id: 'marketplace_invoice_modern',
name: 'Modern Invoice',
category: 'invoice',
preview: '/previews/modern-invoice.png',
description: 'Clean, modern invoice template',
price: 0, // Free
},
{
id: 'marketplace_receipt_minimal',
name: 'Minimal Receipt',
category: 'receipt',
preview: '/previews/minimal-receipt.png',
description: 'Simple, minimal receipt template',
price: 0,
},
{
id: 'marketplace_report_executive',
name: 'Executive Report',
category: 'report',
preview: '/previews/executive-report.png',
description: 'Professional executive report template',
price: 9.99,
},
];
// Install template from marketplace
export async function installMarketplaceTemplate(
marketplaceId: string,
userId: string
): Promise<string> {
const marketplaceTemplate = templateMarketplace.find(
(t) => t.id === marketplaceId
);
if (!marketplaceTemplate) {
throw new Error('Template not found in marketplace');
}
// Create copy for user
const userTemplate = await db.templates.create({
...marketplaceTemplate,
id: crypto.randomUUID(),
userId,
isMarketplace: false,
createdAt: new Date(),
});
return userTemplate.id;
}Access Control
typescript
// src/middleware/template-access.ts
export async function checkTemplateAccess(
userId: string,
templateId: string,
action: 'read' | 'write' | 'delete'
): Promise<boolean> {
const template = await db.templates.findById(templateId);
if (!template) {
return false;
}
// Check ownership
if (template.userId === userId) {
return true;
}
// Check team access
const teamAccess = await db.templateAccess.findOne({
templateId,
userId,
});
if (teamAccess && teamAccess.permissions.includes(action)) {
return true;
}
return false;
}
// Share template with team
export async function shareTemplate(
templateId: string,
userIds: string[],
permissions: ('read' | 'write' | 'delete')[]
): Promise<void> {
for (const userId of userIds) {
await db.templateAccess.upsert(
{ templateId, userId },
{ permissions, sharedAt: new Date() }
);
}
}Best Practices
- Version control: Always version templates for rollback capability
- Test thoroughly: Test templates with various data sets
- Use variables: Make templates reusable with variables
- Validate input: Ensure all required variables are provided
- Optimize performance: Cache compiled templates
- Security: Sanitize user input in templates
- Documentation: Document all template variables
- Branding consistency: Use consistent styling across templates
- Mobile-friendly: Ensure email templates are responsive
- A/B testing: Test different template versions
Performance Optimization
typescript
// Cache compiled templates
import LRU from 'lru-cache';
const templateCache = new LRU<string, HandlebarsTemplateDelegate>({
max: 100,
ttl: 1000 * 60 * 60, // 1 hour
});
export function getCompiledTemplate(template: string): HandlebarsTemplateDelegate {
const cacheKey = crypto.createHash('sha256').update(template).digest('hex');
let compiled = templateCache.get(cacheKey);
if (!compiled) {
compiled = Handlebars.compile(template);
templateCache.set(cacheKey, compiled);
}
return compiled;
}Next Steps
- API Keys - Manage API access
- Billing System - Configure billing
- Webhooks - Automate with webhooks
- Email Configuration - Email delivery setup
