如何在Twilio上存储退出数据

161 阅读10分钟

保持合规性是你的信息传递战略的一个重要部分。它确保你的发件人得到运营商的高度信任,并因此经历较低的过滤和较低的被阻止风险

保持一个干净的退出名单是合规信息传递的关键支柱之一。Twilio平台通过自动退出管理帮助你维护客户的信任(更多信息在这里),这是一个很好的开始,但在某些情况下,退出信息可能会丢失。此外,Twilio还没有提供一个API来检索退出数据。

所有这些都说明:我们Twilio强烈建议你自己维护和存储退出数据。在这篇博文中,我将向你展示如何在Twilio平台上通过无服务器函数同步来存储退出信息。

教程先决条件

在我们开始构建之前,你需要确保你有一个Twilio的账户。你可以在这里注册一个免费账户

一旦你有了账户,请注意以下细节--你以后会需要它们。

有了这些,你就可以开始了。

正如我提到的,我们将利用Twilio功能和同步。但在我们开始写代码之前,这里是我们的高级计划。

  1. 创建一个消息服务并添加一个电话号码
  2. 创建一个Sync服务,并在其中创建一个Map对象;Map将是我们的opt-out数据存储。
  3. 创建一个函数,一旦客户向电话号码发送消息,该函数将被调用。该函数将负责。
    1. 分析消息的主体,看它是否是opt-out或opt-in的字样
    2. 如果邮件正文是 "退出 "字样,该函数将把发件人号码添加到同步地图中。
    3. 如果正文是一个选择加入的词,该函数将从同步地图中删除发件人号码。
  4. 更新Messaging Service的设置,将传入信息的webhook指向该函数的URL。

下面是我们要建立的解决方案的图示。

Storing Opt-Out data on Twilio with Functions and Sync Diagram

同步设置

如前所述,我们将在同步地图中存储我们的选择退出数据。

首先,我们需要一个同步服务,然后在该服务中建立一个地图。在你的Twilio账户中,一个名为 "默认服务 "的同步服务应该已经存在--使用这个服务是可以的,在这篇博文中,我将使用默认服务。或者,你可以在Twilio控制台的同步服务页面创建一个新的服务。

为了以后配置环境变量,你将需要同步服务的SID。如果你进入Twilio控制台的同步服务页面,你可以看到所有服务的SID(注意你将使用的服务的SID)。

接下来,导航到所选的同步服务,在左边选择地图,然后创建一个新的同步地图。你必须为你的新地图提供一个名称--确保把时间留到空白。地图创建后,记下它的SID......我们以后会需要它。

一旦你有了一个同步服务和地图,我们就可以继续了。

开发者环境设置

在我们继续之前,让我们确保你有你需要的软件。

我将在本教程中使用Typescript,但用JavaScript也应该可以。

现在我们可以开始编码了

创建项目

我们将首先使用Twilio Serverless Toolkit创建一个项目。对于这个运行,在你的shell中发出以下命令。

twilio serverless:init opt-out-data-storage --typescript

这里有几个注意事项。

  • 在命令中,我使用opt-out-data-storage 作为我的项目名称,请随意使用不同的名称
  • 添加--typescript 参数将创建一个准备用于Typescript的项目。如果你喜欢JavaScript,你可以省略这个参数。

完成这一步后,你就有了一个可以在本地环境中运行或直接部署到Twilio Functions的项目。

你会发现在src/functions 文件夹下有几个函数的例子,在src/assets 下有几个资产的例子,你可以安全地删除它们。(或者你可以忽略它们,以你喜欢的方式为准)。

配置环境

为了运行代码,我们需要将环境变量设置到位。进入项目根目录下的.env ,并更新文件,使其具有以下键和值。确保将占位符替换为你在前面步骤中收集的值。

ACCOUNT_SID=<Twilio Account SID>
AUTH_TOKEN=<Twilio Auth Token>
SYNC_SERVICE_SID=<Sync Service SID>
SYNC_MAP_SID=<Sync Map SID>

创建功能

现在是时候创建你将在其中工作的文件了。导航到src/functions,并创建一个名为capture-opt-out.protected.ts的文件。

虽然你可以自由地为你的函数选择任何其他名称,但要确保名称以.protected.ts (或.protected.js )结尾,因为这定义了函数的可见性级别

我们将从下面的片段开始,因为它包含了完成一个函数的最基本代码。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

export const handler: ServerlessFunctionSignature = async function (
  context: Context,
  event: {},
  callback: ServerlessCallback,
) {
  callback(null, 'Hello from Twilio Functions!')
}

我们知道我们的函数将接受哪些参数,以及它将使用哪些环境变量。反映在代码中,这将看起来像。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

type InboundMessage = {
  Body: string,
  From: string
}

