使用 Next.js、Novu 和 Appwrite 构建具有实时聊天和通知功能的客户支持应用程序

2025-06-08

使用 Next.js、Novu 和 Appwrite 构建具有实时聊天和通知功能的客户支持应用程序

在本文中,我将向您介绍如何使用 Next.js、Novu、Appwrite 和 EmailJS 构建具有实时聊天、应用内和电子邮件通知功能的客户支持应用程序。

Appwrite Cloud处理身份验证、数据库和文件存储方面,Novu处理应用内通知,EmailJS处理电子邮件消息传递。

完成后,您将能够使用 Appwrite 构建需要聊天功能的高级应用程序,并通过 EmailJS 和 Novu 向您的应用程序添加电子邮件和应用内通知。

💡 PS:本教程假设您具有 React 或 Next.js 的基础知识

准备就绪

申请流程

在开始编码之前,让我先总结一下该应用程序的工作原理。该应用程序执行以下操作:

  • 允许客户创建支持票,
  • 对员工进行身份验证,并确保除员工之外没有人可以登录应用程序,
  • 当客户创建支持单时通知工作人员,
  • 允许员工和客户通过Appwrite Cloud提供的实时聊天功能解决纠纷,并且
  • 当客户开具票据时以及当工作人员向他们发送有关开具票据的消息时,向客户发送确认电子邮件。

使用 Next.js 的客户支持应用程序

简要演示

UI设计流程

在这里,我将引导您创建 Web 应用程序所需的页面。

首先,您需要创建一个主页,客户可以在该主页上创建支持票,工作人员也可以通过该页面导航到登录页面。

主页

接下来,创建员工登录页面,对访问者进行身份验证,以确保他们是员工,然后才允许他们访问仪表板。

员工登录页面
您还需要一个仪表板页面,根据工单状态显示所有工单。每张支持工单都必须可点击,并将工作人员重定向到其详细信息页面。

仪表板页面

门票详情

接下来,创建一个页面,显示与特定工单相关的所有数据。工作人员可以更新支持工单的状态并下载其附件(如果有)。

详细信息页面

然后,创建一个聊天页面,以便客服人员和客户可以实时沟通问题。
该页面无需身份验证,只需将访问代码发送到客户的邮箱即可。聊天 URL 类似于此https://firm-support.vercel.app/chat/<ticket_ID>

聊天页面

最后,您需要一个管理页面,用于在应用程序中添加或删除员工。这样,您就可以让多个用户管理支持工单或回复客户的疑问。

管理页面

将 Appwrite 添加到 Next.js 应用程序

Appwrite是一个强大的开源后端平台,可帮助您创建安全且可扩展的 Web 和移动应用程序。使用 Appwrite,您无需担心应用程序的后端资源,因为 Appwrite 可以实现“快速构建、大规模扩展、一站式”的体验。

Appwrite Cloud 使您无需在计算机上设置后端服务器,而是专注于开发应用程序;同时,它管理关键的后端功能,例如用户身份验证、数据库管理、文件存储等。

安装和配置

要将 Appwrite 添加到 Next.js 应用,请按照以下步骤操作:

首先,通过运行下面的代码创建一个 Next.js 项目。

npx create-next-app customer-support-app
Enter fullscreen mode Exit fullscreen mode

访问Appwrite 的网站并创建一个新帐户。

创建新的组织和项目。每个项目都包含构建功能齐全的应用程序所需的所有资源。

创建 Appwrite 项目

接下来,您需要选择在何处以及如何使用 Appwrite,可以作为 Web 或移动 SDK,或者您需要将其与您的(现有)服务器集成。

连接到 Web 应用程序

由于我们正在使用 Appwrite Cloud 构建 Next.js 应用程序,因此从 SDK 平台菜单中选择 Web 应用程序并在项目下注册一个新应用程序。

从下图中,我为应用程序提供了一个名称,并使用星号作为主机名。在 Vercel 上部署应用程序后,您可以将主机名更改为Vercel提供的 URL 。

将 Appwrite 连接到 Next.js

如下所示将 Appwrite Node.js SDK 安装到您的 Next.js 项目中。

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

在项目根目录下创建一个.env.local文件。appwrite.js

touch appwrite.js .env.local
Enter fullscreen mode Exit fullscreen mode

