Attempt to guess first name for welcome email

This commit is contained in:
Taras Kushnir
2025-08-16 12:48:24 +03:00
parent 6231572bee
commit 25bf16eada
6 changed files with 78 additions and 4 deletions
+1
View File
@@ -5,6 +5,7 @@ import "net/http"
const (
DefaultOrgName = "My Organization"
PrivateCaptcha = "Private Captcha"
PrivateCaptchaTeam = "Private Captcha Team"
StageDev = "dev"
StageStaging = "staging"
StageTest = "test"
+47
View File
@@ -9,6 +9,7 @@ import (
"net/url"
"strings"
"time"
"unicode"
"github.com/jpillora/backoff"
)
@@ -98,6 +99,52 @@ func ParseBoolean(value string) bool {
}
}
func containsAlphabetic(s string) bool {
for _, r := range s {
if unicode.IsLetter(r) {
return true
}
}
return false
}
func onlyAlphabetic(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
func isLowerCase(s string) bool {
for _, r := range s {
if !unicode.IsLower(r) {
return false
}
}
return true
}
func GuessFirstName(username string) string {
parts := strings.Fields(username)
for _, p := range parts {
if containsAlphabetic(p) {
if onlyAlphabetic(p) && isLowerCase(p) {
runes := []rune(p)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}
return p
}
}
return username
}
func ChunkedCleanup(ctx context.Context, minInterval, maxInterval time.Duration, defaultChunkSize int, deleter func(context.Context, time.Time, int) int) {
b := &backoff.Backoff{
Min: minInterval,
+26
View File
@@ -123,3 +123,29 @@ func TestSubDomain(t *testing.T) {
})
}
}
func TestGuessFirstName(t *testing.T) {
tests := []struct {
username string
expected string
}{
{"john doe", "John"},
{"123 alice 456", "Alice"},
{"bob123 charlie", "bob123"},
{"david", "David"},
{"123 456 789", "123 456 789"},
{"", ""},
{" ", " "},
{"!@# john_doe", "john_doe"},
{"___123___", "___123___"},
{" emily rose ", "Emily"},
{"123 456abc", "456abc"},
}
for _, tt := range tests {
actual := GuessFirstName(tt.username)
if actual != tt.expected {
t.Errorf("GuessFirstName(%q) = %q; want %q", tt.username, actual, tt.expected)
}
}
}
+1 -1
View File
@@ -254,7 +254,7 @@ func (j *UserEmailNotificationsJob) processNotificationsChunk(ctx context.Contex
Subject: un.Subject,
EmailTo: n.Email,
EmailFrom: emailFrom,
NameFrom: common.PrivateCaptcha,
NameFrom: common.PrivateCaptchaTeam,
ReplyTo: replyToEmail,
HTMLBody: htmlBodyTpl.String(),
TextBody: textBodyTpl.String(),
+2 -2
View File
@@ -93,7 +93,7 @@ func (pm *PortalMailer) SendTwoFactor(ctx context.Context, email string, code in
Subject: fmt.Sprintf("[%s] Your verification code is %v", common.PrivateCaptcha, data.Code),
EmailTo: email,
EmailFrom: pm.EmailFrom.Value(),
NameFrom: common.PrivateCaptcha,
NameFrom: common.PrivateCaptchaTeam,
}
clog := slog.With("email", email, "code", data.Code)
@@ -151,7 +151,7 @@ func (pm *PortalMailer) SendWelcome(ctx context.Context, email, name string) err
Subject: "Welcome to Private Captcha",
EmailTo: email,
EmailFrom: pm.EmailFrom.Value(),
NameFrom: common.PrivateCaptcha,
NameFrom: common.PrivateCaptchaTeam,
ReplyTo: pm.ReplyToEmail.Value(),
}
+1 -1
View File
@@ -36,5 +36,5 @@ func (j *onboardUserJob) InitialPause() time.Duration {
}
func (j *onboardUserJob) RunOnce(ctx context.Context) error {
return j.mailer.SendWelcome(ctx, j.user.Email, j.user.Name)
return j.mailer.SendWelcome(ctx, j.user.Email, common.GuessFirstName(j.user.Name))
}