Covers migrating ActivePieces inquiry flow into the CRM: client confirmation emails, sales team notifications, client addresses table, and admin configuration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9.0 KiB
Inquiry Notifications System Design
Migrates the ActivePieces-powered inquiry notification flow into the CRM. When a client registers interest via the Port Nimara website, the system sends a confirmation email to the client and notifies the sales team -- all using the CRM's own database and email infrastructure instead of NocoDB + ActivePieces.
Scope
- Expand the public interest API to accept all website form fields
- Add client address storage (multi-address with primary flag)
- Send branded confirmation email to the client
- Send notification to sales team (CRM users + optional external recipients)
- Make notification recipients and contact email configurable by admins
Database Changes
New table: client_addresses
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | crypto.randomUUID() |
client_id |
uuid FK → clients | cascade delete |
port_id |
uuid FK → ports | cascade delete |
label |
text | e.g., "Home", "Office", "Billing" |
street_address |
text | |
city |
text | |
state_province |
text | |
postal_code |
text | |
country |
text | |
is_primary |
boolean | default true, one-primary-per-client enforced in service layer |
created_at |
timestamp | default now() |
updated_at |
timestamp | default now() |
Schema file: src/lib/db/schema/clients.ts (alongside existing client tables).
Relations: added to src/lib/db/schema/relations.ts (client has many addresses).
No changes to existing tables
clients.preferred_contact_methodalready exists -- we populate it from the form.interests.berth_idalready exists -- we resolvemooringNumberto a berth and link it.notifications.typealready hasnew_registration-- we fire it.
Public API Changes
POST /api/public/interests
Expanded request schema:
// Required
firstName: string; // max 100
lastName: string; // max 100
email: string; // email format
phone: string;
// Optional
preferredContactMethod: 'email' | 'phone' | 'sms';
mooringNumber: string; // e.g., "A3" -- resolved against berths.mooring_number
companyName: string;
yachtName: string;
yachtLengthFt: number;
yachtWidthFt: number;
yachtDraftFt: number;
preferredBerthSize: string;
notes: string; // max 2000
address: {
street: string;
city: string;
stateProvince: string;
postalCode: string;
country: string;
}
// Backward compatibility
fullName: string; // accepted if firstName/lastName not provided
Backward compatibility: if fullName is provided without firstName/lastName, it is used as-is for clients.full_name. If firstName+lastName are provided, they are concatenated.
Behavior after record creation
- Resolve
mooringNumberagainstberths.mooring_numberfor the port. Linkinterests.berth_idif found; leave null if not. - Store
addressinclient_addresseswithis_primary: trueandlabel: 'Primary'. - Set
clients.preferred_contact_methodfrom the form value. - Queue client confirmation email (see Email Templates below).
- Fire
new_registrationnotifications to sales team (see Notification Flow below). - Return
201 { data: { id, message } }unchanged.
Rate limiting remains 5 requests/hour per IP.
Email Templates
Located in src/lib/email/templates/. Each exports a function that accepts a typed data object and returns { subject: string, html: string, text: string }.
inquiry-client-confirmation.ts
Sent to the client who submitted the form.
Input data:
firstName-- for the greetingmooringNumber-- berth identifier (nullable)contactEmail-- frominquiry_contact_emailsystem setting
Subject: "Thank You for Your Interest in Berth {mooringNumber}" or "Thank You for Your Interest in a Port Nimara Berth" if no berth.
Body: Greeting with first name, confirmation their interest is registered, mention they'll be contacted by preferred method, link to the contact email address.
Styling: Branded -- Port Nimara logo, background image, white card layout. Matches the existing ActivePieces client confirmation template.
inquiry-sales-notification.ts
Sent to CRM users and optional external recipients.
Input data:
fullNameemailphonemooringNumber(nullable, defaults to "None")crmUrl-- link to the interest detail page in the CRM (built from port slug + interest ID)
Subject: "New Interest - Port Nimara"
Body: Notifies that a new interest has been registered, shows client details and berth selected, links to the CRM.
Styling: Branded -- Port Nimara logo, background image, white card layout. Matches the existing ActivePieces admin notification template.
Both templates include a plain-text fallback.
Notification & Delivery Flow
Client confirmation email
- After record creation, queue a
send-inquiry-confirmationjob on theemailBullMQ queue. - Email worker renders the
inquiry-client-confirmationtemplate with the interest data. - Sends via system SMTP (
src/lib/email/index.ts). - No in-app notification (client is not a CRM user).
Sales team notification
- Query all users on the port who have
interestsread permission via their role. - For each user, call
createNotification()with typenew_registration.- The existing notification service checks
user_notification_preferences(in-app / email / both / neither). - Creates in-app notification + Socket.IO push if
in_app: true. - Queues
send-notification-emailjob ifemail: true.
- The existing notification service checks
- Fetch
inquiry_notification_recipientssystem setting for the port. - For each external email, queue a
send-inquiry-sales-notificationjob on theemailqueue (bypasses notification preferences since these are not CRM users).
Independence
Client confirmation and sales notifications are independent -- a failure in one does not block the other. The 201 response returns immediately after record creation, before any emails are sent.
Admin Configuration
Two new system settings, managed via the existing admin settings UI:
inquiry_contact_email (string, per-port)
The reply-to / contact email shown in client confirmation emails.
- Default:
sales@portnimara.com - Displayed as a mailto link in the client confirmation email.
inquiry_notification_recipients (JSON array of strings, per-port)
Additional external email addresses that receive the sales team notification.
- Default:
[](empty) - Only CRM users with interests permissions are notified by default.
- External recipients receive the sales notification email directly.
Existing infrastructure (no changes needed)
- Which CRM users get notified: controlled by roles/permissions.
- How each user receives notifications:
user_notification_preferencestable. - Admin settings UI: already supports custom key-value pairs in
system_settings.
Files to Create or Modify
New files
src/lib/db/schema/client-addresses.ts-- (or added toclients.ts)src/lib/email/templates/inquiry-client-confirmation.tssrc/lib/email/templates/inquiry-sales-notification.ts
Modified files
src/lib/db/schema/clients.ts-- addclientAddressestable exportsrc/lib/db/schema/index.ts-- re-export new tablesrc/lib/db/schema/relations.ts-- add client addresses relationssrc/lib/validators/public-interest.ts(or whereverpublicInterestSchemalives) -- expand schemasrc/app/api/public/interests/route.ts-- berth resolution, address storage, notification + email triggerssrc/lib/queue/workers/email.ts-- handlesend-inquiry-confirmationandsend-inquiry-sales-notificationjobssrc/lib/services/interests.service.ts-- helper to find users with interests permissions on a portsrc/app/(dashboard)/[portSlug]/admin/settings/settings-manager.tsx-- register the two new setting keys
Out of Scope
- Editing email templates from the admin UI (templates are in code).
- Supplemental forms for collecting missing info (separate feature using existing
form_templates/form_submissionsinfrastructure). - Documenso EOI integration with address merge fields (separate feature).
- Changes to the Port Nimara website form itself (website team wires the form to our API).