将下面的代码复制到appwrite.js文件中。

import { Client, Account, Databases, Storage } from "appwrite";
const client = new Client();

client
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject(process.env.NEXT_PUBLIC_PROJECT_ID);

export const account = new Account(client);

export const db = new Databases(client);

export const storage = new Storage(client);
Enter fullscreen mode Exit fullscreen mode

上面的代码片段使我们能够访问并与 Appwrite 提供的身份验证、数据库和文件存储功能进行交互。

将下面的代码复制到.env.local文件中。

NEXT_PUBLIC_PROJECT_ID=<your_project_id>
NEXT_PUBLIC_DB_ID=<your_database_id>
NEXT_PUBLIC_TICKETS_COLLECTION_ID=<your_collection_id>
NEXT_PUBLIC_USERS_COLLECTION_ID=<your_collection_id>
NEXT_PUBLIC_BUCKET_ID=<your_bucket_id>
Enter fullscreen mode Exit fullscreen mode

上面的代码片段包含环境变量,其中包含与 Appwrite Cloud 交互所需的所有私钥。

在您的项目仪表板上,单击Project ID按钮复制项目的 ID 并将其粘贴到.env.local变量中。

项目仪表盘ID

使用 Appwrite Cloud 设置身份验证

由于您使用的是电子邮件和密码身份验证方法,因此在使用该服务之前无需在 Appwrite Cloud 上添加任何配置,因为它已默认配置。

但是,让我们通过更新默认会话长度来为项目添加额外的安全性。

Auth从侧边栏菜单中选择并切换到Security选项卡。

授权设置

向下滚动到“会话时长”部分,并将其从 更改365 days1 hour。用户使用应用程序一小时后需要重新进行身份验证,并且如果用户未退出我们的应用程序,则会在一小时后自动注销。

会话长度

单击Update添加新设置,即可开始使用。🚀

设置 Appwrite 数据库

在这里,您将学习如何在 Appwrite Cloud 上设置数据库。

Database从侧边栏菜单中选择创建一个新的数据库。

创建数据库

接下来,您需要创建两个数据库集合,一个用于支持工单,另一个用于应用程序内的员工。
您可能想知道为什么我们需要为用户创建另一个集合。那么在 Appwrite Auth 上保存的用户呢?

原因是您无法使用 Appwrite Cloud获取所有用户删除某个用户。只有使用Node.js SDK时才可以

但是,由于我们需要从应用程序的管理页面查看所有用户(员工)并在必要时添加或删除;因此,您需要创建一个users包含与 Auth 部分类似信息的数据库。

为支持票证和用户创建两个数据库集合,并将他们的 ID 复制到env.local文件中。

数据库集合

选择users集合并创建三个必需的属性,如下所示。

用户属性

最后,单击下方的“设置”菜单,users并更新权限以仅允许用户从集合中创建、读取和删除用户。

Appwrite 设置

设置 Appwrite 存储

从侧边栏菜单中选择Storage,并为每个支持工单附带的图片创建一个新的存储桶。用户可以在创建支持工单时上传他们遇到的问题的屏幕截图。

创建存储桶

复制存储桶 ID 并将其粘贴到.env.local文件中。

存储桶 ID

与 Appwrite 通信:验证用户身份

与传统应用程序不同,此应用程序没有注册页面,因为它是员工专用的。因此,您可以通过 Appwrite Cloud 上的仪表板创建初始帐户。

在本节中,您将学习如何为应用程序设置身份验证流程。
您可以创建一个utils包含函数的文件夹,并将其导入到所需的组件中。

mkdir utils
cd utils
touch functions.js
Enter fullscreen mode Exit fullscreen mode

将以下导入添加到文件中,以便我们能够与后端功能进行交互。我们将在接下来的部分中使用它们。

import { account, db, storage } from "./appwrite";
import { ID } from "appwrite";
Enter fullscreen mode Exit fullscreen mode

登录应用程序

记住,我们有 Appwrite Auth 和users数据库上的集合来存储用户的详细信息。因此,要将用户登录到应用程序,您需要使用 Appwrite Auth 对用户进行身份验证,然后过滤集合users以验证用户的详细信息是否存在,然后再授予对应用程序的访问权限。

