From 289a6732f335c9796336e8d5ccb2019edf425afd Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 9 Dec 2025 12:37:13 -0600 Subject: [PATCH] feat: integrate Slack notifications for contact form submissions - Added a new Slack notification feature to alert sales or support channels upon contact form submissions. - Implemented a `notifySlack` function to format and send messages to Slack using a webhook. - Enhanced error handling to ensure email notifications proceed even if Slack notifications fail. --- apps/website/app/api/contact/route.ts | 18 +++ apps/website/lib/slack.ts | 167 ++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 apps/website/lib/slack.ts diff --git a/apps/website/app/api/contact/route.ts b/apps/website/app/api/contact/route.ts index a489115..58565fa 100644 --- a/apps/website/app/api/contact/route.ts +++ b/apps/website/app/api/contact/route.ts @@ -1,4 +1,5 @@ import { getHubSpotUTK, submitToHubSpot } from "@/lib/hubspot"; +import { notifySlack } from "@/lib/slack"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { Resend } from "resend"; @@ -70,6 +71,23 @@ export async function POST(request: NextRequest) { } } + // Send notification to Slack (sales or support channel) + try { + const slackSuccess = await notifySlack(body); + if (slackSuccess) { + console.log( + `Successfully sent ${body.inquiryType} inquiry notification to Slack`, + ); + } else { + console.warn( + `Failed to send ${body.inquiryType} inquiry notification to Slack, but continuing with email`, + ); + } + } catch (error) { + console.error("Error sending to Slack:", error); + // Continue with email even if Slack fails + } + // Format email content const emailSubject = `[${body.inquiryType.toUpperCase()}] New contact form submission from ${body.firstName} ${body.lastName}`; const emailBody = ` diff --git a/apps/website/lib/slack.ts b/apps/website/lib/slack.ts new file mode 100644 index 0000000..436c0c9 --- /dev/null +++ b/apps/website/lib/slack.ts @@ -0,0 +1,167 @@ +interface ContactFormData { + inquiryType: "support" | "sales" | "other"; + firstName: string; + lastName: string; + email: string; + company: string; + message: string; +} + +interface SlackMessage { + text?: string; + blocks?: SlackBlock[]; +} + +interface SlackBlock { + type: string; + text?: { + type: string; + text: string; + }; + fields?: Array<{ + type: string; + text: string; + }>; + elements?: Array<{ + type: string; + text: string; + }>; +} + +/** + * Format contact form data as a Slack message with blocks + */ +function formatContactDataForSlack( + contactData: ContactFormData, +): SlackMessage { + // Get emoji and label based on inquiry type + let inquiryTypeEmoji: string; + let inquiryTypeLabel: string; + + switch (contactData.inquiryType) { + case "sales": + inquiryTypeEmoji = "💰"; + inquiryTypeLabel = "Sales"; + break; + case "support": + inquiryTypeEmoji = "🛟"; + inquiryTypeLabel = "Support"; + break; + case "other": + inquiryTypeEmoji = "📝"; + inquiryTypeLabel = "Other"; + break; + default: + inquiryTypeEmoji = "📧"; + inquiryTypeLabel = "Contact"; + } + + return { + text: `New ${contactData.inquiryType} inquiry from ${contactData.firstName} ${contactData.lastName}`, + blocks: [ + { + type: "header", + text: { + type: "plain_text", + text: `${inquiryTypeEmoji} New ${inquiryTypeLabel} Inquiry`, + }, + }, + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Name:*\n${contactData.firstName} ${contactData.lastName}`, + }, + { + type: "mrkdwn", + text: `*Email:*\n${contactData.email}`, + }, + { + type: "mrkdwn", + text: `*Company:*\n${contactData.company}`, + }, + { + type: "mrkdwn", + text: `*Type:*\n${inquiryTypeLabel}`, + }, + ], + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Message:*\n${contactData.message}`, + }, + }, + { + type: "divider", + }, + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: "Sent from Dokploy website contact form", + }, + ], + }, + ], + }; +} + +/** + * Send a message to Slack using a webhook URL + */ +export async function sendToSlack( + contactData: ContactFormData, + webhookUrl: string, +): Promise { + try { + if (!webhookUrl) { + console.error("Slack webhook URL is not configured"); + return false; + } + + const message = formatContactDataForSlack(contactData); + + const response = await fetch(webhookUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(message), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error("Slack webhook error:", response.status, errorText); + return false; + } + + console.log("Slack notification sent successfully"); + return true; + } catch (error) { + console.error("Error sending to Slack:", error); + return false; + } +} + +/** + * Send contact form notification to Slack + * All inquiry types (sales, support, other) are sent to the same Slack channel + */ +export async function notifySlack( + contactData: ContactFormData, +): Promise { + const webhookUrl = process.env.SLACK_WEBHOOK_URL; + + if (!webhookUrl) { + console.warn( + "Slack webhook URL is not configured (SLACK_WEBHOOK_URL)", + ); + return false; + } + + return await sendToSlack(contactData, webhookUrl); +}