{"id":66365,"plugin_id":"plugin_asdk_app_6a8d9699480c8191b355912591c99178","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-28T12:06:05.602Z","digest":"1f9b1d4d34335ce65c9c1c66d5473052546d7ab2675224763e8ea0d752d9fb4b","against":null,"payload":{"description":"Checks Brevo email HTML against Brevo's template rules and fixes what fails. Use when email HTML was just written or changed, before a campaign or template is created from it, or when the user asks to check, review or debug an email template (broken personalization, a variable that shows empty, a template that won't save). Goes through a fixed checklist - template syntax, contact attributes that exist in the account, params only where they work, absolute https images, size, unsubscribe link - fixes failures by editing only the affected lines, at most 3 rounds, and reports honestly that the check was done by hand.","included_files":[],"name":"email-template-check","skill_md_contents":"---\nname: email-template-check\ndescription: Checks Brevo email HTML against Brevo's template rules and fixes what fails. Use when email HTML was just written or changed, before a campaign or template is created from it, or when the user asks to check, review or debug an email template (broken personalization, a variable that shows empty, a template that won't save). Goes through a fixed checklist - template syntax, contact attributes that exist in the account, params only where they work, absolute https images, size, unsubscribe link - fixes failures by editing only the affected lines, at most 3 rounds, and reports honestly that the check was done by hand.\nmetadata:\n  version: 1.0.0\n  status: beta\n  author: brevo\n---\n# Check an email template\n\nChecks one email's HTML and template language against Brevo's rules, fixes what fails, and\nreports the result in one line. Brevo has no automatic template check through the\nconnection, so this is a checklist you apply yourself, and the report says so. It never\nsaves or sends anything.\n\n| Step | Tool |\n|---|---|\n| The account's contact attribute names | `attributes_get_attributes` |\n| An existing contact email for a preview (the account's own email) | `accounts_get_account` |\n| Read a saved template to check it, one at a time | `templates_get_smtp_template` |\n| Render a saved template on Brevo's servers (step 4) | `templates_post_preview_smtp_email_templates` |\n\nIf a tool a step needs isn't in your tool list, stop that step and tell the user their Brevo\nconnection doesn't offer it (connection rules below).\n\n## Brevo connection rules\n\n- Every Brevo call goes through the Brevo MCP server's tools. The user's connection carries\n  their authentication (OAuth). **Never ask for an API key, never ask the user to paste a key\n  into the chat, and never put a key in a file or command.**\n- If a tool returns 401, or 403 about the login or token, ask the user to reconnect Brevo in\n  their agent's settings.\n- A 403 \"required scope not present\" (`permission_denied`) means this connection doesn't\n  allow that action; reconnecting won't change it. Say so, skip that step, and carry on with\n  the fallback the skill gives (for a template render: the test send and its outcome check).\n  If the skill gives no fallback for that step, stop the step and tell the user.\n- If a tool a step needs is missing from your tool list, stop that step and say that the\n  user's Brevo connection doesn't offer it (connections can differ). Don't improvise with\n  other tools. Writing HTML never needs a tool; offer it, and the steps to finish in Brevo.\n- On a sub-account, pass `subaccount_id` on every call: every Brevo tool takes it per call,\n  and a call without it acts on the main account.\n- Show a tool's error message as returned, after a plain-language summary. Don't invent a\n  cause the tool didn't give.\n\n## Where this skill comes from\n\n- You already have this skill: it is in your context, whether installed, from the plugin, uploaded\n  or fetched with `get_brevo_guide`. Don't call `get_brevo_guide` for it again in this\n  conversation.\n- To have it without a tool call next time, install it once: the Brevo plugin from\n  `getbrevo/brevo-skills`, `npx skills add getbrevo/brevo-skills`, or, with a shell, the Brevo CLI\n  (`npm i -g @getbrevo/cli`, then `brevo skill install`). `brevo skill check` lists copies that are\n  behind.\n- Once per conversation, call `list_brevo_guides` with `installed` set to the version in the\n  `metadata` of each Brevo skill you have, if the tool accepts `installed`; otherwise call it\n  with no arguments and compare the versions yourself. For a `stale` one, follow its `update`\n  hint.\n- If you don't have the skill, call `get_brevo_guide` with its name: it returns the current text.\n\n## Working rules for every step\n\n- Follow the steps in order. Don't skip, merge or reorder them, and don't add steps this skill\n  doesn't have.\n- Use only the tools this skill names, with the arguments it describes. Never invent a tool, a\n  parameter, an ID or an email address; ask the user instead, one question at a time.\n- Before anything is created, changed or sent, say in plain words what will happen and wait for\n  the user's yes. A yes covers only what you described.\n- Read each Brevo object once per conversation and reuse it. Re-read only after something\n  changed it.\n- Report a result only from what the tool returned. If a call failed or returned nothing,\n  say so; never describe it as done.\n- Reply in the user's language, and keep names, IDs and addresses exactly as Brevo shows them.\n\n## Conversation cache (every email skill)\n\nKeep what you read from Brevo in this conversation and reuse it, instead of calling the same\ntool again. The cache is your own notes in the conversation, never a file. Key every entry by\naccount **and** sub-account ID (none for the main account), and note when you fetched it\n(`fetchedAt`). Never show or reuse an entry from another account or sub-account.\n\n| What | Keep it | Fetch again when |\n|---|---|---|\n| Account (company, address, own email), senders, contact attributes | About 10 minutes | Time is up. An attribute in the email isn't in the cached list and the cache is more than about a minute old: fetch the attributes once more before reporting it (the user may have just added it). The user switches account or sub-account, or says they changed it in Brevo |\n| Sender domains (whether each is authenticated) | Same as senders | Same as senders |\n| Brand kit | The whole conversation (one read) | The user says they changed it in Brevo, or switches account. After this conversation's own save, update the cache with the approved values; don't read it back |\n| Templates this conversation created or read | The whole conversation | The user says they edited it in Brevo's editor (fetch it before a preview or test). After this conversation's own update, apply the change to the cache |\n| Lists and segments | Not cached | Fetch when needed |\n\n- **A failed fetch never blocks.** Skip the check that needed it, say so in one line (\"I\n  couldn't read your contact attributes, so that check was skipped\"), and go on.\n- **Errors:** a 5xx is retried once, then the user is told it is on Brevo's side. A 404 on a\n  cached template: drop it from the cache and tell the user it no longer exists. A 429 on\n  the brand kit: keep the cached kit and say the hourly limit is reached.\n- **\"What do you have cached?\"** List each entry (what, account, when fetched) without\n  secrets. **\"Refresh\"** clears everything; fetch again as each step needs it.\n- If you can't tell how much time has passed, fetch the 10-minute items again when the user\n  starts a new email.\n- Never keep API keys, tokens or passwords in the cache or anywhere in the conversation.\n\n**Call budget** for a transactional template (write, check, save, preview, test): about 11\ncalls with the cache (account, senders, attributes, brand kit, save, 3 sample renders,\nsender domains, test send, outcome) against about 16 without it (the account read 3 times,\nattributes and senders twice, and the template read back). Each fix round that re-checks\nattributes would add one more call without it.\n\n## 1. Collect the inputs\n\n- **The HTML.** From the conversation, or a saved template: fetch it by ID with\n  `templates_get_smtp_template` (or the cached copy). Never list all templates to find one: a template list\n  returns the full HTML of every template. Ask the user for the ID or the name; if you must\n  list, ask for one result at a time.\n- **The type:** campaign, automation email or transactional. It decides which variables work\n  and whether a missing unsubscribe link is an error.\n- **Attribute names:** from the cache, else call `attributes_get_attributes` once. If the\n  fetch fails, skip item 6 with a warning and go on. Without a connection, use\n  `FIRSTNAME`, `LASTNAME`, `EMAIL` plus the names the user gave, and say the attribute check\n  used assumed names.\n- **A sample payload** (automation and transactional only): from the user, or the one written\n  with the email. Without one, `params.*` paths can't be checked; say so.\n\n## 2. Go through the checklist\n\nCheck every item against the HTML. Note each failure with what the user sees (\"the greeting\nin the first paragraph\"), not by line number alone.\n\nItems 1, 2, 4, 5 and 15 are about template tags and visible markup: skip the CSS inside\n`<style>` for them (item 3 still applies there).\nThe skeleton's own rules (`-webkit-text-size-adjust:100%}`, `width:100%!important}}` in a\nmedia query, `img{border:0}`) are not stray `}}` or borders without a colour.\n\n**Template syntax**\n\n1. Every `{{` has its `}}` and every `{%` its `%}`; no single-brace closings, no stray `}}`.\n2. Each `if` has its `endif`, each `for` its `endfor`; `elif` and `else` sit inside an `if`,\n   `empty` inside a `for`. **Never `{% else %}` inside a `{% for %}`**: use `{% empty %}`.\n3. No `{#` anywhere (it opens a template comment), including in CSS and text. No template\n   tags inside HTML comments.\n4. Inside tags: straight quotes only (no curly quotes), no HTML (`{{ contact.<b>NAME</b> }}`),\n   no HTML entities (`&quot;`), no URL-encoded braces (`%7B%7B`).\n5. Only Brevo filters, in the colon form: `| default:\"there\"`. No `default(\"x\")`, `raw` or\n   `number_format`. No arithmetic: every amount is a field in the data.\n\n**Variables**\n\n6. Every `contact.X` is an attribute returned by `attributes_get_attributes`, written exactly\n   (upper case). List every `contact.X` in the HTML and the subject, and compare each one\n   against that list, letter by letter. A name missing from a cache older than about a\n   minute: fetch the attributes once more before reporting it. A misspelt name (`FIRST_NAME`\n   for `FIRSTNAME`) saves and sends without any error and silently shows its default, so this\n   check is the only one that catches it. Suggest the closest name; never keep it silently.\n7. Every `contact.X` shown in text has `| default:\"…\"` or sits inside `{% if contact.X %}`.\n8. `params.*` only in automation and transactional emails; **in a campaign it is always\n   empty**. Every `params` path exists in the sample payload. Read the email once with the\n   sample, and once with empty lists and missing optional keys: an optional key is used only\n   inside an `{% if %}` that tests it. A filter that needs a value (`time_parse`,\n   `time_parse_rfc3339`, `date`, `date_i18n` or `floatformat` on a param) always sits inside\n   `{% if params.x %}`: on an empty value it can fail the whole email, and test sends have no\n   params.\n9. No `{{ company.* }}` or `{{ label.* }}`: they render blank in sent email. The company name\n   and address are plain text.\n10. No other tools' merge tags (`*|FNAME|*`, `%%FirstName%%`, `{{ first_name }}`). List any\n    you find with the Brevo equivalent and ask before replacing.\n\n**Footer and action**\n\n11. `{{ unsubscribe }}` link present: required in automation emails (no footer is added) and\n    in campaigns (otherwise Brevo appends its default footer: a warning, not an error).\n    Purely transactional mail (receipt, password reset) doesn't need one.\n12. At least one call-to-action link. No `javascript:` links, no spaces inside an `href`.\n\n**Email HTML**\n\n13. Images: absolute `https://` URLs (no relative paths, no `http://`), an `alt` on each, a\n    `width` attribute; no SVG or WEBP; each image URL used once (the logo excepted). An empty\n    `src=\"\"` is a placeholder: list it for the user.\n14. No `<script>`, `<iframe>`, `<form>`, `<video>`, `<audio>`, `<object>`, `<embed>`, `<svg>`;\n    no `display:flex`, `grid` or `position`.\n15. Every border has a colour (Outlook draws a border without one in black).\n16. A doctype and a viewport meta tag.\n17. Size under 100 KB (Gmail clips the email at 102 KB).\n\n**Drag-and-drop output only** (the HTML will be converted into an editable design)\n\n18. Every `{% if %}` and `{% for %}` with its branches sits inside ONE `<p>` or heading.\n    Between section rows, between the rows of a nested table, or across cells, it is lost on\n    conversion.\n19. No `<ul>`, `<ol>`, `<li>` (their content is dropped).\n20. Every section cell sets all four padding sides; the hidden preheader text is also passed\n    as `previewText` (the `<div>` is dropped on conversion).\n\n**Subject line** (the template's subject, or the `<title>` of the HTML)\n\n21. Same syntax rules as the body: closed tags, colon-form filters, straight quotes.\n    `contact.X` only with attributes that exist, with a default.\n22. `params.*` only in a transactional or automation subject, never in a campaign's (it is\n    always empty there). Word the subject so it still reads well with the value blank\n    (\"Your order is confirmed\", not \"Order  confirmed\"): a test send has no params, so the\n    test's subject shows it empty.\n\n## 3. Fix and check again\n\n- Fix every failure by editing only the affected lines, never by rewriting the whole email.\n- Never delete content the user asked for to make a check pass. When a fix needs a decision\n  (an attribute that doesn't exist, a missing payload field), ask.\n- Go through the checklist again. **Stop after 3 rounds** and show the user what is still\n  wrong, with your suggestion for each.\n\n## 4. Preview a saved template\n\nA saved template is rendered on Brevo's servers with\n`templates_post_preview_smtp_email_templates`, which catches template errors the checklist\ncan miss:\n\n- Pass `templateId` and `email` of an **existing contact**: the account's own email (cached\n  from `accounts_get_account`) works. For automation and transactional templates, also pass the\n  sample `params`. With `params` alone (no `email`), the preview fails with 400 \"Contact not\n  found\" whenever the template uses `contact.*` tags.\n- The unsubscribe link renders with an empty `href` in a preview. That is expected, not a\n  failure: check that `{{ unsubscribe }}` is in the HTML instead.\n- Render three times: with the full sample; with empty lists and missing optional keys; and\n  with `params: {}`. The last one is what a test send renders: **test sends carry no params**,\n  so every `params` line and `params` in the subject come out blank, and a filter that needs\n  a value (`time_parse` on an empty date) fails the whole email. Wrap such lines in\n  `{% if params.x %}...{% endif %}` until the `params: {}` render passes.\n- An error in the response is a real failure: fix it and check again (it counts as a round).\n- Don't paste the rendered HTML into the chat; say whether it rendered, and quote any error.\n\nHTML that isn't saved yet has no server render: skip this step and say so in the report.\n\n## 5. Report in one line\n\nBe honest about what ran. For example:\n\n- \"Checked by hand against Brevo's rules (no automatic validation was run): 0 problems left;\n  fixed 2 (greeting without a default, image without alt).\"\n- \"Checked by hand against Brevo's rules (no automatic validation was run): 1 problem left:\n  `contact.TIER` isn't an attribute in your account. Use `contact.VIP`, or create the\n  attribute in Brevo?\"\n\nAdd \"Rendered on Brevo's servers: no errors\" only when step 4 ran. Then offer a test send\n(`email-test-preview`) as the real check in an inbox.\n\n## Requirements\n\n`templates_post_preview_smtp_email_templates` renders a saved template with a contact and\nsample `params` (step 4). There is no automatic template-validation tool on the Brevo MCP\nserver: this skill is a checklist, and its report always says no automatic validation was\nrun.\n\n## Output rules\n\n- Do one thing per request: create one template, one campaign, one list. Never create a\n  second object to \"fix\" the first without the user's go-ahead.\n- Don't paste large inputs or outputs (HTML, design JSON, full reports) into the chat\n  unless the user asks. Summarize them.\n- Describe problems by what the user sees (\"the 'Shop the sale' button\"), never by\n  selectors, block types or internal field names.\n- Always end with the object's name, its ID, a link to it in Brevo, and what the user\n  still has to do there.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}