Skip to content

Commit 5c6e643

Browse files
authored
Merge pull request #209 from authorizerdev/feat/send-email-based-on-template
feat: send email based on template
2 parents 0714b43 + 7792cdb commit 5c6e643

30 files changed

+275
-237
lines changed

dashboard/package-lock.json

Lines changed: 14 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/src/components/UpdateEmailTemplateModal.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ interface validatorDataType {
6262
}
6363

6464
const initTemplateData: emailTemplateDataType = {
65-
[EmailTemplateInputDataFields.EVENT_NAME]:
66-
emailTemplateEventNames.BASIC_AUTH_SIGNUP,
65+
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
6766
[EmailTemplateInputDataFields.SUBJECT]: '',
6867
};
6968

@@ -206,10 +205,10 @@ const UpdateEmailTemplate = ({
206205
).reduce((acc, varData): any => {
207206
if (
208207
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
209-
emailTemplateEventNames.VERIFY_OTP &&
208+
emailTemplateEventNames['Verify Otp'] &&
210209
varData[1] === emailTemplateVariables.otp) ||
211210
(templateData[EmailTemplateInputDataFields.EVENT_NAME] ===
212-
emailTemplateEventNames.VERIFY_OTP &&
211+
emailTemplateEventNames['Verify Otp'] &&
213212
varData[1] === emailTemplateVariables.verification_url)
214213
) {
215214
return acc;

dashboard/src/components/UpdateWebhookModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ interface validatorDataType {
9494
}
9595

9696
const initWebhookData: webhookDataType = {
97-
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames.USER_LOGIN,
97+
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'],
9898
[WebhookInputDataFields.ENDPOINT]: '',
9999
[WebhookInputDataFields.ENABLED]: true,
100100
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],

dashboard/src/constants.ts

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -183,20 +183,21 @@ export enum UpdateModalViews {
183183
export const pageLimits: number[] = [5, 10, 15];
184184

185185
export const webhookEventNames = {
186-
USER_SIGNUP: 'user.signup',
187-
USER_CREATED: 'user.created',
188-
USER_LOGIN: 'user.login',
189-
USER_DELETED: 'user.deleted',
190-
USER_ACCESS_ENABLED: 'user.access_enabled',
191-
USER_ACCESS_REVOKED: 'user.access_revoked',
186+
'User signup': 'user.signup',
187+
'User created': 'user.created',
188+
'User login': 'user.login',
189+
'User deleted': 'user.deleted',
190+
'User access enabled': 'user.access_enabled',
191+
'User access revoked': 'user.access_revoked',
192192
};
193193

194194
export const emailTemplateEventNames = {
195-
BASIC_AUTH_SIGNUP: 'basic_auth_signup',
196-
MAGIC_LINK_LOGIN: 'magic_link_login',
197-
UPDATE_EMAIL: 'update_email',
198-
FORGOT_PASSWORD: 'forgot_password',
199-
VERIFY_OTP: 'verify_otp',
195+
Signup: 'basic_auth_signup',
196+
'Magic Link Login': 'magic_link_login',
197+
'Update Email': 'update_email',
198+
'Forgot Password': 'forgot_password',
199+
'Verify Otp': 'verify_otp',
200+
'Invite member': 'invite_member',
200201
};
201202

202203
export enum webhookVerifiedStatus {
@@ -206,27 +207,27 @@ export enum webhookVerifiedStatus {
206207
}
207208

208209
export const emailTemplateVariables = {
209-
'user.id': '{user.id}}',
210-
'user.email': '{user.email}}',
211-
'user.given_name': '{user.given_name}}',
212-
'user.family_name': '{user.family_name}}',
213-
'user.signup_methods': '{user.signup_methods}}',
214-
'user.email_verified': '{user.email_verified}}',
215-
'user.picture': '{user.picture}}',
216-
'user.roles': '{user.roles}}',
217-
'user.middle_name': '{user.middle_name}}',
218-
'user.nickname': '{user.nickname}}',
219-
'user.preferred_username': '{user.preferred_username}}',
220-
'user.gender': '{user.gender}}',
221-
'user.birthdate': '{user.birthdate}}',
222-
'user.phone_number': '{user.phone_number}}',
223-
'user.phone_number_verified': '{user.phone_number_verified}}',
224-
'user.created_at': '{user.created_at}}',
225-
'user.updated_at': '{user.updated_at}}',
226-
'organization.name': '{organization.name}}',
227-
'organization.logo': '{organization.logo}}',
228-
verification_url: '{verification_url}}',
229-
otp: '{otp}}',
210+
'user.id': '{.user.id}}',
211+
'user.email': '{.user.email}}',
212+
'user.given_name': '{.user.given_name}}',
213+
'user.family_name': '{.user.family_name}}',
214+
'user.signup_methods': '{.user.signup_methods}}',
215+
'user.email_verified': '{.user.email_verified}}',
216+
'user.picture': '{.user.picture}}',
217+
'user.roles': '{.user.roles}}',
218+
'user.middle_name': '{.user.middle_name}}',
219+
'user.nickname': '{.user.nickname}}',
220+
'user.preferred_username': '{.user.preferred_username}}',
221+
'user.gender': '{.user.gender}}',
222+
'user.birthdate': '{.user.birthdate}}',
223+
'user.phone_number': '{.user.phone_number}}',
224+
'user.phone_number_verified': '{.user.phone_number_verified}}',
225+
'user.created_at': '{.user.created_at}}',
226+
'user.updated_at': '{.user.updated_at}}',
227+
'organization.name': '{.organization.name}}',
228+
'organization.logo': '{.organization.logo}}',
229+
verification_url: '{.verification_url}}',
230+
otp: '{.otp}}',
230231
};
231232

232233
export const webhookPayloadExample: string = `{

dashboard/src/graphql/queries/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export const WebhooksDataQuery = `
126126
export const EmailTemplatesQuery = `
127127
query getEmailTemplates($params: PaginatedInput!) {
128128
_email_templates(params: $params) {
129-
EmailTemplates {
129+
email_templates {
130130
id
131131
event_name
132132
subject

dashboard/src/pages/EmailTemplates.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
UpdateModalViews,
4141
EmailTemplateInputDataFields,
4242
} from '../constants';
43-
import { EmailTemplatesQuery, WebhooksDataQuery } from '../graphql/queries';
43+
import { EmailTemplatesQuery } from '../graphql/queries';
4444
import dayjs from 'dayjs';
4545
import DeleteEmailTemplateModal from '../components/DeleteEmailTemplateModal';
4646

@@ -94,7 +94,7 @@ const EmailTemplates = () => {
9494
})
9595
.toPromise();
9696
if (res.data?._email_templates) {
97-
const { pagination, EmailTemplates: emailTemplates } =
97+
const { pagination, email_templates: emailTemplates } =
9898
res.data?._email_templates;
9999
const maxPages = getMaxPages(pagination);
100100
if (emailTemplates?.length) {

server/constants/verification_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ const (
99
VerificationTypeUpdateEmail = "update_email"
1010
// VerificationTypeForgotPassword is the forgot_password verification type
1111
VerificationTypeForgotPassword = "forgot_password"
12+
// VerificationTypeInviteMember is the invite_member verification type
13+
VerificationTypeInviteMember = "invite_member"
14+
// VerificationTypeOTP is the otp verification type
15+
VerificationTypeOTP = "otp"
1216
)

server/db/models/user.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package models
22

33
import (
4+
"encoding/json"
45
"strings"
56

67
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -64,3 +65,10 @@ func (user *User) AsAPIUser() *model.User {
6465
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
6566
}
6667
}
68+
69+
func (user *User) ToMap() map[string]interface{} {
70+
res := map[string]interface{}{}
71+
data, _ := json.Marshal(user) // Convert to a json string
72+
json.Unmarshal(data, &res) // Convert to a map
73+
return res
74+
}

server/email/email.go

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,84 @@ package email
22

33
import (
44
"bytes"
5+
"context"
56
"crypto/tls"
6-
"encoding/json"
77
"strconv"
88
"text/template"
99

1010
log "github.com/sirupsen/logrus"
1111
gomail "gopkg.in/mail.v2"
1212

1313
"github.com/authorizerdev/authorizer/server/constants"
14+
"github.com/authorizerdev/authorizer/server/db"
15+
"github.com/authorizerdev/authorizer/server/graph/model"
1416
"github.com/authorizerdev/authorizer/server/memorystore"
1517
)
1618

17-
// addEmailTemplate is used to add html template in email body
18-
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string {
19-
tmpl, err := template.New(templateName).Parse(a)
19+
func getDefaultTemplate(event string) *model.EmailTemplate {
20+
switch event {
21+
case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin:
22+
return &model.EmailTemplate{
23+
Subject: emailVerificationSubject,
24+
Template: emailVerificationTemplate,
25+
}
26+
case constants.VerificationTypeForgotPassword:
27+
return &model.EmailTemplate{
28+
Subject: forgotPasswordSubject,
29+
Template: forgotPasswordTemplate,
30+
}
31+
case constants.VerificationTypeInviteMember:
32+
return &model.EmailTemplate{
33+
Subject: inviteEmailSubject,
34+
Template: inviteEmailTemplate,
35+
}
36+
case constants.VerificationTypeOTP:
37+
return &model.EmailTemplate{
38+
Subject: otpEmailSubject,
39+
Template: otpEmailTemplate,
40+
}
41+
default:
42+
return nil
43+
}
44+
}
45+
46+
func getEmailTemplate(event string, data map[string]interface{}) (*model.EmailTemplate, error) {
47+
ctx := context.Background()
48+
tmp, err := db.Provider.GetEmailTemplateByEventName(ctx, event)
49+
if err != nil || tmp == nil {
50+
tmp = getDefaultTemplate(event)
51+
}
52+
53+
templ, err := template.New(event + "_template.tmpl").Parse(tmp.Template)
2054
if err != nil {
21-
output, _ := json.Marshal(b)
22-
return string(output)
55+
return nil, err
2356
}
2457
buf := &bytes.Buffer{}
25-
err = tmpl.Execute(buf, b)
58+
err = templ.Execute(buf, data)
59+
if err != nil {
60+
return nil, err
61+
}
62+
templateString := buf.String()
63+
64+
subject, err := template.New(event + "_subject.tmpl").Parse(tmp.Subject)
2665
if err != nil {
27-
panic(err)
66+
return nil, err
2867
}
29-
s := buf.String()
30-
return s
68+
buf = &bytes.Buffer{}
69+
err = subject.Execute(buf, data)
70+
if err != nil {
71+
return nil, err
72+
}
73+
subjectString := buf.String()
74+
75+
return &model.EmailTemplate{
76+
Template: templateString,
77+
Subject: subjectString,
78+
}, nil
3179
}
3280

33-
// SendMail function to send mail
34-
func SendMail(to []string, Subject, bodyMessage string) error {
81+
// SendEmail function to send mail
82+
func SendEmail(to []string, event string, data map[string]interface{}) error {
3583
// dont trigger email sending in case of test
3684
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
3785
if err != nil {
@@ -40,6 +88,13 @@ func SendMail(to []string, Subject, bodyMessage string) error {
4088
if envKey == constants.TestEnv {
4189
return nil
4290
}
91+
92+
tmp, err := getEmailTemplate(event, data)
93+
if err != nil {
94+
log.Errorf("Failed to get event template: ", err)
95+
return err
96+
}
97+
4398
m := gomail.NewMessage()
4499
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
45100
if err != nil {
@@ -79,8 +134,8 @@ func SendMail(to []string, Subject, bodyMessage string) error {
79134

80135
m.SetHeader("From", senderEmail)
81136
m.SetHeader("To", to...)
82-
m.SetHeader("Subject", Subject)
83-
m.SetBody("text/html", bodyMessage)
137+
m.SetHeader("Subject", tmp.Subject)
138+
m.SetBody("text/html", tmp.Template)
84139
port, _ := strconv.Atoi(smtpPort)
85140
d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword)
86141
if !isProd {

0 commit comments

Comments
 (0)