From odoo-crm-toolkit
Create QWeb email templates for Odoo 18 mailing campaigns via XML-RPC API. Use this skill when the user asks to "create email template", "design email for mailing", "make newsletter template", "email template for campaign", "QWeb email template", "vytvoř emailovou šablonu", "navrhni email pro kampaň", "šablona pro mailing", "QWeb šablona emailu", "design emailu", "vytvoř newsletter šablonu", or any request involving creating email templates for Odoo 18 mailing.mailing campaigns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/odoo-crm-toolkit:email-templatesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create professional email templates for mailing campaigns in Odoo 18.
Create professional email templates for mailing campaigns in Odoo 18.
Before executing any API calls, read the shared reference at ${CLAUDE_PLUGIN_ROOT}/references/xmlrpc-api.md.
Same environment variables: ODOO_URL, ODOO_DB, ODOO_API_KEY (UID se zjistí automaticky)
For one-off campaigns. HTML is stored directly in the mailing body.
For templates that can be reused across multiple mailings. Supports Jinja2 placeholders.
For complex templates rendered server-side. Most powerful but requires more setup.
The body_html field accepts full HTML. Design emails with inline CSS (email clients don't support external stylesheets or <style> blocks reliably).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body style="margin: 0; padding: 0; background-color: #f5f5f5; font-family: Arial, Helvetica, sans-serif;">
<!-- Wrapper table for email clients -->
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f5f5f5;">
<tr>
<td align="center" style="padding: 20px 0;">
<!-- Content container -->
<table role="presentation" cellpadding="0" cellspacing="0" width="600" style="background-color: #ffffff; border-radius: 8px; overflow: hidden;">
<!-- Header -->
<tr>
<td style="padding: 30px 40px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);">
<h1 style="margin: 0; color: #ffffff; font-size: 22px; font-weight: 600;">
Michal Varyš
</h1>
<p style="margin: 4px 0 0; color: #a0aec0; font-size: 13px;">
Web Design & Development
</p>
</td>
</tr>
<!-- Hero Section -->
<tr>
<td style="padding: 40px;">
<h2 style="margin: 0 0 16px; color: #1a1a2e; font-size: 24px; line-height: 1.3;">
Headline Text
</h2>
<p style="margin: 0 0 24px; color: #4a5568; font-size: 16px; line-height: 1.6;">
Main message paragraph with key value proposition.
</p>
<!-- CTA Button -->
<table role="presentation" cellpadding="0" cellspacing="0">
<tr>
<td style="border-radius: 8px; background-color: #2563eb;">
<a href="https://michalvarys.eu/page"
style="display: inline-block; padding: 14px 28px;
color: #ffffff; text-decoration: none;
font-weight: 600; font-size: 16px;">
Zobrazit nabídku
</a>
</td>
</tr>
</table>
</td>
</tr>
<!-- Content Sections -->
<tr>
<td style="padding: 0 40px 40px;">
<!-- Feature list or content blocks -->
<table role="presentation" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td style="padding: 16px; background: #f8fafc; border-radius: 8px; margin-bottom: 12px;">
<h3 style="margin: 0 0 8px; color: #1a1a2e; font-size: 16px;">Feature Title</h3>
<p style="margin: 0; color: #4a5568; font-size: 14px;">Feature description.</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- Pricing Section -->
<tr>
<td style="padding: 30px 40px; background: #f8fafc; border-top: 1px solid #e2e8f0;">
<h3 style="margin: 0 0 12px; color: #1a1a2e; font-size: 18px;">Ceník</h3>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td style="padding: 8px 0; color: #4a5568; font-size: 14px;">Web, server, design, SSL</td>
<td align="right" style="padding: 8px 0; color: #1a1a2e; font-weight: 600;">od 890 Kč/měsíc</td>
</tr>
</table>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="padding: 24px 40px; background: #1a1a2e; text-align: center;">
<p style="margin: 0 0 8px; color: #a0aec0; font-size: 12px;">
Michal Varyš | <a href="https://michalvarys.eu" style="color: #60a5fa; text-decoration: none;">michalvarys.eu</a>
</p>
<p style="margin: 0; color: #718096; font-size: 11px;">
<a href="https://www.michalvarys.eu/mailing/{MAILING_ID}/unsubscribe" style="color: #718096; text-decoration: underline;">Odhlásit se</a>
|
<a href="https://www.michalvarys.eu/mailing/{MAILING_ID}/view" style="color: #718096; text-decoration: underline;">Otevřít v prohlížeči</a>
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
When setting body_arch, the HTML MUST follow Odoo's mailing editor structure so blocks are editable/draggable in the Odoo editor. Without these classes, blocks appear greyed out with "this block cannot be dropped anywhere".
<div class="o_layout oe_unremovable oe_unmovable bg-200" data-name="Mailing"
style="background-color: #f5f5f5; padding: 0;">
<div class="container o_mail_wrapper o_mail_regular oe_unremovable"
style="max-width: 600px; margin: 0 auto;">
<div class="row">
<div class="col o_mail_no_options o_mail_wrapper_td oe_structure"
style="padding: 0;">
<!-- EDITABLE BLOCKS GO HERE -->
</div>
</div>
</div>
</div>
Every content block MUST have class o_mail_snippet_general plus a block-type class:
s_text_block — text paragraph/headings_call_to_action — CTA button sections_three_columns — 3-column layouts_cover — hero/cover with background images_features — feature cards with iconss_image_text — image + text side by sideAdd as the FIRST block inside the structure.
CRITICAL: NEVER use t-att-href or any QWeb directives in body_html/body_arch! QWeb directives only work in ir.ui.view templates, NOT in mailing body. Using them causes "Failed to render QWeb template" error and the email won't send.
Use plain href with the mailing ID (known after create()):
<div class="o_snippet_view_in_browser o_mail_snippet_general"
style="text-align: center; padding: 8px; font-size: 12px;">
<a href="https://www.michalvarys.eu/mailing/{MAILING_ID}/view"
style="color: #999; text-decoration: underline;">
Zobrazit v prohlížeči
</a>
</div>
<div class="s_text_block o_mail_snippet_general" style="padding: 24px 40px;">
<div class="container s_allow_columns">
<div class="row">
<div class="col-lg-12">
<h2 style="color: #1a1a2e; font-size: 22px;">Editable Heading</h2>
<p style="color: #4a5568; font-size: 15px; line-height: 1.6;">
Editable paragraph text...
</p>
</div>
</div>
</div>
</div>
<div class="s_call_to_action o_mail_snippet_general o_cc o_cc3"
style="padding: 24px 40px; text-align: center;">
<div class="container s_allow_columns">
<a href="https://michalvarys.eu/page"
class="btn btn-primary btn-lg"
style="display: inline-block; padding: 14px 28px; background: #2563eb;
color: #fff; text-decoration: none; border-radius: 8px;
font-weight: 600;">
Zobrazit nabídku
</a>
</div>
</div>
<div class="o_mail_snippet_general" data-name="Footer"
style="padding: 20px 40px; text-align: center; background: #1a1a2e;">
<p style="color: #a0aec0; font-size: 12px; margin: 0 0 8px;">
Michal Varyš | michalvarys.eu
</p>
<p style="color: #718096; font-size: 11px; margin: 0;">
<a href="https://www.michalvarys.eu/mailing/{MAILING_ID}/unsubscribe"
style="color: #718096; text-decoration: underline;">Odhlásit se</a>
</p>
</div>
models.execute_kw(DB, UID, KEY, 'mailing.mailing', 'write', [[mailing_id], {
'body_arch': email_with_odoo_classes, # Editor version with o_mail_snippet_general
'body_html': email_with_odoo_classes, # Same HTML for sending
}])
Email clients strip <style> tags. ALL styling must be inline.
Use <table role="presentation"> for layout. Flexbox and Grid don't work in email.
Standard email width. Use a centered wrapper table.
Always use https://michalvarys.eu/web/image/... — never relative paths.
Stick to system fonts: Arial, Helvetica, Georgia, Times New Roman. Web fonts are unreliable in email.
color on text (don't rely on default black)background-color explicitly on white backgroundsAvailable in mail.template context:
{{ object.name }} — contact/partner name{{ object.email }} — contact email{{ object.company_name }} — company name{{ object.partner_id.name }} — linked partner namemailing_model_id = models.execute_kw(DB, UID, KEY, 'ir.model', 'search', [
[['model', '=', 'mailing.contact']]
])[0]
mailing_id = models.execute_kw(DB, UID, KEY, 'mailing.mailing', 'create', [{
'subject': 'Email Subject',
'mailing_type': 'mail',
'body_html': email_html_content,
'mailing_model_id': mailing_model_id,
'contact_list_ids': [(6, 0, [list_id])],
'state': 'draft',
'email_from': 'Michal Varyš <[email protected]>',
'reply_to': '[email protected]',
'keep_archives': True,
}])
href (NO t-att-href!){ODOO_URL}/web#id={mailing_id}&model=mailing.mailing&view_type=formSince MAILING_ID is only known after create(), use a two-step process:
# Step 1: Create mailing with placeholder links
mailing_id = models.execute_kw(DB, UID, KEY, 'mailing.mailing', 'create', [{
'subject': 'Subject',
'mailing_type': 'mail',
'body_html': email_html, # use {MAILING_ID} placeholder
'body_arch': email_html,
'state': 'draft',
'email_from': 'Michal Varyš <[email protected]>',
...
}])
# Step 2: Replace placeholder with actual ID
for field in ['body_html', 'body_arch']:
content = models.execute_kw(DB, UID, KEY, 'mailing.mailing', 'read',
[[mailing_id]], {'fields': [field]})[0][field]
if '{MAILING_ID}' in content:
content = content.replace('{MAILING_ID}', str(mailing_id))
models.execute_kw(DB, UID, KEY, 'mailing.mailing', 'write',
[[mailing_id], {field: content}])
npx claudepluginhub michalvarys/claude-plugins --plugin odoo-crm-toolkitCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.