//👇🏻 filters the users' list
const checkUserFromList = async (email, router) => {
    try {
        const response = await db.listDocuments(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_USERS_COLLECTION_ID
        );
        const users = response.documents;
        const result = users.filter((user) => user.email === email);

        //👉🏻 USER OBJECT ==> console.log(result[0])

        if (result.length > 0) {
            successMessage("Welcome back 🎉");
            router.push("/staff/dashboard");
        } else {
            errorMessage("Unauthorized...Contact Management.");
        }
    } catch (error) {
        errorMessage("An error occurred 😪");
        console.error(error);
    }
};

//👇🏻 authenticates the user
export const logIn = async (email, password, router) => {
    try {
        //👇🏻 Appwrite login method
        await account.createEmailSession(email, password);
        //👇🏻 calls the filter function
        await checkUserFromList(email, router);
    } catch (error) {
        console.log(error);
        errorMessage("Invalid credentials ❌");
    }
};

Enter fullscreen mode Exit fullscreen mode

上面的代码片段从表单字段接受用户的电子邮件和密码,使用 Appwrite Auth 对用户进行身份验证,然后在授予应用程序权限之前检查用户是否在员工列表中。

注销

注销用户仍然可以通过常规方式进行。Appwrite 还提供了一种account.deleteSession()方法,允许用户退出正在进行的会话。

export const logOut = async (router) => {
    try {
        await account.deleteSession("current");
        router.push("/");
        successMessage("See ya later 🎉");
    } catch (error) {
        console.log(error);
        errorMessage("Encountered an error 😪");
    }
};
Enter fullscreen mode Exit fullscreen mode

保护页面免受未经身份验证的用户的访问

为此,您可以将用户的信息对象存储到登录后的状态中,或者使用 Appwrite 的account.get()方法。

使用account.get()方法:

