# @devwithbobby/loops 中文文档

规范地址：https://npmcn.com/package/%40devwithbobby/loops/

版本：0\.2\.0
npm 元数据更新时间：2026-01-19T22:28:41.389Z
中文译文更新时间：2026-11-12T05:09:01.403Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40devwithbobby/loops
原文对照：https://npmcn.com/package/%40devwithbobby/loops/?lang=original
项目主页：https://github.com/robertalv/loops#readme
源码仓库：https://github.com/robertalv/loops

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：319 kB，70 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：3 个
- peer 依赖：convex ^1\.0\.0
- 首次发布：2025-11-25T23:01:18.192Z
- 最新版本发布：2026-01-19T22:28:41.272Z
- 近 30 天下载量：2838（2026-10-12 至 2026-11-10，npm 官方下载统计）
- 已发布版本数：21（近 12 个月 21 个）

## 中文简介

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

## 安装

```sh
npm install @devwithbobby/loops
```

也可以使用：`pnpm add @devwithbobby/loops`、`yarn add @devwithbobby/loops`、`bun add @devwithbobby/loops`

## README

# @ devwithbby/ loops (英语)

[![npm version](https://img.shields.io/npm/v/@devwithbobby/loops.svg)](https://www.npmjs.com/package/@devwithbobby/loops)

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

## 特征

-   **联系人管理** - 创建、更新、查找、列表和删除联系人
-   **交易邮件** - 发送带有模板的一次性电子邮件
-   **活动** - 基于事件的触发电子邮件工作流程
-   **循环** - 触发自动电子邮件序列
-   **监测** - 用垃圾邮件检测跟踪所有电子邮件操作
-   **限制费率** - 内在费率限制对预防虐待的查询
-   **类型安全** - 附有Zod验证的完整类型脚本支持

## 安装

```bash
npm install @devwithbobby/loops
# or
bun add @devwithbobby/loops
```

## 快速启动

### 1\. 安装和挂载组件

在你身边 `convex/convex.config.ts`数字 :

```typescript
import loops from "@devwithbobby/loops/convex.config";
import { defineApp } from "convex/server";

const app = defineApp();
app.use(loops);

export default app;
```

### 2\. 设置环境变量

**重要: 在使用组件前先设置您的循环 API 密钥 .**

```bash
npx convex env set LOOPS_API_KEY "your-loops-api-key-here"
```

**或者通过Convex Dashboard:**

1.  转到设置 - > 环境变量
2.  添加 `LOOPS_API_KEY` 与您的循环。 所以 API 密钥

获取您的 API 密钥 [循环。 所以 Dashboard](https://app.loops.so/settings?page=api)。 。 。 .

### 3\. 使用本构成部分

在你身边 `convex/functions.ts` (或任何凸起文件):

```typescript
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 参考

### 联系人管理

#### 添加或更新联系人

```typescript
await loops.addContact(ctx, {
  email: "user@example.com",
  firstName: "John",
  lastName: "Doe",
  userId: "user123",
  source: "webapp",
  subscribed: true,
  userGroup: "premium",
});
```

#### 更新联系人

```typescript
await loops.updateContact(ctx, "user@example.com", {
  firstName: "Jane",
  userGroup: "vip",
});
```

#### 查找联系人

```typescript
const contact = await loops.findContact(ctx, "user@example.com");
```

#### 列表联系人

列出与 pagination 和可选过滤的联系人 .

```typescript
// 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
```

#### 删除联系人

```typescript
await loops.deleteContact(ctx, "user@example.com");
```

#### 批次创建联系人

```typescript
await loops.batchCreateContacts(ctx, {
  contacts: [
    { email: "user1@example.com", firstName: "John" },
    { email: "user2@example.com", firstName: "Jane" },
  ],
});
```

#### 未订阅/ 重新订阅

```typescript
await loops.unsubscribeContact(ctx, "user@example.com");
await loops.resubscribeContact(ctx, "user@example.com");
```

#### 计数联系人

```typescript
// Count all contacts
const total = await loops.countContacts(ctx, {});

// Count by filter
const premium = await loops.countContacts(ctx, {
  userGroup: "premium",
  subscribed: true,
});
```

### 电子邮件发送

#### 发送交易邮件

```typescript
await loops.sendTransactional(ctx, {
  transactionalId: "template-id-from-loops",
  email: "user@example.com",
  dataVariables: {
    name: "John",
    orderId: "12345",
  },
});
```

#### 发送事件( Triggers 工作流程)

```typescript
await loops.sendEvent(ctx, {
  email: "user@example.com",
  eventName: "purchase_completed",
  eventProperties: {
    product: "Premium Plan",
    amount: 99.99,
  },
});
```

#### 触发循环( 自动序列)

```typescript
await loops.triggerLoop(ctx, {
  loopId: "loop-id-from-loops",
  email: "user@example.com",
  dataVariables: {
    onboardingStep: "welcome",
  },
});
```

### 监测和分析

#### 获取电子邮件统计

```typescript
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
```

#### 检测垃圾邮件模式

```typescript
// 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,
});
```

### 限制费率

#### 检查利率限制

```typescript
// Check recipient rate limit
const recipientCheck = await loops.checkRecipientRateLimit(ctx, {
  email: "user@example.com",
  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,
});
```

**示例: 利率限制的电子邮件发送**

```typescript
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()` 较容易再导出的工具 :

```typescript
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()` 帮助导出不认证的函数 。 在生产中总是用认证检查来包装这些函数 :

```typescript
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.  **解密错误** - 不要向客户披露敏感错误细节

### 认证示例

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

```typescript
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:**

```bash
npx convex env set LOOPS_API_KEY "your-api-key"
```

**Via Dashboard : (英语)**

1.  转到您的 Convex 板
2.  导航到设置 - > 环境变量
3.  添加 `LOOPS_API_KEY` 与您的循环。 所以 API 密钥值

获取您的 API 密钥 [循环。 所以 Dashboard](https://app.loops.so/settings?page=api)。 。 。 .

**从不** 直接在代码中或通过生产中的函数选项通过 API 密钥。 总是使用环境变量.

## 监视率限制( R)

组件自动将所有电子邮件操作登录到 `emailOperations` 用于监测的表格。 使用内置查询到 :

-   **跟踪电子邮件统计** - 参看发送总量、成功率/失败率、按类型分列的细目
-   **检测垃圾邮件模式** - 查明接受者或行为者的可疑活动
-   **执行费率限额** - 防止受虐者、行为者或全球费率限额
-   **虐待监测** - 检测出快火模式和不寻常的发送行为

所有监测查询均可通过下列途径获得: `Loops` 客户端 - 见 [监测和分析](https://npmcn.com/package/%40devwithbobby/loops/#readme-monitoring--analytics) 上文关于用法示例的部分.

## 发展

### 地方发展

要在开发中使用此组件并进行活式重载:

```bash
bun run dev:backend
```

此启动 Convex dev 以 `--live-component-sources` 启用,允许立即反映更改.

### 大楼

```bash
npm run build
```

### 测试

```bash
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

## 资源

-   [Loops.so 文档](https://loops.so/docs)
-   [Convex 组件文档](https://www.convex.dev/components)
-   [Convex 环境变量](https://docs.convex.dev/production/environment-variables)

## 常见问题

### @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 和提交记录判断。

