前言

Amazon Simple Notification Service (Amazon SNS) is a web service that coordinates and manages the delivery or sending of messages to subscribing endpoints or clients. In Amazon SNS, there are two types of clients—publishers and subscribers—also referred to as producers and consumers. Publishers communicate asynchronously with subscribers by producing and sending a message to a topic, which is a logical access point and communication channel. Subscribers (i.e., web servers, email addresses, Amazon SQS queues, AWS Lambda functions) consume or receive the message or notification over one of the supported protocols (i.e., Amazon SQS, HTTP/S, email, SMS, Lambda) when they are subscribed to the topic. [1]

正如SNS的介绍所述,SNS是AWS提供的一个消息收发服务,它包括了诸如消息推送、短信、电子邮件等服务。AWS官方文档提供了非常多的内容,但提供的示例代码是以Java或.Net为主,关于Node.js的直接资料较少,所以这里便来介绍如何使用AWS SNS服务发送短信。

Node.js中使用SNS发送短信

在Node.js中使用AWS的服务,需要先安装aws-sdk依赖。AWS SDK中包括了众多服务的接口,在这里我们需要的是AWS.SNS类。首先,需要实例化AWS.SNS对象,其构造函数的参数为一个对象,通常需要包括accessKeyIdsecretAccessKeyregion等属性。在AWS IAM中,可生成并下载使用的用户对应的accessKeyIdsecretAccessKey。需要注意的是,使用的用户需要在IAM中设置SNS对应的权限。

const AWS = require('aws-sdk');

const options = {
  accessKeyId: 'String',
  secretAccessKey: 'String',
  apiVersion: '2010-03-31',
};

const snsService = new AWS.SNS(options);

通过AWS.SNS类的实例,就可以使用其进行SNS服务的相关操作。本文的主题为使用SNS服务发送短信,所以接下来即可通过AWS.SNS实例的publish方法以短信形式发送消息。

AWS SDK for Node.js中,publish方法接收一个Object类型的参数,它其中包括MessageMessageAttributesMessageStructurePhoneNumberSubjectTargetArn以及TopicArn属性。publish是一个SNS中通用的方法,发送邮件、消息推送也是通过它进行完成,所以在发送短信时部分的参数不是必须的。下面是一个发送短信所需最少参数的例子。

const params = {
  Message: text,
  MessageAttributes: {
    'AWS.SNS.SMS.SMSType': {
      DataType: 'String',
      StringValue: 'Transactional', // Transactional or Promotional
    },
    // AWS.SNS.SMS.MaxPrice
    // AWS.SNS.SMS.SenderId
  },
  PhoneNumber: phoneNumber, // 电话号码,需要遵从E.164格式
};

MessageAttributes中可以包含多个属性,例如上述例子中的AWS.SNS.SMS.SMSType。MessageAttribute的值主要包括DataTypeStringValueBinaryValue,其根据DataType的值决定需要的是StringValue或是BinaryValueStringValue接受一组以UTF-8编码的字符串,而BinaryValue可以接受任何二进制值,例如压缩后的数据、图片等。

MessageAttributesAWS.SNS.SMS.SMSType属性中,其值需为TransactionalPromotional。二者的区别为Transactional更为可靠,但其价格通常更为昂贵,一般用于发送较为重要的消息(如短信验证码等),而Promotional一般用于发送推广信息。另外AWS.SNS.SMS.MaxPrice为愿意为发送消息支付以美元为单位的最高金额;AWS.SNS.SMS.SenderId为一个在接收设备上显示的自定义ID,但其支持程度受所在地区限制,如中国便不支持SenderId的显示。

使用上面定义的参数调用publish方法即可发送短信。publish方法提供了基于Promise的异步使用方法,只需将代码修改为:const response = await snsService.publish(params).promise();。同理,AWS SDK中的其它方法也可通过添加.promise()将其异步形式从回调改为基于Promise。

snsService.publish(params, (err, data) => {
  if (err) {
    // ...
  }
  // ...
})

返回结果

在参数错误、权限不足等情况下,调用publish方法将会抛出诸如InvalidParameterAuthorizationError等错误,可根据其具体错误信息判断错误原因。调用publish方法将会返回下述结果。其中,MessageId为该消息的唯一标识符,当开启CloudWatch Logs后可通过该标识符获取消息的传输信息。

{
  "ResponseMetadata":{
    "RequestId":"bfb2a062-c201-5d34-a7d8-f5fd653b27f9"
  },
  "MessageId":"2b38eec7-a3f0-5679-a116-bb5804cadcb4"
}