export const checkAuthStatus = async (setUser, setLoading, router) => {
    try {
        const response = await account.get();
        setUser(response);
        setLoading(false);
    } catch (err) {
        router.push("/");
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

上面的代码片段获取了与当前登录用户相关的所有信息。它检查用户是否处于活动状态,并返回包含所有用户详细信息的对象。

您可以在页面加载时对包含受保护数据的路由执行该功能,例如仪表板、票证详细信息和管理路由。

管理页面:添加和删除员工

在本节中,我将指导您如何在用户集合中添加、获取和删除员工。从技术上讲,我们正在构建管理页面的后端功能。

管理页面

添加新员工

下面的代码片段在您提交表单时接受用户的姓名、电子邮件和密码,在 Appwrite Auth 上创建一个帐户,并将电子邮件和姓名保存在users数据库中。

//👇🏻 generates random string as ID
const generateID = () => Math.random().toString(36).substring(2, 24);

export const addUser = async (name, email, password) => {
    try {
        //👇🏻 create a new acct on Appwrite Auth
        await account.create(generateID(), email, password, name);
        //👇🏻 adds the user's details to the users database
        await db.createDocument(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_USERS_COLLECTION_ID,
            ID.unique(),
            { user_id: generateID(), name, email }
        );
        successMessage("User added successfully 🎉");
    } catch (error) {
        console.log(error);
    }
};
Enter fullscreen mode Exit fullscreen mode

获取员工名单

代码片段访问 Appwrite Cloud 上的文档并返回users集合中的所有数据。

export const getUsers = async (setUsers) => {
    try {
        const response = await db.listDocuments(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_USERS_COLLECTION_ID
        );
        setUsers(response.documents);
    } catch (error) {
        console.log(error);
    }
};
Enter fullscreen mode Exit fullscreen mode

移除员工

为此,您需要在单击“删除”按钮时将选定员工的 ID 传递到函数中。

export const deleteUser = async (id) => {
    try {
        await db.deleteDocument(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_USERS_COLLECTION_ID,
            id
        );
        successMessage("User removed 🎉"); // Success
    } catch (error) {
        console.log(error); // Failure
        errorMessage("Encountered an error 😪");
    }
};
Enter fullscreen mode Exit fullscreen mode

💡 用户的凭据仍然在 Appwrite Auth 中可用,但由于我们users在用户登录时根据集合验证用户的凭据,因此此方法仍然有效。

支持票:数据结构、数据库和存储

在本节中,您将学习如何为客户支持应用程序创建后端(数据库)。

创建支持票

当客户创建支持工单时,您必须从表单中检索所有详细信息并将其保存到数据库。因此,您需要创建一个函数,该函数接受所有工单的详细信息,并确保无论客户是否上传他们所遇到问题的屏幕截图,该函数都能完美运行。

export const sendTicket = async (name, email, subject, message, attachment) => {
    if (attachment !== null) {
        //👇🏻 Customer attached an image
        console.log({ name, email, subject, message, attachment });
    } else {
        //👇🏻 No attachment
        console.log({ name, email, subject, message });
    }
};
Enter fullscreen mode Exit fullscreen mode

要将票证的信息添加到ticketsAppwrite Cloud 上的集合,请将以下属性添加到集合中。

姓名 必需的? 类型
姓名 是的 细绳
电子邮件 是的 细绳
主题 是的 细绳
地位 是的 细绳
内容 是的 细绳
访问代码 是的 细绳
附件网址 细绳
消息 细绳[]

票证属性

切换到“设置”选项卡并更新集合的权限tickets,如下所示。

支持票证权限

最后,更新sendTicket功能以将事件详细信息添加到 Appwrite Cloud。

export const sendTicket = async (name, email, subject, message, attachment) => {
    const createTicket = async (file_url = "https://google.com") => {
        try {
            const response = await db.createDocument(
                process.env.NEXT_PUBLIC_DB_ID,
                process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID,
                ID.unique(),
                {
                    name,
                    email,
                    subject,
                    content: message,
                    status: "open",
                    messages: [
                        JSON.stringify({
                            id: generateID(),
                            content: message,
                            admin: false,
                            name: "Customer",
                        }),
                    ],
                    attachment_url: file_url,
                    access_code: generateID(),
                }
            );
            //👇🏻 send notification to the customer
            console.log("RESPONSE >>>", response);
            successMessage("Ticket created 🎉");
        } catch (error) {
            errorMessage("Encountered saving ticket ❌");
        }
    };

    if (attachment !== null) {
        try {
            const response = await storage.createFile(
                process.env.NEXT_PUBLIC_BUCKET_ID,
                ID.unique(),
                attachment
            );
            const file_url = `https://cloud.appwrite.io/v1/storage/buckets/${process.env.NEXT_PUBLIC_BUCKET_ID}/files/${response.$id}/view?project=${process.env.NEXT_PUBLIC_PROJECT_ID}&mode=admin`;
            //👇🏻 creates ticket with its image
            createTicket(file_url);
        } catch (error) {
            errorMessage("Error uploading the image ❌");
        }
    } else {
        //👇🏻 creates ticket even without an image (screenshot)
        await createTicket();
    }
};
Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看:
    • 嵌套函数createTicket接受所有工单的属性,并在 Appwrite Cloud 上创建一个新文档。由于上传附件是可选的,因此该flier_url属性具有默认的 URL 值。
    • arraymessages属性为实时聊天功能创建了一个新结构。它将内容和客户信息转换为 JSON 字符串,并将其添加到messages数组中。
    • if 和 else 代码块会检查客户是否上传了图片。如果是,代码会将图片上传到 Cloud Storage,检索其 URL,并将其传递给createTicket函数。
    • 否则,该createTicket函数使用默认值(https://google.com)作为附件 URL。

从 Appwrite Cloud 获取支持票

在仪表板页面上,您需要检索所有支持工单。下面的函数将检索它们并根据其状态进行分组。

支持票

export const getTickets = async (
    setOpenTickets,
    setInProgressTickets,
    setCompletedTickets
) => {
    try {
        const response = await db.listDocuments(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID
        );
        const tickets = response.documents;
        const openTickets = tickets.filter((ticket) => ticket.status === "open");
        const inProgressTickets = tickets.filter(
            (ticket) => ticket.status === "in-progress"
        );
        const completedTickets = tickets.filter(
            (ticket) => ticket.status === "completed"
        );
        setCompletedTickets(completedTickets);
        setOpenTickets(openTickets);
        setInProgressTickets(inProgressTickets);
    } catch (error) {
        console.log(error); // Failure
    }
};
Enter fullscreen mode Exit fullscreen mode

获取票务详情

当您单击仪表板上的每个支持票时,它需要将您重定向到另一个包含与该支持票相关的所有信息页面。

因此,您需要创建一个文件,使用页面路由ticket/[id].js通过服务器端渲染检索票证的详细信息,如下所示。id

export async function getServerSideProps(context) {
    let ticketObject = {};
    try {
        const response = await db.getDocument(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID,
            context.query.id
        );

        ticketObject = response;
    } catch (err) {
        ticketObject = {};
    }

    return {
        props: { ticketObject },
    };
}
Enter fullscreen mode Exit fullscreen mode

更新工单状态

在详细信息页面上,您可以使用 HTML select 标签更新支持工单的状态,并接受三种类型的值 - "open""in-progress""completed"。您可以使用以下函数更新状态:

export const updateTicketStatus = async (id, status) => {
    try {
        await db.updateDocument(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID,
            id,
            { status }
        );
        successMessage("Status updated, refresh page 🎉");
    } catch (error) {
        console.log(error); // Failure
        errorMessage("Encountered an error ❌");
    }
};
Enter fullscreen mode Exit fullscreen mode

使用 Next.js 和 Appwrite 的实时聊天功能

在本节中,我将指导您向应用程序添加聊天功能。为此,请创建一个类似下图的聊天页面。

聊天页面

此页面在加载时需要访问代码,但无需身份验证即可访问。当用户发送消息时,运行以下代码片段。

export const sendMessage = async (text, docId) => {
    //👇🏻 get the ticket ID
    const doc = await db.getDocument(
        process.env.NEXT_PUBLIC_DB_ID,
        process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID,
        docId
    );

    try {
        //👇🏻 gets the user's object (admin)
        const user = await account.get();
        const result = await db.updateDocument(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID,
            docId,
            {
                messages: [
                    ...doc.messages,
                    JSON.stringify({
                        id: generateID(),
                        content: text,
                        admin: true,
                        name: user.name,
                    }),
                ],
            }
        );
        //👇🏻 message was added successfully
        if (result.$id) {
            successMessage("Message Sent! ✅");
            //👉🏻 email the customer with access code and chat URL
        } else {
            errorMessage("Error! Try resending your message❌");
        }
    } catch (error) {
        //👇🏻 means the user is a customer
        const result = await db.updateDocument(
            process.env.NEXT_PUBLIC_DB_ID,
            process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID,
            docId,
            {
                messages: [
                    ...doc.messages,
                    JSON.stringify({
                        id: generateID(),
                        content: text,
                        admin: false,
                        name: "Customer",
                    }),
                ],
            }
        );
        if (result.$id) {
            successMessage("Message Sent! ✅");
            //👉🏻 notify staff via notifications
        } else {
            errorMessage("Error! Try resending your message❌");
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

上面的代码片段在将消息添加到数组之前检查用户是员工还是客户messages

实时消息传递

立即显示您发送的聊天消息。

在集合上创建一个事件监听器tickets,并messages使用更改更新数组,如下所示。

//👉🏻 "client" is from your appwrite.js file
useEffect(() => {
    const unsubscribe = client.subscribe(
        `databases.${process.env.NEXT_PUBLIC_DB_ID}.collections.${process.env.NEXT_PUBLIC_TICKETS_COLLECTION_ID}.documents`,
        (data) => {
            const messages = data.payload.messages;
            setMessages(messages.map(parseJSON));
        }
    );
    return () => {
        unsubscribe();
    };
}, []);
Enter fullscreen mode Exit fullscreen mode

您可以在 Appwrite 中了解有关实时监听器的更多信息。

自动滚动功能

div在聊天消息元素下方添加一个空白。

<div className='chat__container'>
    //👇🏻 chat messages element
    {/* {messages.map((message) => (
                                        <div>{message}</div>
                                    ))} */}
    <div ref={lastMessageRef} />
</div>
Enter fullscreen mode Exit fullscreen mode

创建对元素的引用div,并在有新消息时转移鼠标焦点,如下所示。

const lastMessageRef = useRef(null);

useEffect(() => {
    // 👇️ scroll to bottom every time messages change
    lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
Enter fullscreen mode Exit fullscreen mode

使用 Novu 和 EmailJS 添加应用内和电子邮件通知

在这里,您将学习如何使用 Novu 添加应用内通知以及如何使用 EmailJS 发送电子邮件。
回想一下,当客户通过聊天页面发送消息以及创建新工单时,我们需要应用内通知。
当客服人员发送消息以及客户创建新工单时,我们需要电子邮件通知。

在 Next.js 中使用 Novu 设置应用内通知

Novu是第一个管理所有通信形式的开源通知基础架构。在本文中,我们将使用其应用内通知功能。

通过运行以下代码安装 Novu Node.js SDK 及其通知中心。

npm install @novu/node @novu/notification-center
Enter fullscreen mode Exit fullscreen mode

运行npx novu init以创建并访问您的仪表板。

将您的 Novu 订阅者 ID、应用程序 ID 和 API 密钥添加到.env.local文件中。

NEXT_PUBLIC_NOVU_SUBSCRIBER_ID=<subscriber_ID>
NEXT_PUBLIC_NOVU_APP_ID=<your_app_ID>
NEXT_PUBLIC_NOVU_API_KEY=<your_api_key>
Enter fullscreen mode Exit fullscreen mode

在您的 Novu 仪表板上创建两个通知模板,并针对两种情况(客户创建工单和发送聊天消息)编辑其内容。将通知功能添加到您的 Next.js 应用中,如下所示。

const { Novu } = require("@novu/node");
const novu = new Novu(process.env.NEXT_PUBLIC_NOVU_API_KEY);

export default async function handler(req, res) {
    const { status, title, username } = req.body;
    const response = await novu
        .trigger("<template_name>", {
            to: {
                subscriberId: process.env.NEXT_PUBLIC_NOVU_SUBSCRIBER_ID,
            },
            payload: {
                status,
                title,
                username,
            },
        })
        .catch((err) => console.error(err));

    res.status(200).json(response.data);
}
Enter fullscreen mode Exit fullscreen mode

最后,创建通知铃以在您的应用程序内显示通知,并将其添加到应用程序中的导航组件。

import {
    NovuProvider,
    PopoverNotificationCenter,
    NotificationBell,
} from "@novu/notification-center";

function Novu() {
    function onNotificationClick(message) {
        if (message?.cta?.data?.url) {
            window.location.href = message.cta.data.url;
        }
    }

    return (
        <NovuProvider
            subscriberId={process.env.NEXT_PUBLIC_NOVU_SUBSCRIBER_ID}
            applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID}
        >
            <PopoverNotificationCenter
                onNotificationClick={onNotificationClick}
                colorScheme='light'
            >
                {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />}
            </PopoverNotificationCenter>
        </NovuProvider>
    );
}
export default Novu;
Enter fullscreen mode Exit fullscreen mode

使用 EmailJS 发送电子邮件通知

在这里,您需要在客户创建支持工单时以及工作人员向其发送消息时向其发送电子邮件。请按照以下步骤操作。

通过运行以下代码安装 EmailJS。

npm install @emailjs/browser
Enter fullscreen mode Exit fullscreen mode

配置您的EmailJS 帐户并将您的凭据复制到.env.local文件中。

NEXT_PUBLIC_EMAIL_SERVICE_ID=<your_service_id>
NEXT_PUBLIC_EMAIL_API_KEY=<your_api_key>
NEXT_PUBLIC_TICKET_CREATION_ID=<template_id>
NEXT_PUBLIC_NEW_MESSAGE_ID=<template_id>
Enter fullscreen mode Exit fullscreen mode

为这两种情况创建模板并向您的客户发送电子邮件通知

点击此处获取更多指导

总结

该应用程序的源代码可在此处获取。欢迎随时查看。

愿意工作🙂

你喜欢这篇文章吗?或者你需要一位经验丰富的 React 技术作家和开发人员来担任远程、全职或合同制职位?欢迎随时联系我。GitHub || LinkedIn
|| Twitter

请大卫喝杯咖啡

谢谢

链接已关闭,无法翻译:https://dev.to/arshadayvid/building-a-customer-support-app-with-live-chat-and-notifications-using-nextjs-novu-and-appwrite-2fn8
PREV
使用 Vue 创建无需任何 Node 模块的 SPA
NEXT
Java AWS Security LIVE 中的 100 多个顶级数据结构和算法面试问题!