diff --git a/components/email/__tests__/composer-island-sanitize.test.ts b/components/email/__tests__/composer-island-sanitize.test.ts
new file mode 100644
index 000000000..e297be0af
--- /dev/null
+++ b/components/email/__tests__/composer-island-sanitize.test.ts
@@ -0,0 +1,64 @@
+import { afterEach, describe, expect, it } from 'vitest';
+import { Editor } from '@tiptap/core';
+import Document from '@tiptap/extension-document';
+import Paragraph from '@tiptap/extension-paragraph';
+import Text from '@tiptap/extension-text';
+import type { Editor as ReactEditor } from '@tiptap/react';
+
+import { QuotedHtml, serializeEditorContent } from '../quoted-html';
+import { SignatureBlock, SIGNATURE_BLOCK_MARKER } from '../signature-block';
+
+// A re-opened draft hands its stored HTML to the editor as is, and the quote
+// and signature islands copy their inner HTML verbatim into the app document.
+// A draft written by another client, or by another member of a shared
+// mailbox, could carry event handlers that then ran in the webmail origin.
+
+const PAYLOAD = 'l
Quoted original
'; + +function mount(html: string): Editor { + return new Editor({ extensions: [Document, Paragraph, Text, QuotedHtml, SignatureBlock], content: html }); +} + +function islandHtml(editor: Editor, selector: string): string { + const host = editor.view.dom.querySelector(selector) as HTMLElement; + return host.shadowRoot!.innerHTML; +} + +function expectInert(html: string) { + expect(html).not.toMatch(/onerror/i); + expect(html).not.toMatch(/javascript:/i); + expect(html).not.toMatch(/