未收到短信

调用publish接口成功并正常返回结果,不代表短信发送成功。短信发送失败的原因可能是下列其中一个:

  • 被电话运营商作为垃圾消息屏蔽
  • 目标已加入黑名单
  • 电话号码无效
  • 消息正文无效
  • 电话运营商已屏蔽此消息
  • 电话运营商目前无法访问/不可用
  • 电话已屏蔽SMS
  • 电话已加入黑名单
  • 电话当前无法访问/可用
  • 电话号码已退出
  • 此传输会超过最高价格
  • 尝试联系电话时发生未知错误

publish方法返回的结果无法得知短信发送是否成功,只有在开启CloudWatch Logs功能后才能从日志中获取短信是否发送成功及发送失败的原因。

发送短信至多个号码

使用AWS SNS可以实现同时发送短信至多个手机号码,其中一种实现方法即为多次调用publish方法向不同号码发送短信。但重复多次调用publish方法需要耗费更多的时间,所以在此也可选用另一种方法,即使用Topic发送短信至多个号码。该方法的步骤是在创建一个topic后,为多个手机号码订阅该topic。订阅后在发送消息时,会将消息发送至订阅该topic的端点中,此处的端点即为电话号码。

在发送信息至多个号码前,需要创建一个新的Topic并使用subscribe方法为发送的号码订阅该Topic。Topic即一个消息信道,当有消息发送至topic中时,SNS服务将会将该消息发送至订阅该topic的终端节点中。Topic可通过AWS Console创建,也可通过AWS SDK中的createTopic方法创建。

通过SDK调用createTopic创建Topic,需要包括NameAttributesTags三个参数,其中Name为必须的参数。Attributes参数。

const params = {
  Name: 'STRING_VALUE', // Topic 名称
  Attributes: {
    '<attributeName>': 'STRING_VALUE',
    /* '<attributeName>': ... */
  },
  Tags: [
    {
      Key: 'STRING_VALUE', // Tag key
      Value: 'STRING_VALUE', // Tag value
    },
    // ...
  ],
};

const response = await snsService.createTopic(params).promise();
// response.TopicArn

完成topic的创建之后,即可为需要发送短信的电话号码订阅所创建的topic。为电话号码订阅topic需要使用subscribe方法,它必填的参数包括TopicArnEndpoint以及Protocol。其中,Protocol的值必须为smsTopicArn的值为创建的Topic Arn地址,Endpoint为订阅Topic的电话号码。另外,它的参数还包括有Attributes以及ReturnSubscriptionArn两个属性。Attributes属性如同publish方法的MessageAttributes属性,通过它的DeliveryPolicyFilterPolicyRawMessageDelivery配置订阅的相关设置。

const params = {
  Protocol: 'sms',
  TopicArn: topicArn,
  // Attributes: {
  //   '<attributeName>': 'STRING_VALUE',
  // },
  Endpoint: phoneNumber,
  // ReturnSubscriptionArn: false,
};

const response = await snsService.subscribe(params).promise();

在为需要发送短信的号码订阅topic后,即可调用publish方法发送消息。发送短信至多个号码时调用publish方法的参数与发送至单个号码大致相同,唯一需要修改的地方为不再需要设置PhoneNumber,而是需要设置TopicArn属性,它的值即为上面调用createTopic所得到的TopicArn字段。

const params = {
  Message: text,
  MessageAttributes: {
    // 发送短信至topic同样可设置SMSType, MaxPrice, SenderId三个属性
    'AWS.SNS.SMS.SMSType': {
      DataType: 'String',
      StringValue: 'Transactional', // Transactional or Promotional
    },
    // AWS.SNS.SMS.MaxPrice
    // AWS.SNS.SMS.SenderId
  },
  TopicArn: topicArn, // 群发的topic ARN地址
};

const response = await snsService.publish(params).promise();

结束语

AWS的free tier提供了每月100条的免费短信可以使用,但需要注意的是此处的免费短信接收方为美国号码。若要发送短信至国内号码,价格约为0.01531 USD(约0.11元),该价格相对于国内服务商较为昂贵。另外,SNS发送短信至国内时失败率也较高,且在发送失败的情况下也是按正常价格收取费用。在实际开发中,若不是以海外业务为主的情况下,可考虑国内服务商提供的短信服务。

参考资料

  1. Amazon Simple Notification Service Developer Guide
  2. AWS Javascript SDK Docs