feat(email): system/user senderType + attachments
Composer validator now takes senderType (system|user) and an attachments[] array, and the service dispatches across two paths: the system path uses lib/email/index.ts with port-config noreply identity and logs signed_doc_emailed when an attachment matches a document's signed PDF; the user path stays on the existing personal- account flow but is gated by the new email.allowPersonalAccountSends toggle and the attachment fileIds are persisted on email_messages. sendEmail in lib/email accepts attachments and resolves them from MinIO with cross-port enforcement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,15 +15,25 @@ export const toggleAccountSchema = z.object({
|
||||
isActive: z.boolean(),
|
||||
});
|
||||
|
||||
export const composeEmailSchema = z.object({
|
||||
accountId: z.string().uuid(),
|
||||
threadId: z.string().uuid().optional(),
|
||||
to: z.array(z.string().email()).min(1),
|
||||
cc: z.array(z.string().email()).optional(),
|
||||
subject: z.string().min(1),
|
||||
bodyHtml: z.string().min(1),
|
||||
inReplyToMessageId: z.string().optional(),
|
||||
});
|
||||
export const composeEmailSchema = z
|
||||
.object({
|
||||
senderType: z.enum(['system', 'user']).default('user'),
|
||||
/** Required when senderType=user; ignored otherwise. */
|
||||
accountId: z.string().uuid().optional(),
|
||||
threadId: z.string().uuid().optional(),
|
||||
to: z.array(z.string().email()).min(1),
|
||||
cc: z.array(z.string().email()).optional(),
|
||||
subject: z.string().min(1),
|
||||
bodyHtml: z.string().min(1),
|
||||
inReplyToMessageId: z.string().optional(),
|
||||
attachments: z
|
||||
.array(z.object({ fileId: z.string().uuid(), filename: z.string().optional() }))
|
||||
.optional(),
|
||||
})
|
||||
.refine((d) => d.senderType !== 'user' || Boolean(d.accountId), {
|
||||
path: ['accountId'],
|
||||
message: 'accountId is required when senderType=user',
|
||||
});
|
||||
|
||||
export const listThreadsSchema = z.object({
|
||||
page: z.coerce.number().int().positive().default(1),
|
||||
|
||||
Reference in New Issue
Block a user