type OptOutContext = {
  SYNC_SERVICE_SID: string,
  SYNC_MAP_SID: string
}

export const handler: ServerlessFunctionSignature<OptOutContext, InboundMessage> = function (
  context: Context<OptOutContext>,
  event: InboundMessage,
  callback: ServerlessCallback,
) {
  callback(null, 'Hello from Twilio Functions!')
}

接下来,我们可以添加一个选择退出和选择加入的关键词列表(这里我将使用默认列表。你可以在你的信息服务中的 "退出管理 "部分找到默认值)。

如果需要,你可以利用Twilio的高级退出功能来修改列表,这篇博文详细描述了如何做到这一点。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

type InboundMessage = {
  Body: string,
  From: string
}

type OptOutContext = {
  SYNC_SERVICE_SID: string,
  SYNC_MAP_SID: string
}

const optOutWords = new Set<string>([
  'stop',
  'cancel',
  'end',
  'quit',
  'unsubscribe',
  'stopall',
])

const optInWords = new Set<string>([
  'start',
  'yes',
  'unstop',
])

export const handler: ServerlessFunctionSignature<OptOutContext, InboundMessage> = function (
  context: Context<OptOutContext>,
  event: InboundMessage,
  callback: ServerlessCallback,
) {
  callback(null, 'Hello from Twilio Functions!')
}

现在我们可以开始工作,一旦我们检测到选择退出或选择加入的关键词,就可以从地图上添加或删除一个号码的逻辑。

首先,我们将构建一个信息传递响应对象。我们将用它来发送反馈,我们将总是发送一个空的响应,这意味着没有消息将被发送到发送者那里。其次,我们将有if-else的三个分支:opt-out、opt-in和default。默认分支将不做任何事情;我们希望忽略所有不是opt-out或opt-in关键字的东西。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

type InboundMessage = {
  Body: string,
  From: string
}

type OptOutContext = {
  SYNC_SERVICE_SID: string,
  SYNC_MAP_SID: string
}

const optOutWords = new Set<string>([
  'stop',
  'cancel',
  'end',
  'quit',
  'unsubscribe',
  'stopall',
])

const optInWords = new Set<string>([
  'start',
  'yes',
  'unstop',
])

export const handler: ServerlessFunctionSignature<OptOutContext, InboundMessage> = function (
  context: Context<OptOutContext>,
  event: InboundMessage,
  callback: ServerlessCallback,
) {

  // Response object
  const response = new Twilio.twiml.MessagingResponse()

  if (optOutWords.has(event.Body.trim().toLowerCase())) {
    console.log('We\'ve got opt-out', event)
    // Opt-out path -> here we should add number to the map
    callback(null, response)
  } else if (optInWords.has(event.Body.trim().toLowerCase())) {
    console.log('We\'ve got opt-in', event)
    // Opt-in path -> here we should remove number from the map
    callback(null, response)
  } else {
    callback(null, response)
  }
}

接下来,我们将编写代码,如果我们检测到一个opt-out关键字,就在地图中添加一个数字。这里有几个注意事项。

  • 如果地图中已经包含了数字,就会出现一个异常。如果一个选择退出的用户已经再次发送了一个选择退出的关键字,这就会发生。我们可以忽略这个异常,因为我们存储中的状态不应该改变。
  • 我们将使用发件人号码作为我们新的地图项目的 "键",并将该 "数据 "保留为一个空的JSON对象。在生产中,你可以向数据对象添加其他有用的信息,例如时间戳。
// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

type InboundMessage = {
  Body: string,
  From: string
}

type OptOutContext = {
  SYNC_SERVICE_SID: string,
  SYNC_MAP_SID: string
}

const optOutWords = new Set<string>([
  'stop',
  'cancel',
  'end',
  'quit',
  'unsubscribe',
  'stopall',
])

const optInWords = new Set<string>([
  'start',
  'yes',
  'unstop',
])

export const handler: ServerlessFunctionSignature<OptOutContext, InboundMessage> = function (
  context: Context<OptOutContext>,
  event: InboundMessage,
  callback: ServerlessCallback,
) {

  // Response object
  const response = new Twilio.twiml.MessagingResponse()

  if (optOutWords.has(event.Body.trim().toLowerCase())) {
    console.log('We\'ve got opt-out', event)

    context.getTwilioClient()
      .sync
      .services(context.SYNC_SERVICE_SID)
      .syncMaps(context.SYNC_MAP_SID)
      .syncMapItems
      .create({
        key: event.From,
        data: {},
      })
      .then(_ => {
        console.log('Successfully stored map item')
        callback(null, response)
      })
      .catch(err => {
          console.log(err)
          callback(null, response)
        },
      )
  } else if (optInWords.has(event.Body.trim().toLowerCase())) {
    console.log('We\'ve got opt-in', event)
    // Opt-in path -> here we should remove number from the map
    callback(null, response)
  } else {
    callback(null, response)
  }
}

