🔥 使用 React Flow 和 Resend 构建电子邮件自动化系统 🎉
TL;DR
TL;DR
在本教程中,您将学习如何构建电子邮件自动化系统,每 10 分钟向人们发送一系列消息。⏰
- 使用 React Flow 构建表示电子邮件流程的客户端图表。⿳
- 每 10 分钟根据流程重新发送电子邮件。📝
Novu——第一个开源通知基础设施
简单介绍一下我们。Novu 是一个开源通知基础设施。我们主要负责管理所有产品通知。通知可以是应用内通知(类似于开发者社区的Websockets中的铃铛图标)、电子邮件、短信等等。
我们实际上也在项目中实现了 ReactFlow 和 Resend
如果你能给我们一颗星,我会非常高兴!这会帮助我每周写更多文章🚀
https://github.com/novuhq/novu
ReactFlow 构建你的流程✅
ReactFlow 是一个易于使用的库,可用于构建从静态图表到数据可视化,甚至复杂的可视化编辑器等各种内容。它高度可定制,并默认提供各种内置功能,例如拖动节点、缩放和平移、选择多个节点和边等等。
在本文中,您将学习如何使用 ReactFlow 向您的 React 应用程序添加交互式图表,以及如何 通过构建电子邮件推广应用程序使用Resend发送电子邮件。
该应用程序通过 ReactFlow 中的节点接受各种电子邮件内容,并将它们作为电子邮件消息发送。
让我们开始吧🔥
在这里,我将引导您安装该项目所需的包依赖项;使用 Next.js v12。
npx create-next-app@12 email-outreach-app
运行下面的代码片段来安装 ReactFlow 和 Resend 包。
npm install reactflow resend
最后,安装 React Redux 和 Redux Toolkit 包,以便我们管理应用程序内的状态。
npm install react-redux @reduxjs/toolkit
放置基本页面布局📟
在这里,我们将创建一个表单,它接受一封电子邮件、一个主题以及一系列包含要发送给收件人的消息的节点。消息将以 30 分钟的间隔发送。
首先,将下面的代码片段复制到pages/index.js
文件中。
import Head from "next/head";
import { useState } from "react";
export default function Home() {
const [email, setEmail] = useState("");
const [subject, setSubject] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email, subject });
setEmail("");
setSubject("");
};
return (
<>
<Head>
<title>Email Outreach - Resend & ReactFlow</title>
<meta name='description' content='Generated by create next app' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
</Head>
<main className='main'>
<header className='header'>
<h1 style={{ marginBottom: "15px" }}>
Email Outreach with ReactFlow and Resend
</h1>
</header>
<form className='form' onSubmit={handleSubmit}>
<label htmlFor='email'>Email</label>
<input
type='email'
name='email'
id='email'
className='input'
value={email}
required
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor='subject'>Subject</label>
<input
type='text'
name='subject'
id='subject'
className='input'
value={subject}
required
onChange={(e) => setSubject(e.target.value)}
/>
{/* --- 👉🏻 ReactFlow Component placeholder 👈🏼 --- */}
<button className='submitBtn'>START AUTOMATION</button>
</form>
</main>
</>
);
}
上面的代码片段创建了一个简单的表单,用于接收收件人的电子邮件地址和邮件主题。在接下来的部分中,我们将添加 ReactFlow 组件。
管理 ReactFlow 组件内的状态
在导入 ReactFlow 组件之前,让我们先设置状态管理库 - Redux Toolkit。
💡 PS:您不需要状态管理库即可使用 ReactFlow。
我们使用 Redux 来追踪组件内的输入并相应地更新应用程序的状态。此外, 您也可以轻松添加 ReactFlow 组件。
因此,创建一个redux
包含nodes.js
和一个store.js
文件的文件夹。
mkdir redux
cd redux
touch nodes.js store.js
将下面的代码片段复制到redux/nodes.js
文件中。
import { createSlice } from "@reduxjs/toolkit";
const addNode = (object) => {
const newNode = {
id: `${Number(object.id) + 1}`,
type: "task",
position: { x: 0, y: object.position.y + 120 },
data: { value: "" },
};
return newNode;
};
const addEdge = (object) => {
const newEdge = {
id: `${object.id}->${Number(object.id) + 1}`,
source: `${object.id}`,
target: `${Number(object.id) + 1}`,
};
return newEdge;
};
上面的代码片段包含两个函数,它们接受一个对象(节点数组中的最后一个元素)并返回另一个包含上述值的对象。
接下来,在同一个文件中,在函数下方添加代码片段。
//below the functions (within the same file)
//---- 👉🏻 functions 👈🏼---
export const nodeSlice = createSlice({
name: "nodes",
initialState: {
nodes: [
{
id: "1",
type: "task",
position: { x: 0, y: 0 },
data: { value: "" },
},
],
edges: [],
},
reducers: {
setNodes: (state, action) => {
let nodes = state.nodes;
state.nodes = [...state.nodes, addNode(nodes[nodes.length - 1])];
state.edges = [...state.edges, addEdge(nodes[nodes.length - 1])];
},
updateNodeValue: (state, action) => {
let nodes = [...state.nodes];
let objectIndex = nodes.findIndex((obj) => obj.id === action.payload.id);
if (objectIndex !== -1) {
state.nodes[objectIndex] = {
...nodes[objectIndex],
data: { value: action.payload.value },
};
}
},
},
});
// Action creators are generated for each case reducer function
export const { setNodes, updateNodeValue } = nodeSlice.actions;
export default nodeSlice.reducer;
- 从上面的代码片段来看,
- 我们创建了两个状态
nodes
和edges
数组。nodes
状态中只有一个元素,代表图中的初始节点。 - Reducer
setNodes
更新nodes
andedges
数组。当用户点击Add button
每个图表节点内的 时,它会执行。 - 减速器
updateNodeValue
跟踪图表中每个节点内的输入,并使用新值更新正确的节点。
- 我们创建了两个状态
将节点减速器添加到store.js
文件中。
import { configureStore } from "@reduxjs/toolkit";
import nodeReducer from "./nodes";
export const store = configureStore({
reducer: {
nodes: nodeReducer,
},
});
最后,通过更新文件使整个应用程序可以使用该存储_app.js
。
import { store } from "../redux/store";
import "../styles/globals.css";
import { Provider } from "react-redux";
export default function App({ Component, pageProps }) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
恭喜!您已设置好图表所需的状态。接下来,让我们将其添加到应用中。
添加 ReactFlow 组件
由于我们对图中的每个节点使用自定义组件,因此创建一个components
包含Task.js
文件的文件夹。
mkdir components
cd components
touch Task.js
将以下代码复制到Task.js
文件中。Task 组件代表图中的每个节点。
import { useState } from "react";
import { Handle, Position } from "reactflow";
import { useSelector, useDispatch } from "react-redux";
import { setNodes, updateNodeValue } from "../redux/nodes";
export default function Task({ id }) {
const initialNodes = useSelector((state) => state.nodes.nodes);
const [value, setValue] = useState("");
const dispatch = useDispatch();
return (
<>
<Handle type='target' position={Position.Top} />
<div
style={{
padding: "10px",
backgroundColor: "#F5F5F5",
borderRadius: "5px",
}}
>
<input
className='textInput'
type='text'
required
onChange={(e) => {
setValue(e.target.value);
dispatch(updateNodeValue({ id, value: e.target.value }));
}}
value={value}
/>
{Number(id) === initialNodes.length && (
<button onClick={() => dispatch(setNodes())} className='addBtn'>
ADD NODE
</button>
)}
</div>
<Handle type='source' position={Position.Bottom} id='a' />
</>
);
}
- 从上面的代码片段来看,
- 顶部和底部渲染的Handle 组件将 每个节点连接到另一个节点。它有一个 type 属性,用于确定节点是源节点还是目标节点。
- 该
Add Node
按钮触发setNodes
减速器。 - 当用户更新输入字段中的内容时,
updateNodeValue
也会触发 Reducer 使用输入值更新所选注释。 - 图中的每个节点都有一个
data
和一个id
包含该节点详细信息的道具。
接下来,将以下导入添加到pages/index.js
文件中。
import { useState, useCallback, useMemo, useEffect } from "react";
import ReactFlow, {
useNodesState,
useEdgesState,
getIncomers,
getOutgoers,
addEdge,
getConnectedEdges,
} from "reactflow";
import "reactflow/dist/style.css";
import Task from "../components/Task";
import { useSelector } from "react-redux";
在文件的 Home 组件中添加下面的代码片段pages/index.js
。
const initialNodes = useSelector((state) => state.nodes.nodes);
const initialEdges = useSelector((state) => state.nodes.edges);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const nodeTypes = useMemo(() => ({ task: Task }), []);
useEffect(() => {
setNodes(initialNodes);
setEdges(initialEdges);
}, [initialNodes, setNodes, initialEdges, setEdges]);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
);
- 从上面的代码片段来看,
- 使用ReactFlow提供的
useNodesState
和钩子将 Redux 状态中的节点和边设置为图的节点和边。useEdgesState
- 变量
nodeTypes
使我们能够定制每个节点。Task
是我们的自定义组件。 onConnect
当您添加新节点时,该函数就会执行。useEffect
当边和节点发生变化时,钩子就会运行。
- 使用ReactFlow提供的
最后,将ReactFlow
组件添加到用户界面,如下所示。
return (
<form>
{/*---👉🏻 other form elements 👈🏼---*/}
<div style={{ height: "60vh", width: "100%", marginTop: "20px" }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
/>
</div>
<button className='submitBtn'>START AUTOMATION</button>
</form>
);
恭喜,您已成功将图表添加到您的应用程序中。
使用 Resend.com 发送您的电子邮件 📜
在本节中,您将学习如何使用 Resend 发送电子邮件,通过将每个节点中的输入发送到表单上提供的电子邮件。
Resend 是一个电子邮件 API,可让您轻松发送文本、附件和电子邮件模板。借助 Resend,您可以大规模构建、测试和发送事务性电子邮件。
其最佳功能之一是您的邮件不会进入收件人的垃圾邮件箱,而是进入收件人的收件箱。
我们已经在本教程开始时安装了 Resend。因此,请转到 注册页面并创建一个帐户。
创建 API 密钥并将其保存到.env.local
Next.js 项目中的文件中。
RESEND_API_KEY=<place_your_API_key>
接下来,在文件夹中创建一个send.js
文件pages/api
,并将下面的代码复制到该文件中。
//👇🏻 within the send.js file
import { Resend } from "resend";
// initiate the resend instance
const resend = new Resend(process.env.RESEND_API_KEY);
const timer = (time) => {
return new Promise((res) => {
setTimeout(() => res(true), time);
});
}
export default async function handler(req, res) {
const { subject, email, tasks } = req.body;
if (!subject || !tasks || !email) {
res.status(400).json({invalid: true});
}
for (const task of tasks) {
await resend.emails.send({
from: "name@yourcompany.dev",
to: [email],
subject,
text: task,
});
// Wait 10 minutes
await timer(600000);
}
res.status(200).json({invalid: false});
}
上面的代码片段从请求中接收主题、收件人和电子邮件内容,并通过Resend向收件人发送电子邮件 。
请注意,电子邮件之间会有 10 分钟的延迟。
这将无法部署到 Vercel,因为他们的免费包支持每个请求最多 10 秒。
您绝对可以在本地机器上对其进行测试。
在生产中,这样的事情需要进入一个队列,每隔 X 次发送电子邮件一次。
在文件中添加以下函数pages/index.js
。
const sendEmail = (index) => {
fetch("/api/send", {
method: "POST",
body: JSON.stringify({
email,
subject,
tasks: nodes.map(data => data.value), // map all nodes to a string array
}),
headers: {
"Content-Type": "application/json",
},
})
.then((data) => {
alert(`Sent to processing`);
})
.catch((err) => {
alert(`Encountered an error when message${index} ❌`);
console.error(err);
});
};
上述函数循环遍历 ReactFlow 图中的各个节点,并定期向收件人发送包含节点值的电子邮件。
最后,当用户提交表单时执行该函数。
const handleSubmit = (e) => {
e.preventDefault();
sendEmail();//👈🏼 Send to server
setEmail(""); // Reset the input
setSubject(""); // Reset the input
};
让我们总结一下🎁
到目前为止,您已经学习了如何使用 ReactFlow 向应用程序添加交互式图表以及如何使用 Resend 发送电子邮件。
ReactFlow 是一个 流行的开源库 ,它使我们能够构建交互式且可自定义的流程图和图表。如果您想构建一个需要拖放功能和可自定义图形 UI 元素的应用程序,那么应该考虑使用 ReactFlow。
本教程的源代码可以在这里找到: https://github.com/novuhq/blog/tree/main/email-outreach-with-reactflow-and-resend
感谢您的阅读!🎉
帮帮我!
如果您觉得这篇文章帮助您更好地理解了电子邮件自动化!请给我们一颗星,我会非常高兴!也请在评论区告诉我❤️
https://github.com/novuhq/novu