Skip to content

Commit f58a74e

Browse files
committed
feat(api): highlight webhook email payload
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48f6d946-ae22-4440-b7a1-44e939419b11
1 parent 6d1e18a commit f58a74e

2 files changed

Lines changed: 105 additions & 1 deletion

File tree

api/pkg/emails/hermes_notification_email_factory.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package emails
22

33
import (
44
"fmt"
5+
"strings"
56
"time"
67

78
"github.com/NdoleStudio/httpsms/pkg/events"
@@ -76,6 +77,8 @@ func (factory *hermesNotificationEmailFactory) DiscordSendFailed(user *entities.
7677
}
7778

7879
func (factory *hermesNotificationEmailFactory) WebhookSendFailed(user *entities.User, payload *events.WebhookSendFailedPayload) (*Email, error) {
80+
formattedPayload, formattedPayloadHTML := formatEventPayload(payload.EventPayload)
81+
7982
email := hermes.Email{
8083
Body: hermes.Body{
8184
Title: "Hello",
@@ -89,7 +92,11 @@ func (factory *hermesNotificationEmailFactory) WebhookSendFailed(user *entities.
8992
{Key: "Phone Number", Value: factory.formatPhoneNumber(payload.Owner)},
9093
{Key: "HTTP Response Code", Value: factory.formatHTTPResponseCode(payload.HTTPResponseStatusCode)},
9194
{Key: "Error Message / HTTP Response", Value: payload.ErrorMessage},
92-
{Key: "Event Payload", Value: payload.EventPayload},
95+
{
96+
Key: "Event Payload",
97+
Value: formattedPayload,
98+
UnsafeValue: formattedPayloadHTML,
99+
},
93100
},
94101
Actions: []hermes.Action{
95102
{
@@ -118,6 +125,7 @@ func (factory *hermesNotificationEmailFactory) WebhookSendFailed(user *entities.
118125
if err != nil {
119126
return nil, stacktrace.Propagate(err, "cannot generate text email")
120127
}
128+
text = formatWebhookSendFailedTextPayload(text, formattedPayload)
121129

122130
return &Email{
123131
ToEmail: user.Email,
@@ -127,6 +135,26 @@ func (factory *hermesNotificationEmailFactory) WebhookSendFailed(user *entities.
127135
}, nil
128136
}
129137

138+
func formatWebhookSendFailedTextPayload(text string, formattedPayload string) string {
139+
const (
140+
eventPayloadPrefix = "* Event Payload: "
141+
nextSectionPrefix = "\n\nUsually this error happens"
142+
)
143+
144+
start := strings.Index(text, eventPayloadPrefix)
145+
if start == -1 {
146+
return text
147+
}
148+
149+
end := strings.Index(text[start:], nextSectionPrefix)
150+
if end == -1 {
151+
return text
152+
}
153+
154+
end += start
155+
return text[:start] + eventPayloadPrefix + formattedPayload + text[end:]
156+
}
157+
130158
func (factory *hermesNotificationEmailFactory) MessageExpired(user *entities.User, payload *events.MessageSendExpiredPayload) (*Email, error) {
131159
email := hermes.Email{
132160
Body: hermes.Body{
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package emails
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/NdoleStudio/httpsms/pkg/entities"
8+
"github.com/NdoleStudio/httpsms/pkg/events"
9+
"github.com/google/uuid"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func testNotificationEmailFactory() NotificationEmailFactory {
15+
return NewHermesNotificationEmailFactory(&HermesGeneratorConfig{
16+
AppURL: "https://httpsms.com",
17+
AppName: "httpSMS",
18+
AppLogoURL: "https://httpsms.com/logo.png",
19+
})
20+
}
21+
22+
func TestWebhookSendFailedFormatsOnlyEventPayload(t *testing.T) {
23+
statusCode := 500
24+
factory := testNotificationEmailFactory()
25+
user := &entities.User{
26+
Email: "name@email.com",
27+
Timezone: "UTC",
28+
}
29+
payload := &events.WebhookSendFailedPayload{
30+
WebhookID: uuid.New(),
31+
WebhookURL: "https://example.com/webhooks",
32+
Owner: "+237612345678",
33+
EventID: "event-id",
34+
EventType: "message.phone.received",
35+
EventPayload: `{"message":"hello","retry":false}`,
36+
HTTPResponseStatusCode: &statusCode,
37+
ErrorMessage: "plain failure response",
38+
}
39+
40+
email, err := factory.WebhookSendFailed(user, payload)
41+
require.NoError(t, err)
42+
43+
assert.Equal(t, "name@email.com", email.ToEmail)
44+
assert.Equal(t, "📢 We could not forward a webhook event to your server", email.Subject)
45+
assert.Contains(t, email.HTML, `<pre style=`)
46+
assert.Equal(t, 1, strings.Count(email.HTML, `<pre style=`))
47+
assert.Contains(t, email.HTML, `&#34;message&#34;`)
48+
assert.Contains(t, email.HTML, `plain failure response`)
49+
assert.Contains(t, email.Text, `"message": "hello"`)
50+
assert.Contains(t, email.Text, `"retry": false`)
51+
assert.NotContains(t, email.Text, "<pre")
52+
}
53+
54+
func TestWebhookSendFailedPreservesNonJSONEventPayload(t *testing.T) {
55+
factory := testNotificationEmailFactory()
56+
user := &entities.User{
57+
Email: "name@email.com",
58+
Timezone: "UTC",
59+
}
60+
payload := &events.WebhookSendFailedPayload{
61+
WebhookID: uuid.New(),
62+
WebhookURL: "https://example.com/webhooks",
63+
Owner: "+237612345678",
64+
EventID: "event-id",
65+
EventType: "message.phone.received",
66+
EventPayload: "line one\n line two",
67+
ErrorMessage: "plain failure response",
68+
}
69+
70+
email, err := factory.WebhookSendFailed(user, payload)
71+
require.NoError(t, err)
72+
73+
assert.Contains(t, email.HTML, "line one\n line two")
74+
assert.NotContains(t, email.HTML, `<span style="color:`)
75+
assert.Contains(t, email.Text, "line one\n line two")
76+
}

0 commit comments

Comments
 (0)