Skip to content

Commit

Permalink
feat: reinstate upgrade whatsapp support on Twilio Programmable Messa…
Browse files Browse the repository at this point in the history
…ging to support Content API (#1266)

Resinstates WhatsApp support for the content API by reinstating:
#1249


After further discussion, it looks like we do have users who are using
custom SMS templates (e.g. "Your <company> code is {{.code}} ")

We were notified that this is required as alphanumeric senders require
the company name in the message. This means we will need to support the
Content API as custom messages aren't allowed under Programmable
Messaging API + WhatsApp Template


There will be 2 more PRs after this:
1. PR to allow for backward compatibility with Basic Authentication
Template
2. PR to add ContentSid env var
3. Dashboard PR to expose variable
  • Loading branch information
J0 authored Oct 10, 2023
1 parent 76c8eeb commit 00ee75c
Show file tree
Hide file tree
Showing 10 changed files with 31 additions and 17 deletions.
2 changes: 1 addition & 1 deletion internal/api/phone.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (a *API) sendPhoneConfirmation(ctx context.Context, tx *storage.Connection,
return "", err
}

messageID, err = smsProvider.SendMessage(phone, message, channel)
messageID, err = smsProvider.SendMessage(phone, message, channel, otp)
if err != nil {
return messageID, err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/api/phone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type TestSmsProvider struct {
SentMessages int
}

func (t *TestSmsProvider) SendMessage(phone string, message string, channel string) (string, error) {
func (t *TestSmsProvider) SendMessage(phone, message, channel, otp string) (string, error) {
t.SentMessages += 1
return "", nil
}
Expand Down
2 changes: 1 addition & 1 deletion internal/api/sms_provider/messagebird.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewMessagebirdProvider(config conf.MessagebirdProviderConfiguration) (SmsPr
}, nil
}

func (t *MessagebirdProvider) SendMessage(phone string, message string, channel string) (string, error) {
func (t *MessagebirdProvider) SendMessage(phone, message, channel, otp string) (string, error) {
switch channel {
case SMSProvider:
return t.SendSms(phone, message)
Expand Down
2 changes: 1 addition & 1 deletion internal/api/sms_provider/sms_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func init() {
}

type SmsProvider interface {
SendMessage(phone, message, channel string) (string, error)
SendMessage(phone, message, channel, otp string) (string, error)
}

func GetSmsProvider(config conf.GlobalConfiguration) (SmsProvider, error) {
Expand Down
5 changes: 4 additions & 1 deletion internal/api/sms_provider/sms_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() {
Desc string
TwilioResponse *gock.Response
ExpectedError error
OTP string
}{
{
Desc: "Successfully sent sms",
Expand All @@ -97,6 +98,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() {
Body: message,
MessageSID: "abcdef",
}),
OTP: "123456",
ExpectedError: nil,
},
{
Expand All @@ -123,6 +125,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() {
MoreInfo: "error",
Status: 500,
}),
OTP: "123456",
ExpectedError: &twilioErrResponse{
Code: 500,
Message: "Internal server error",
Expand All @@ -134,7 +137,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() {

for _, c := range cases {
ts.Run(c.Desc, func() {
_, err = twilioProvider.SendSms(phone, message, SMSProvider)
_, err = twilioProvider.SendSms(phone, message, SMSProvider, c.OTP)
require.Equal(ts.T(), c.ExpectedError, err)
})
}
Expand Down
2 changes: 1 addition & 1 deletion internal/api/sms_provider/textlocal.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewTextlocalProvider(config conf.TextlocalProviderConfiguration) (SmsProvid
}, nil
}

func (t *TextlocalProvider) SendMessage(phone string, message string, channel string) (string, error) {
func (t *TextlocalProvider) SendMessage(phone, message, channel, otp string) (string, error) {
switch channel {
case SMSProvider:
return t.SendSms(phone, message)
Expand Down
28 changes: 19 additions & 9 deletions internal/api/sms_provider/twilio.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,41 @@ func NewTwilioProvider(config conf.TwilioProviderConfiguration) (SmsProvider, er
}, nil
}

func (t *TwilioProvider) SendMessage(phone string, message string, channel string) (string, error) {
func (t *TwilioProvider) SendMessage(phone, message, channel, otp string) (string, error) {
switch channel {
case SMSProvider, WhatsappProvider:
return t.SendSms(phone, message, channel)
return t.SendSms(phone, message, channel, otp)
default:
return "", fmt.Errorf("channel type %q is not supported for Twilio", channel)
}
}

// Send an SMS containing the OTP with Twilio's API
func (t *TwilioProvider) SendSms(phone, message, channel string) (string, error) {
func (t *TwilioProvider) SendSms(phone, message, channel, otp string) (string, error) {
sender := t.Config.MessageServiceSid
receiver := "+" + phone
if channel == WhatsappProvider {
receiver = channel + ":" + receiver
if isPhoneNumber.MatchString(formatPhoneNumber(sender)) {
sender = channel + ":" + sender
}
}
body := url.Values{
"To": {receiver}, // twilio api requires "+" extension to be included
"Channel": {channel},
"From": {sender},
"Body": {message},
}
if channel == WhatsappProvider {
receiver = channel + ":" + receiver
if isPhoneNumber.MatchString(formatPhoneNumber(sender)) {
sender = channel + ":" + sender
}
// Used to substitute OTP. See https://www.twilio.com/docs/content/whatsappauthentication for more details
contentVariables := fmt.Sprintf(`{"1": "%s"}`, otp)
// Programmable Messaging (WhatsApp) takes in different set of inputs
body = url.Values{
"To": {receiver}, // twilio api requires "+" extension to be included
"Channel": {channel},
"From": {sender},
"ContentSid": {t.Config.ContentSid},
"ContentVariables": {contentVariables},
}
}
client := &http.Client{Timeout: defaultTimeout}
r, err := http.NewRequest("POST", t.APIPath, strings.NewReader(body.Encode()))
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/api/sms_provider/twilio_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func NewTwilioVerifyProvider(config conf.TwilioVerifyProviderConfiguration) (Sms
}, nil
}

func (t *TwilioVerifyProvider) SendMessage(phone string, message string, channel string) (string, error) {
func (t *TwilioVerifyProvider) SendMessage(phone, message, channel, otp string) (string, error) {
switch channel {
case SMSProvider, WhatsappProvider:
return t.SendSms(phone, message, channel)
Expand Down
2 changes: 1 addition & 1 deletion internal/api/sms_provider/vonage.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func NewVonageProvider(config conf.VonageProviderConfiguration) (SmsProvider, er
}, nil
}

func (t *VonageProvider) SendMessage(phone string, message string, channel string) (string, error) {
func (t *VonageProvider) SendMessage(phone, message, channel, otp string) (string, error) {
switch channel {
case SMSProvider:
return t.SendSms(phone, message)
Expand Down
1 change: 1 addition & 0 deletions internal/conf/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ type TwilioProviderConfiguration struct {
AccountSid string `json:"account_sid" split_words:"true"`
AuthToken string `json:"auth_token" split_words:"true"`
MessageServiceSid string `json:"message_service_sid" split_words:"true"`
ContentSid string `json:"content_sid" split_words:"true"`
}

type TwilioVerifyProviderConfiguration struct {
Expand Down

0 comments on commit 00ee75c

Please sign in to comment.