@devwithbobby/loops

中文文档
0.2.0发布于 2026/01/20Apache-2.0TypeScriptES Module近 30 天下载 2838

与 Loops. so 电子邮件营销平台集成的 Convex 组件

中文使用文档
文档目录25 节
  1. 特征
  2. 安装
  3. 快速启动
  4. 1. 安装和挂载组件
  5. 2. 设置环境变量
  6. 3. 使用本构成部分
  7. API 参考
  8. 联系人管理
  9. 电子邮件发送
  10. 监测和分析
  11. 限制费率
  12. 使用 API 帮助工具
  13. 安保最佳做法
  14. 认证示例
  15. 环境变量
  16. 监视率限制( R)
  17. 发展
  18. 地方发展
  19. 大楼
  20. 测试
  21. 项目结构
  22. API 覆盖范围
  23. 捐款
  24. 许可证
  25. 资源

@ devwithbby/ loops (英语)

npm version

用于与 循环。 。 电子邮件营销平台. 发送交易邮件,管理联系人,触发回路,并用内置的垃圾邮件检测和速率限制来监控电子邮件操作.

特征

  • 联系人管理 - 创建、更新、查找、列表和删除联系人
  • 交易邮件 - 发送带有模板的一次性电子邮件
  • 活动 - 基于事件的触发电子邮件工作流程
  • 循环 - 触发自动电子邮件序列
  • 监测 - 用垃圾邮件检测跟踪所有电子邮件操作
  • 限制费率 - 内在费率限制对预防虐待的查询
  • 类型安全 - 附有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:

  1. 转到设置 - > 环境变量
  2. 添加 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);
  },
});

安保最佳做法

  1. 总是添加认证 - 用认证检查包住所有函数
  2. 使用环境变量 - 在 Convex 环境变量中存储 API 密钥( 绝不是硬码)
  3. 执行率限制 - 使用内置费率限制查询,防止滥用
  4. 虐待监测 - 利用垃圾邮件检测查询来识别可疑模式
  5. 解密错误 - 不要向客户披露敏感错误细节

认证示例

所有功能都应被认证所包裹:

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 : (英语)

  1. 转到您的 Convex 板
  2. 导航到设置 - > 环境变量
  3. 添加 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

资源

版本与发布记录

@devwithbobby/loops 共发布 21 个版本,首次发布于 2025/11/26;过去 12 个月发布了 21 个版本。

版本发布日期距今
0.2.0latest2026/01/209 个月前
0.1.192026/01/209 个月前
0.1.182026/01/129 个月前
0.1.172025/12/0311 个月前
0.1.162025/12/0311 个月前
0.1.152025/12/0311 个月前

关于 @devwithbobby/loops 的常见问题

@devwithbobby/loops 是什么?

与 Loops. so 电子邮件营销平台集成的 Convex 组件

如何安装 @devwithbobby/loops?

在项目目录运行 npm install @devwithbobby/loops,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:convex(^1.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@devwithbobby/loops 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@devwithbobby/loops 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@devwithbobby/loops 可以商用吗?

@devwithbobby/loops 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@devwithbobby/loops 的下载量有多少?

根据 npm 官方下载统计,@devwithbobby/loops 在 2026/10/12 – 2026/11/10 这 30 天内被下载 2,838 次,日均约 95 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@devwithbobby/loops 还在维护吗?

最新版本 0.2.0 发布于 2026/01/20(9 个月前);至今共发布 21 个版本,其中过去 12 个月发布了 21 个;首次发布于 2025/11/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。