From 3f6fa36f9b94f7cf3ae33f65d8e42717f85fdc37 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 30 Mar 2026 19:27:56 +0200 Subject: [PATCH] feat(crm): implement bulk mail action for contacts - Add BulkMailButton component for Payload list view - Add bulkMailEndpoint handler for processing prompts - Integrate bulk mail action into CrmContacts collection - Update dev:clean script to skip interactive prompts --- apps/web/payload.config.ts | 1 + .../src/payload/collections/CrmContacts.ts | 16 +- .../src/payload/components/BulkMailButton.tsx | 147 ++++++++++++++ .../src/payload/endpoints/bulkMailEndpoint.ts | 191 ++++++++++++++++++ package.json | 2 +- 5 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/payload/components/BulkMailButton.tsx create mode 100644 apps/web/src/payload/endpoints/bulkMailEndpoint.ts diff --git a/apps/web/payload.config.ts b/apps/web/payload.config.ts index dea0b77..bf4d4cb 100644 --- a/apps/web/payload.config.ts +++ b/apps/web/payload.config.ts @@ -97,6 +97,7 @@ export default buildConfig({ connectionString: process.env.DATABASE_URI || process.env.POSTGRES_URI || "", }, + push: false, }), sharp, plugins: [ diff --git a/apps/web/src/payload/collections/CrmContacts.ts b/apps/web/src/payload/collections/CrmContacts.ts index 711f269..8ed909e 100644 --- a/apps/web/src/payload/collections/CrmContacts.ts +++ b/apps/web/src/payload/collections/CrmContacts.ts @@ -1,5 +1,5 @@ import type { CollectionConfig } from "payload"; - +import { bulkMailEndpointHandler } from "../endpoints/bulkMailEndpoint"; export const CrmContacts: CollectionConfig = { slug: "crm-contacts", labels: { @@ -12,7 +12,21 @@ export const CrmContacts: CollectionConfig = { group: "CRM", description: "Contacts are the individual people linked to an Account. A person should only be created once and can be assigned to a company here.", + components: { + views: { + list: { + actions: ["@/src/payload/components/BulkMailButton#BulkMailButton"], + }, + }, + }, }, + endpoints: [ + { + path: "/bulk-mail", + method: "post", + handler: bulkMailEndpointHandler, + }, + ], access: { read: ({ req: { user } }) => Boolean(user), create: ({ req: { user } }) => Boolean(user), diff --git a/apps/web/src/payload/components/BulkMailButton.tsx b/apps/web/src/payload/components/BulkMailButton.tsx new file mode 100644 index 0000000..66bf2e7 --- /dev/null +++ b/apps/web/src/payload/components/BulkMailButton.tsx @@ -0,0 +1,147 @@ +"use client"; + +import React, { useState } from "react"; +import { useSelection, Button, toast } from "@payloadcms/ui"; + +export const BulkMailButton: React.FC = () => { + const { selected } = useSelection(); + const [isOpen, setIsOpen] = useState(false); + const [prompt, setPrompt] = useState(""); + const [isTest, setIsTest] = useState(true); + const [isLoading, setIsLoading] = useState(false); + + const selectedCount = Object.keys(selected).length; + + if (selectedCount === 0) { + return null; + } + + const handleBulkMail = async () => { + setIsLoading(true); + try { + const contactIds = Object.keys(selected); + + const response = await fetch("/api/crm-contacts/bulk-mail", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + contactIds, + instructions: prompt, + isTest, + }), + }); + + const data = await response.json(); + + if (data.success) { + toast.success( + `Successfully sent emails to ${contactIds.length} contacts.`, + ); + setIsOpen(false); + } else { + toast.error(`Failed: ${data.error}`); + } + } catch (e: any) { + toast.error(`An error occurred: ${e.message}`); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + + {isOpen && ( +
+
+

AI Bulk Mail

+

+ Generate and send personalized emails to {selectedCount} contacts. +

+ +
+ +