diff --git a/go-backend/internal/handlers/auth_handler.go b/go-backend/internal/handlers/auth_handler.go index 95addc5..54b4bbe 100644 --- a/go-backend/internal/handlers/auth_handler.go +++ b/go-backend/internal/handlers/auth_handler.go @@ -395,7 +395,7 @@ func generateRandomString(n int) (string, error) { if err != nil { return "", err } - return base64.URLEncoding.EncodeToString(b), nil + return base64.RawURLEncoding.EncodeToString(b), nil } func (h *AuthHandler) ForgotPassword(c *gin.Context) { diff --git a/go-backend/internal/services/email_service.go b/go-backend/internal/services/email_service.go index 5c5d5e9..12d0db2 100644 --- a/go-backend/internal/services/email_service.go +++ b/go-backend/internal/services/email_service.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strings" "sync" "time" @@ -52,39 +53,40 @@ type sendPulseIdentity struct { func (s *EmailService) SendVerificationEmail(toEmail, toName, token string) error { subject := "Verify your Sojorn account" - // Ensure we don't double up on /api/v1 if it's already in the config apiBase := strings.TrimSuffix(s.config.APIBaseURL, "/api/v1") - verifyURL := fmt.Sprintf("%s/api/v1/auth/verify?token=%s", apiBase, token) + verifyURL := fmt.Sprintf("%s/api/v1/auth/verify?token=%s", apiBase, url.QueryEscape(token)) title := "Email Verification" - header := fmt.Sprintf("Hey %s! 👋", toName) + header := fmt.Sprintf("Hey %s!", toName) if toName == "" { - header = "Hey there! 👋" + header = "Hey there!" } content := ` -

Welcome to Sojorn — your vibrant new social space. We're thrilled to have you join our community!

+

Welcome to Sojorn — your vibrant new social space. We're thrilled to have you join our community!

To get started in the app, please verify your email address by clicking the button below:

` footer := ` -
-

If the button doesn't work, copy and paste this link into your browser:

- %s -
+ + +
+

If the button above doesn't work, copy and paste this link into your browser:

+ %s +
` footer = fmt.Sprintf(footer, verifyURL, verifyURL) htmlBody := s.buildHTMLEmail(title, header, content, verifyURL, "Verify My Email", footer) - textBody := fmt.Sprintf("Welcome to Sojorn! Please verify your email by clicking here: %s", verifyURL) + textBody := fmt.Sprintf("Welcome to Sojorn!\n\nPlease verify your email by visiting this link:\n\n%s\n\nIf you did not create an account, you can ignore this email.", verifyURL) return s.sendEmail(toEmail, toName, subject, htmlBody, textBody) } func (s *EmailService) SendPasswordResetEmail(toEmail, toName, token string) error { subject := "Reset your Sojorn password" - resetURL := fmt.Sprintf("%s/reset-password?token=%s", s.config.AppBaseURL, token) + resetURL := fmt.Sprintf("%s/reset-password?token=%s", s.config.AppBaseURL, url.QueryEscape(token)) title := "Password Reset" header := "Reset your password" @@ -94,11 +96,18 @@ func (s *EmailService) SendPasswordResetEmail(toEmail, toName, token string) err `, toName) footer := ` -

This link expires in 1 hour. If you did not request this, you can safely ignore this email.

+ + +
+

If the button doesn't work, copy and paste this link:

+ %s +
+

This link expires in 1 hour. If you did not request this, you can safely ignore this email.

` + footer = fmt.Sprintf(footer, resetURL, resetURL) htmlBody := s.buildHTMLEmail(title, header, content, resetURL, "Reset Password", footer) - textBody := fmt.Sprintf("Reset your Sojorn password: %s", resetURL) + textBody := fmt.Sprintf("Reset your Sojorn password by visiting this link:\n\n%s\n\nThis link expires in 1 hour.", resetURL) return s.sendEmail(toEmail, toName, subject, htmlBody, textBody) } @@ -257,49 +266,64 @@ func (s *EmailService) AddSubscriber(email, name string) { } func (s *EmailService) buildHTMLEmail(title, header, content, buttonURL, buttonText, footer string) string { - return fmt.Sprintf(` - - + return fmt.Sprintf(` + + + %s + + - -
-
- -
- Sojorn -
%s
-
- - -
-

%s

-
- %s -
+ + + +
+ + + - + + - %s - - - - - - + + +
+ Sojorn +

%s

+
+

%s

+
+ %s +
+ + + + +
+ %s +
+ %s - +
+

© 2026 Sojorn by MPLS LLC. All rights reserved.

+

+ Website • + Privacy • + Terms +

+
+
- - `, title, title, header, content, buttonURL, buttonText, footer) +`, title, title, header, content, buttonURL, buttonText, footer) }