现在,让我们来看看最后一段代码--选择加入分支。在这里,我们从地图中删除一个发件人号码。再一次,如果该号码不在Map中,我们将忽略这个异常。

// Imports global types
import '@twilio-labs/serverless-runtime-types'
// Fetches specific types
import {
  Context,
  ServerlessCallback,
  ServerlessFunctionSignature,
} from '@twilio-labs/serverless-runtime-types/types'

type InboundMessage = {
  Body: string,
  From: string
}

type OptOutContext = {
  SYNC_SERVICE_SID: string,
  SYNC_MAP_SID: string
}

const optOutWords = new Set<string>([
  'stop',
  'cancel',
  'end',
  'quit',
  'unsubscribe',
  'stopall',
])

const optInWords = new Set<string>([
  'start',
  'yes',
  'unstop',
])

export const handler: ServerlessFunctionSignature<OptOutContext, InboundMessage> = function (
  context: Context<OptOutContext>,
  event: InboundMessage,
  callback: ServerlessCallback,
) {

  // Response object
  const response = new Twilio.twiml.MessagingResponse()

  if (optOutWords.has(event.Body.trim().toLowerCase())) {
    console.log('We\'ve got opt-out', event)

    context.getTwilioClient()
      .sync
      .services(context.SYNC_SERVICE_SID)
      .syncMaps(context.SYNC_MAP_SID)
      .syncMapItems
      .create({
        key: event.From,
        data: {},
      })
      .then(_ => {
        console.log('Successfully stored map item')
        callback(null, response)
      })
      .catch(err => {
          console.log(err)
          callback(null, response)
        },
      )
  } else if (optInWords.has(event.Body.trim().toLowerCase())) {
    console.log('We\'ve got opt-in', event)
    context.getTwilioClient()
      .sync
      .services(context.SYNC_SERVICE_SID)
      .syncMaps(context.SYNC_MAP_SID)
      .syncMapItems(event.From)
      .remove()
      .then(_ => {
        console.log('Successfully removed map item')
        callback(null, response)
      })
      .catch(err => {
          console.log(err)
          callback(null, response)
        },
      )
  } else {
    callback(null, response)
  }
}

这样,我们的函数就完成了

部署功能

下一步是将我们的代码部署到Twilio函数。要做到这一点,在你的项目根目录下执行以下命令。

npm run deploy

如果你在部署代码时遇到问题,请检查ACCOUNT_SIDAUTH_TOKEN 环境变量是否在你的.env 文件中正确配置了。

一旦部署完成,你将看到你新创建的函数的URL。它看起来会像下面这样。

Functions:
   [protected] https://capture-opt-out-0000-dev.twil.io/capture-opt-out

复制这个URL--我们已经准备好配置消息传递服务了。

配置一个消息传递服务

创建一个消息服务并为其添加一个电话号码不在本教程的范围之内。它可以通过Twilio控制台通过API实现

假设你已经有了一个消息服务,去你的Twilio控制台,导航到消息服务下的 "集成"部分。在 "集成"页面,找到 "传入信息"部分,选择 "发送网络钩子"单选按钮。将你在上一步中复制的功能URL输入到请求URL字段中,然后点击保存配置

测试

为了测试我们的解决方案,向你的信息服务发送者库中的一个号码发送短信,短信内容为 "停止"(假设 "停止 "是选择退出的关键词之一)。你应该收到一个关于成功退出的自动回复。

Opt-out Auto Reply

一旦你收到自动回复,请检查发件人号码是否被添加到选择退出地图中。

你可以在Twilio控制台或使用Twilio CLI查看地图内容。CLI的命令是:。

twilio api:sync:v1:services:maps:items:list --service-sid ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --map-sid=MPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --properties='key'

输出应该是这样的。

Key
+1234567890

如果你在地图中看到发件人号码,说明我们的设置符合预期--现在我们可以测试选入流程了。为此,向你的短信服务的发件人池中的一个号码发送一条短信,内容为 "START"(假设START是你的选择加入关键词之一)。

一旦你收到自动回复说你已经成功选择加入,检查发件人号码是否已经从选择退出地图中删除。

总结

使用Twilio功能和同步,我们建立了可靠的选择退出列表,并将其存储在Twilio平台上,而不需要依赖任何第三方工具。这些数据可以在任何时候使用Twilio控制台、Twilio CLI或REST API查看和导出。我们的解决方案最棒的地方在于,它并不局限于SMS;你可以为其他信息传递渠道,如WhatsApp或你采用的任何其他渠道存储退出信息。

该项目的完整代码可在GitHub上找到:github.com/kuschanton/…

喜欢你建立的东西吗?请看我们博客上的其他一些函数和无服务器教程