drin-build-template
Build a transactional HTML email that renders correctly across Gmail, Outlook, and Apple Mail, with a plain-text alternative and dark-mode support. Use when writing or editing the …
IRONBEEM
@atom00blue
What This Skill Does
Builds transactional HTML emails that render correctly across Gmail, Outlook, and Apple Mail, with a plain-text alternative and dark-mode support. Uses table-based layouts, inlined styles, bulletproof buttons, preheaders, and Drin's {{handlebars}} variable/section syntax.
Replaces manually testing email rendering across multiple clients by providing a battle-tested skeleton and rules that handle the quirks of Outlook, Gmail, and Apple Mail.
When to Use It
- Create a transactional email for a password reset or order confirmation
- Add a bulletproof call-to-action button that works in Outlook
- Include a plain-text alternative alongside your HTML email body
- Set up dark-mode styles that survive email client auto-inversion
- Insert a preheader to control the inbox preview line
- Build a reusable email template with Drin variables for dynamic content
Install
$ openclaw skills install @atom00blue/drin-build-templateBuilding cross-client transactional email
Email clients are not browsers. Outlook uses Word to render HTML; Gmail strips
<style> in some contexts and clips messages over ~102KB; many clients block
external CSS and JS. Build defensively.
Hard rules
- Layout with tables, not flexbox/grid. Use nested
<table>withrole="presentation",cellpadding="0" cellspacing="0" border="0". - Inline every style (
style="…"on elements). Treat<style>blocks as a progressive enhancement only (use for@mediadark mode / mobile). - Fixed width ≤ 600px for the main column; make it fluid on mobile.
- Always ship a plain-text part. Never send HTML-only — it hurts
deliverability and breaks for text-only readers. Drin takes
textalongsidehtml. - A preheader: a hidden span right after
<body>that sets the inbox preview line. - Bulletproof buttons: a styled
<a>inside a padded table cell (not a<button>), with VML for Outlook if you need a filled button there. - Alt text on every image; assume images are blocked by default. Don't put critical content only in an image.
- No JavaScript, no
<form>you depend on, no web fonts you depend on (provide a system-font fallback stack). - Dark mode: set
color-scheme/supported-color-schemes: light darkand provide@media (prefers-color-scheme: dark)overrides; don't rely on them — pick colors that survive auto-inversion.
Starter skeleton
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<title>{{subject | default:"Notification"}}</title>
<style>
@media (prefers-color-scheme: dark) {
.bg { background:#0b0b0c !important; }
.card { background:#161617 !important; }
.text { color:#e7e7e7 !important; }
.muted { color:#a1a1a1 !important; }
}
</style>
</head>
<body class="bg" style="margin:0; padding:0; background:#f4f4f5;">
<!-- preheader: shown in the inbox preview, hidden in the body -->
<div style="display:none; max-height:0; overflow:hidden; opacity:0;">
{{preheader | default:"A quick update from {{brandName}}"}}
</div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" style="padding:24px 12px;">
<table role="presentation" class="card" width="600" cellpadding="0" cellspacing="0" border="0"
style="max-width:600px; width:100%; background:#ffffff; border-radius:12px;">
<tr>
<td class="text" style="padding:32px; font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif; font-size:15px; line-height:1.6; color:#18181b;">
<h1 style="margin:0 0 16px; font-size:20px;">Hi {{firstName | default:"there"}},</h1>
<p style="margin:0 0 24px;">{{body}}</p>
<!-- bulletproof button -->
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center" bgcolor="#2563eb" style="border-radius:8px;">
<a href="{{ctaUrl}}" style="display:inline-block; padding:12px 22px; font-family:Arial,sans-serif; font-size:15px; color:#ffffff; text-decoration:none;">
{{ctaLabel | default:"Open"}}
</a>
</td>
</tr>
</table>
<p class="muted" style="margin:24px 0 0; font-size:12px; color:#71717a;">
Sent by {{brandName}}. {{#if unsubscribeUrl}}<a href="{{unsubscribeUrl}}" style="color:#71717a;">Unsubscribe</a>.{{/if}}
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
Always produce a matching plain-text version, e.g.:
Hi {{firstName | default:"there"}},
{{body}}
{{ctaLabel | default:"Open"}}: {{ctaUrl}}
Sent by {{brandName}}.
React Email (TypeScript)
If the project sends from TypeScript, prefer react-email components and render with the Drin SDK helper:
import { sendReactEmail } from "@drin00/sdk/react-email";
await sendReactEmail(drin, { from, to, subject, react: <Welcome name="Ada" /> });
// renders html + text, then sends
Drin's {{handlebars}} template syntax
When you save a reusable template in Drin (create_template / POST /v1/templates
/ dashboard), the engine is a safe handlebars subset — no arbitrary code,
purpose-built for email:
{{ path }}— HTML-escaped interpolation (use in the HTML body).{{{ path }}}or{{& path }}— raw/unescaped (use sparingly, e.g. pre-sanitized HTML).{{ path | default:"x" }},{{ path | upper }},{{ path | lower }}— filters.{{#if path}} … {{else}} … {{/if}},{{#unless path}} … {{/unless}}.{{#each items}} … {{this}} / {{@index}} / {{@first}} / {{@last}} … {{else}} … {{/each}}.{{../x}}— parent scope;{{! comment }}— stripped.
Missing variables render as empty string and are reported. Before sending, call
render_template (a saved template) or preview_template (an unsaved draft;
templates.preview in the SDK) and check the missing array — fix any required
variable that resolved to empty. The subject and text parts render
unescaped; the html part is escaped by default.
Top skills in this category
PollyReach
@pollyreachPollyReach gives every AI agent a phone number and the ability to get things done over the phone — finding contacts, making calls, and completing tasks. Just...
Task Status
@mightyprime1Send short status descriptions in chat for long-running tasks. Use when you need to provide periodic updates during multi-step operations, confirm task completion, or notify of failures. Includes automated periodic monitoring that sends updates every 5 seconds, status message templates, and a helper function for consistent status reporting.
Cost Tracking for Models
@vincentqiuTrack OpenClaw usage costs and provide detailed reports by date and model. Supports daily, weekly, and monthly report formats for Discord and other messaging channels.
Proactive Tasks
@imrkhn03Proactive goal and task management system. Use when managing goals, breaking down projects into tasks, tracking progress, or working autonomously on objectives. Enables agents to work proactively during heartbeats, message humans with updates, and make progress without waiting for prompts.
AI Proposal Generator
@jeffjhunterGenerate professional HTML proposals from meeting notes. Features 5 proposal styles (Corporate, Entrepreneur, Creative, Consultant, Minimal), 6+ color themes, and a Design Wizard for custom templates. Triggers on "create proposal", "proposal for [client]", "proposal wizard", "proposal from [notes]", "show proposal styles", "finalize proposal". Integrates with ai-meeting-notes for context. Outputs beautiful, responsive HTML ready to send or export as PDF.