diff --git a/components/email/__tests__/composer-send-pipeline.test.tsx b/components/email/__tests__/composer-send-pipeline.test.tsx
index 075421c0e..ac68f45e5 100644
--- a/components/email/__tests__/composer-send-pipeline.test.tsx
+++ b/components/email/__tests__/composer-send-pipeline.test.tsx
@@ -3,6 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import React from 'react';
import { EmailComposer } from '../email-composer';
import { registerOutgoingFinalizer, registerPreSendCheck, resetSendPipeline } from '@/lib/outgoing/send-pipeline';
+import { composerHandle } from '@/lib/plugin-sandbox/composer-registry';
// ─── Heavy component mocks (mirrors composer-reopened-draft-signature.test.tsx) ─
@@ -231,6 +232,16 @@ describe('composer send pipeline', () => {
})]);
});
+ it('names the composer plugins know it by, for onAfterEmailSend', async () => {
+ let open = false;
+ const onSend = vi.fn((data: { composerId?: string }) => { open = !!composerHandle(data.composerId); });
+ render();
+ clickSend();
+ await waitFor(() => expect(onSend).toHaveBeenCalledTimes(1));
+ expect(onSend.mock.calls[0][0]).toEqual(expect.objectContaining({ composerId: expect.any(String) }));
+ expect(open).toBe(true);
+ });
+
it('sends nothing when a check says no, and sends after it says yes', async () => {
let allow = false;
const check = vi.fn(() => allow);
diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx
index b0d46d740..5d7bfe0cd 100644
--- a/components/email/email-composer.tsx
+++ b/components/email/email-composer.tsx
@@ -296,6 +296,8 @@ interface EmailComposerProps {
requireTls?: boolean;
/** Importance / X-Priority; absent for normal (#970). */
priority?: MessagePriority;
+ /** The id plugins know this composer by, for onAfterEmailSend. */
+ composerId?: string;
}) => void | false | Promise;
onScheduledSendCreated?: () => void | Promise;
onClose?: () => void;
@@ -3679,6 +3681,7 @@ export function EmailComposer({
requireTls: requireTls || undefined,
priority: priority !== 'normal' ? priority : undefined,
delayedUntil: effectiveDelayedUntil,
+ composerId: pluginComposerIdRef.current ?? undefined,
});
// Not when nothing went: no host, or one that sent nothing.
if (draftElsewhere && onSend && outcome !== false) await removeDraftVersion(finalDraftId!, draftLogin, draftAccountAtSend);
diff --git a/components/mail/__tests__/after-send-hook.test.tsx b/components/mail/__tests__/after-send-hook.test.tsx
index 62a03e54a..e71b1006a 100644
--- a/components/mail/__tests__/after-send-hook.test.tsx
+++ b/components/mail/__tests__/after-send-hook.test.tsx
@@ -25,7 +25,7 @@ const sendData = vi.hoisted(() => ({ delayedUntil: undefined as string | undefin
vi.mock('@/components/email/email-composer', () => ({
EmailComposer: ({ onSend }: { onSend?: (data: unknown) => Promise }) => (