@ devwithbby/ loops (英语)
用于与 循环。 。 电子邮件营销平台. 发送交易邮件,管理联系人,触发回路,并用内置的垃圾邮件检测和速率限制来监控电子邮件操作.
特征
- 联系人管理 - 创建、更新、查找、列表和删除联系人
- 交易邮件 - 发送带有模板的一次性电子邮件
- 活动 - 基于事件的触发电子邮件工作流程
- 循环 - 触发自动电子邮件序列
- 监测 - 用垃圾邮件检测跟踪所有电子邮件操作
- 限制费率 - 内在费率限制对预防虐待的查询
- 类型安全 - 附有Zod验证的完整类型脚本支持
安装
npm install @devwithbobby/loops
# or
bun add @devwithbobby/loops
快速启动
1. 安装和挂载组件
在你身边 convex/convex.config.ts数字 :
import loops from "@devwithbobby/loops/convex.config";
import { defineApp } from "convex/server";
const app = defineApp();
app.use(loops);
export default app;
2. 设置环境变量
重要: 在使用组件前先设置您的循环 API 密钥 .
npx convex env set LOOPS_API_KEY "your-loops-api-key-here"
或者通过Convex Dashboard:
- 转到设置 - > 环境变量
- 添加
LOOPS_API_KEY与您的循环。 所以 API 密钥
获取您的 API 密钥 循环。 所以 Dashboard。 。 。 .
3. 使用本构成部分
在你身边 convex/functions.ts (或任何凸起文件):
import { Loops } from "@devwithbobby/loops";
import { components } from "./_generated/api";
import { action } from "./_generated/server";
import { v } from "convex/values";
// Initialize the Loops client
const loops = new Loops(components.loops);
// Export functions wrapped with auth (required in production)
export const addContact = action({
args: {
email: v.string(),
firstName: v.optional(v.string()),
lastName: v.optional(v.string()),
},
handler: async (ctx, args) => {
// Add authentication check
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
return await loops.addContact(ctx, args);
},
});
export const sendWelcomeEmail = action({
args: {
email: v.string(),
name: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
// Send transactional email
return await loops.sendTransactional(ctx, {
transactionalId: "welcome-email-template-id",
email: args.email,
dataVariables: {
name: args.name,
},
});
},
});
API 参考
联系人管理
添加或更新联系人
await loops.addContact(ctx, {
email: "[email protected]",
firstName: "John",
lastName: "Doe",
userId: "user123",
source: "webapp",
subscribed: true,
userGroup: "premium",
});
更新联系人
await loops.updateContact(ctx, "[email protected]", {
firstName: "Jane",
userGroup: "vip",
});
查找联系人
const contact = await loops.findContact(ctx, "[email protected]");
列表联系人
列出与 pagination 和可选过滤的联系人 .
// Simple list with default limit (100)
const result = await loops.listContacts(ctx);
// List with filters and pagination
const result = await loops.listContacts(ctx, {
userGroup: "premium",
subscribed: true,
limit: 20,
offset: 0
});
console.log(result.contacts); // Array of contacts
console.log(result.total); // Total count matching filters
console.log(result.hasMore); // Boolean indicating if more pages exist
删除联系人
await loops.deleteContact(ctx, "[email protected]");
批次创建联系人
await loops.batchCreateContacts(ctx, {
contacts: [
{ email: "[email protected]", firstName: "John" },
{ email: "[email protected]", firstName: "Jane" },
],
});
未订阅/ 重新订阅
await loops.unsubscribeContact(ctx, "[email protected]");
await loops.resubscribeContact(ctx, "[email protected]");
计数联系人
// Count all contacts
const total = await loops.countContacts(ctx, {});
// Count by filter
const premium = await loops.countContacts(ctx, {
userGroup: "premium",
subscribed: true,
});
电子邮件发送
发送交易邮件
await loops.sendTransactional(ctx, {
transactionalId: "template-id-from-loops",
email: "[email protected]",
dataVariables: {
name: "John",
orderId: "12345",
},
});
发送事件( Triggers 工作流程)
await loops.sendEvent(ctx, {
email: "[email protected]",
eventName: "purchase_completed",
eventProperties: {
product: "Premium Plan",
amount: 99.99,
},
});
触发循环( 自动序列)
await loops.triggerLoop(ctx, {
loopId: "loop-id-from-loops",
email: "[email protected]",
dataVariables: {
onboardingStep: "welcome",
},
});
监测和分析
获取电子邮件统计
const stats = await loops.getEmailStats(ctx, {
timeWindowMs: 3600000, // Last hour
});
console.log(stats.totalOperations); // Total emails sent
console.log(stats.successfulOperations); // Successful sends
console.log(stats.failedOperations); // Failed sends
console.log(stats.operationsByType); // Breakdown by type
console.log(stats.uniqueRecipients); // Unique email addresses
检测垃圾邮件模式
// Detect recipients with suspicious activity
const spamRecipients = await loops.detectRecipientSpam(ctx, {
timeWindowMs: 3600000,
maxEmailsPerRecipient: 10,
});
// Detect actors with suspicious activity
const spamActors = await loops.detectActorSpam(ctx, {
timeWindowMs: 3600000,
maxEmailsPerActor: 50,
});
// Detect rapid-fire patterns
const rapidFire = await loops.detectRapidFirePatterns(ctx, {
timeWindowMs: 60000, // Last minute
maxEmailsPerWindow: 5,
});
限制费率
检查利率限制
// Check recipient rate limit
const recipientCheck = await loops.checkRecipientRateLimit(ctx, {
email: "[email protected]",
timeWindowMs: 3600000, // 1 hour
maxEmails: 10,
});
if (!recipientCheck.allowed) {
throw new Error(`Rate limit exceeded. Try again after ${recipientCheck.retryAfter}ms`);
}
// Check actor rate limit
const actorCheck = await loops.checkActorRateLimit(ctx, {
actorId: "user123",
timeWindowMs: 60000, // 1 minute
maxEmails: 20,
});
// Check global rate limit
const globalCheck = await loops.checkGlobalRateLimit(ctx, {
timeWindowMs: 60000,
maxEmails: 1000,
});
示例: 利率限制的电子邮件发送
export const sendTransactionalWithRateLimit = action({
args: {
transactionalId: v.string(),
email: v.string(),
actorId: v.optional(v.string()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
const actorId = args.actorId ?? identity.subject;
// Check rate limit before sending
const rateLimitCheck = await loops.checkActorRateLimit(ctx, {
actorId,
timeWindowMs: 60000, // 1 minute
maxEmails: 10,
});
if (!rateLimitCheck.allowed) {
throw new Error(
`Rate limit exceeded. Please try again after ${rateLimitCheck.retryAfter}ms.`
);
}
// Send email
return await loops.sendTransactional(ctx, {
...args,
actorId,
});
},
});
使用 API 帮助工具
该部分还出口: api() 较容易再导出的工具 :
import { Loops } from "@devwithbobby/loops";
import { components } from "./_generated/api";
const loops = new Loops(components.loops);
// Export all functions at once
export const {
addContact,
updateContact,
sendTransactional,
sendEvent,
triggerLoop,
countContacts,
listContacts,
// ... all other functions
} = loops.api();
安全警告 : 这个 api() 帮助导出不认证的函数 。 在生产中总是用认证检查来包装这些函数 :
export const addContact = action({
args: { email: v.string(), ... },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
return await loops.addContact(ctx, args);
},
});
安保最佳做法
- 总是添加认证 - 用认证检查包住所有函数
- 使用环境变量 - 在 Convex 环境变量中存储 API 密钥( 绝不是硬码)
- 执行率限制 - 使用内置费率限制查询,防止滥用
- 虐待监测 - 利用垃圾邮件检测查询来识别可疑模式
- 解密错误 - 不要向客户披露敏感错误细节
认证示例
所有功能都应被认证所包裹:
export const addContact = action({
args: { email: v.string(), ... },
handler: async (ctx, args) => {
// Add authentication check
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
// Add authorization checks if needed
// if (!isAdmin(identity)) throw new Error("Forbidden");
return await loops.addContact(ctx, args);
},
});
环境变量
设定 LOOPS_API_KEY 在您的凸起环境中 :
(原始内容存档于2018-09-31). Via CLI:
npx convex env set LOOPS_API_KEY "your-api-key"
Via Dashboard : (英语)
- 转到您的 Convex 板
- 导航到设置 - > 环境变量
- 添加
LOOPS_API_KEY与您的循环。 所以 API 密钥值
获取您的 API 密钥 循环。 所以 Dashboard。 。 。 .
从不 直接在代码中或通过生产中的函数选项通过 API 密钥。 总是使用环境变量.
监视率限制( R)
组件自动将所有电子邮件操作登录到 emailOperations 用于监测的表格。 使用内置查询到 :
- 跟踪电子邮件统计 - 参看发送总量、成功率/失败率、按类型分列的细目
- 检测垃圾邮件模式 - 查明接受者或行为者的可疑活动
- 执行费率限额 - 防止受虐者、行为者或全球费率限额
- 虐待监测 - 检测出快火模式和不寻常的发送行为
所有监测查询均可通过下列途径获得: Loops 客户端 - 见 监测和分析 上文关于用法示例的部分.
发展
地方发展
要在开发中使用此组件并进行活式重载:
bun run dev:backend
此启动 Convex dev 以 --live-component-sources 启用,允许立即反映更改.
大楼
npm run build
测试
npm test
项目结构
src/
component/ # The Convex component
convex.config.ts # Component configuration
schema.ts # Database schema
lib.ts # Component functions
validators.ts # Zod validators
tables/ # Table definitions
client/ # Client library
index.ts # Loops client class
types.ts # TypeScript types
example/ # Example app
convex/
example.ts # Example usage
API 覆盖范围
此组件执行以下循环. so API 端点 :
- 创建/ 更新联系人
- 删除联系人
- 查找联系人
- 批次创建联系人
- 取消订阅/重新订阅联系人
- 联系人(海关执行)
- 清单联系人(海关执行)
- 发送交易邮件
- 发送事件
- 触发循环
捐款
欢迎捐款! 请打开一个问题或提交拉动请求.
许可证
阿帕奇 - 2.0