From e283827789542331e68c398c51225d486a338f4f Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Tue, 19 Nov 2024 10:39:29 +0530 Subject: [PATCH] Separate out welcome templates --- admin/jobs/river/subscription_handlers.go | 7 +- admin/server/billing.go | 11 +- runtime/pkg/email/email.go | 104 +++--- .../email/templates/gen/welcome_to_team.html | 303 ++++++++++++++++++ .../{welcome.html => welcome_to_trial.html} | 7 +- .../pkg/email/templates/welcome_to_team.mjml | 66 ++++ .../{welcome.mjml => welcome_to_trial.mjml} | 23 +- web-admin/src/features/billing/plans/utils.ts | 2 - 8 files changed, 455 insertions(+), 68 deletions(-) create mode 100644 runtime/pkg/email/templates/gen/welcome_to_team.html rename runtime/pkg/email/templates/gen/{welcome.html => welcome_to_trial.html} (97%) create mode 100644 runtime/pkg/email/templates/welcome_to_team.mjml rename runtime/pkg/email/templates/{welcome.mjml => welcome_to_trial.mjml} (75%) diff --git a/admin/jobs/river/subscription_handlers.go b/admin/jobs/river/subscription_handlers.go index 44f8b3df749..24a3fb00306 100644 --- a/admin/jobs/river/subscription_handlers.go +++ b/admin/jobs/river/subscription_handlers.go @@ -108,9 +108,10 @@ func (w *SubscriptionCancellationCheckWorker) subscriptionCancellationCheck(ctx } err = w.admin.Email.SendSubscriptionEnded(&email.SubscriptionEnded{ - ToEmail: org.BillingEmail, - ToName: org.Name, - OrgName: org.Name, + ToEmail: org.BillingEmail, + ToName: org.Name, + OrgName: org.Name, + BillingURL: w.admin.URLs.Billing(org.Name, false), }) if err != nil { w.logger.Error("failed to send subscription ended email", zap.String("org_id", org.ID), zap.String("org_name", org.Name), zap.String("billing_email", org.BillingEmail), zap.Error(err)) diff --git a/admin/server/billing.go b/admin/server/billing.go index cf73ec135dd..62436a91062 100644 --- a/admin/server/billing.go +++ b/admin/server/billing.go @@ -277,11 +277,12 @@ func (s *Server) CancelBillingSubscription(ctx context.Context, req *adminv1.Can s.logger.Named("billing").Warn("subscription cancelled", zap.String("org_id", org.ID), zap.String("org_name", org.Name)) err = s.admin.Email.SendSubscriptionCancelled(&email.SubscriptionCancelled{ - ToEmail: org.BillingEmail, - ToName: org.Name, - OrgName: org.Name, - PlanName: sub.Plan.DisplayName, - EndDate: endDate, + ToEmail: org.BillingEmail, + ToName: org.Name, + OrgName: org.Name, + PlanName: sub.Plan.DisplayName, + EndDate: endDate, + BillingURL: s.admin.URLs.Billing(org.Name, false), }) if err != nil { s.logger.Named("billing").Error("failed to send subscription cancelled email", zap.String("org_name", org.Name), zap.String("org_id", org.ID), zap.String("billing_email", org.BillingEmail), zap.Error(err)) diff --git a/runtime/pkg/email/email.go b/runtime/pkg/email/email.go index 75c5eb7abe2..86c85dec5bc 100644 --- a/runtime/pkg/email/email.go +++ b/runtime/pkg/email/email.go @@ -197,18 +197,26 @@ func (c *Client) SendInformational(opts *Informational) error { } type Welcome struct { - ToEmail string - ToName string - Subject string - FrontendURL string - WelcomeText template.HTML - IsNew bool - IsFromDeploy bool + ToEmail string + ToName string + Subject string + FrontendURL string + WelcomeText template.HTML +} + +func (c *Client) SendWelcomeToTrial(opts *Welcome) error { + buf := new(bytes.Buffer) + err := c.templates.Lookup("welcome_to_trial.html").Execute(buf, opts) + if err != nil { + return fmt.Errorf("email template error: %w", err) + } + html := buf.String() + return c.Sender.Send(opts.ToEmail, opts.ToName, opts.Subject, html) } -func (c *Client) SendWelcome(opts *Welcome) error { +func (c *Client) SendWelcomeToTeam(opts *Welcome) error { buf := new(bytes.Buffer) - err := c.templates.Lookup("welcome.html").Execute(buf, opts) + err := c.templates.Lookup("welcome_to_team.html").Execute(buf, opts) if err != nil { return fmt.Errorf("email template error: %w", err) } @@ -390,10 +398,10 @@ func (c *Client) SendInvoicePaymentFailed(opts *InvoicePaymentFailed) error { return c.SendCallToAction(&CallToAction{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("Payment failed. Please update your payment method"), + Subject: fmt.Sprintf("Payment failed for %s. Please update your payment method", opts.OrgName), PreButton: template.HTML(fmt.Sprintf(` -We couldn’t process your payment for %s. You have until %s to update your payment details before your account is hibernating. -`, opts.ToName, opts.GracePeriodEndDate.Format(dateFormat))), +We couldn’t process your payment for %s. You have until %s to update your payment details before your org is hibernating. +`, opts.OrgName, opts.GracePeriodEndDate.Format(dateFormat))), ButtonText: "Update Payment Info", ButtonLink: opts.PaymentURL, }) @@ -413,7 +421,7 @@ func (c *Client) SendInvoicePaymentSuccess(opts *InvoicePaymentSuccess) error { return c.SendInformational(&Informational{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("Successful payment %s", opts.PaymentDate.Format("January 2, 2006 15:04:05 MST")), + Subject: fmt.Sprintf("Successful payment %s", opts.PaymentDate.Format(dateFormat)), Body: template.HTML(fmt.Sprintf(` Thank you for your payment!

@@ -438,7 +446,7 @@ func (c *Client) SendInvoiceUnpaid(opts *InvoiceUnpaid) error { return c.SendCallToAction(&CallToAction{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("%s is now hibernated", opts.OrgName), + Subject: fmt.Sprintf("Invoice for %s is now past due. Org is now hibernated", opts.OrgName), PreButton: template.HTML(fmt.Sprintf(` %s and its projects have been hibernated due to an overdue payment.

@@ -450,40 +458,58 @@ Restore access by updating your payment information today! } type SubscriptionCancelled struct { - ToEmail string - ToName string - OrgName string - PlanName string - EndDate time.Time + ToEmail string + ToName string + OrgName string + PlanName string + BillingURL string + EndDate time.Time } func (c *Client) SendSubscriptionCancelled(opts *SubscriptionCancelled) error { - return c.SendInformational(&Informational{ + return c.SendCallToAction(&CallToAction{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("%s for %s is canceled", opts.PlanName, opts.OrgName), - Body: template.HTML(fmt.Sprintf(` -You’ve successfully canceled the %s for %s. Your access will continue until %s. If you change your mind, you can always reactivate your subscription! + Subject: fmt.Sprintf("%s for %s is canceled. Access available until %s", opts.PlanName, opts.OrgName, opts.EndDate.Format(dateFormat)), + PreButton: template.HTML(fmt.Sprintf(` +We’re sorry to see you go! +

+You’ve successfully canceled the %s for %s. You’ll still have access to Rill Cloud until %s. After this date, your subscription will expire, and you will no longer have access. +

+If you change your mind, you can always reactivate your subscription!

If you found that our service did not meet your needs, please reply to this email and we’ll do our best to address your feedback and concerns. `, opts.PlanName, opts.ToName, opts.EndDate.Format(dateFormat))), + ButtonText: "Billing Settings", + ButtonLink: opts.BillingURL, + PostButton: template.HTML(fmt.Sprintf("If you found that our service did not meet your needs, please reply to this email and we’ll do our best to address your feedback and concerns.")), }) } type SubscriptionEnded struct { - ToEmail string - ToName string - OrgName string + ToEmail string + ToName string + OrgName string + BillingURL string } func (c *Client) SendSubscriptionEnded(opts *SubscriptionEnded) error { - return c.SendInformational(&Informational{ + return c.SendCallToAction(&CallToAction{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("%s is now hibernated", opts.OrgName), - Body: template.HTML(fmt.Sprintf(` -%s and its projects are now hibernating. + Subject: fmt.Sprintf("Subscription for %s has now ended. Org is hibernated", opts.OrgName), + PreButton: template.HTML(fmt.Sprintf(` +Your cancelled subscription for %s has and its projects are now hibernating. We hope you enjoyed using Rill Cloud during your time with us. +

+If you’d like to reactive your subscription and regain access, you can easily do so at any time by renewing your subscription from here: `, opts.OrgName)), + ButtonText: "Billing Settings", + ButtonLink: opts.BillingURL, + PostButton: template.HTML(fmt.Sprintf(` +We’d also love to hear from you! If you have any feedback about your experience or how we can improve, please share it with us by replying to this email. +

+Thank you for trying Rill Cloud. We hope to see you again in the future! +`)), }) } @@ -496,17 +522,16 @@ type TrialStarted struct { } func (c *Client) SendTrialStarted(opts *TrialStarted) error { - return c.SendWelcome(&Welcome{ + return c.SendWelcomeToTrial(&Welcome{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("A 30-day free trial for for %s has started", opts.OrgName), + Subject: fmt.Sprintf("A 30-day free trial for %s has started", opts.OrgName), FrontendURL: opts.FrontendURL, WelcomeText: template.HTML(fmt.Sprintf(` Hi %s,

You now have access to Rill Cloud until %s to explore all features. Let us know if you need any help along the way! `, opts.ToName, opts.TrialEndDate.Format(dateFormat))), - IsNew: true, }) } @@ -574,14 +599,19 @@ func (c *Client) SendTrialGracePeriodEnded(opts *TrialGracePeriodEnded) error { return c.SendCallToAction(&CallToAction{ ToEmail: opts.ToEmail, ToName: opts.ToName, - Subject: fmt.Sprintf("%s is now hibernated", opts.OrgName), + Subject: fmt.Sprintf("Trial plan grace period for %s has ended. Org is now hibernated", opts.OrgName), PreButton: template.HTML(fmt.Sprintf(` %s and its projects are now hibernating.

-Reactivate your account by upgrading to the Team Plan today! +Reactivate your org by upgrading to the Team Plan today! `, opts.OrgName)), ButtonText: "Upgrade to Team Plan", ButtonLink: opts.UpgradeURL, + PostButton: template.HTML(fmt.Sprintf(` +We’d also love to hear from you! If you have any feedback about your experience or how we can improve, please share it with us by replying to this email. +

+Thank you for trying Rill Cloud. We hope to see you again in the future! +`)), }) } @@ -644,7 +674,7 @@ type TeamPlan struct { // SendTeamPlanStarted sends customised plan started email for Team Plan func (c *Client) SendTeamPlanStarted(opts *TeamPlan) error { - return c.SendWelcome(&Welcome{ + return c.SendWelcomeToTeam(&Welcome{ ToEmail: opts.ToEmail, ToName: opts.ToName, Subject: fmt.Sprintf("Welcome to the %s", opts.PlanName), @@ -659,7 +689,7 @@ Your next billing cycle starts on %s. // SendTeamPlanRenewal sends customised plan renewed email for Team Plan func (c *Client) SendTeamPlanRenewal(opts *TeamPlan) error { - return c.SendWelcome(&Welcome{ + return c.SendWelcomeToTeam(&Welcome{ ToEmail: opts.ToEmail, ToName: opts.ToName, Subject: fmt.Sprintf("Your %s subscription for %s has been renewed", opts.PlanName, opts.OrgName), diff --git a/runtime/pkg/email/templates/gen/welcome_to_team.html b/runtime/pkg/email/templates/gen/welcome_to_team.html new file mode 100644 index 00000000000..2c4e983b454 --- /dev/null +++ b/runtime/pkg/email/templates/gen/welcome_to_team.html @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
+ +
+
+
Welcome to Rill
+
+ + + + + + +
+ +
+
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + +
+ + + + + + +
+
{{ .WelcomeText }}
+
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ Documentation and tutorials → +
+
+
Check out the quickstart, example projects, then dive deep into advanced features.
+
+ + + + + + +
+ Release notes → +
+
+
We are constantly shipping new features. Follow our release notes to stay up to date.
+
+
- The Rill Team
+
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + + +
+

+

+ +
+
+
+
© 2024 Rill Data, Inc
18 Bartol St. • San Francisco • CA
+ Contact usCommunityPrivacy Policy +
+
+
+
+ +
+
+ +
+ + + diff --git a/runtime/pkg/email/templates/gen/welcome.html b/runtime/pkg/email/templates/gen/welcome_to_trial.html similarity index 97% rename from runtime/pkg/email/templates/gen/welcome.html rename to runtime/pkg/email/templates/gen/welcome_to_trial.html index 0f4d50c5851..57d1e5856d9 100644 --- a/runtime/pkg/email/templates/gen/welcome.html +++ b/runtime/pkg/email/templates/gen/welcome_to_trial.html @@ -165,7 +165,7 @@ -
{{ .WelcomeText }}
+
{{ .WelcomeText }} Let’s get started exploring your environment:
@@ -181,11 +181,6 @@ - - -
You now have full access for 30 days to explore all features. Let us know if you need any help along the way. Enjoy!
- - diff --git a/runtime/pkg/email/templates/welcome_to_team.mjml b/runtime/pkg/email/templates/welcome_to_team.mjml new file mode 100644 index 00000000000..2fa185615b2 --- /dev/null +++ b/runtime/pkg/email/templates/welcome_to_team.mjml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + Welcome to Rill + + + + + + + + {{ .WelcomeText }} + + + + + + + Documentation and tutorials → + + Check out the quickstart, example projects, then dive deep into advanced features. + + + Release notes → + + We are constantly shipping new features. Follow our release notes to stay up to date. + + - The Rill Team + + + + + + + + + + + © 2024 Rill Data, Inc
+ 18 Bartol St. • San Francisco • CA
+ Contact us • + Community • + Privacy Policy +
+
+
+ +
+
\ No newline at end of file diff --git a/runtime/pkg/email/templates/welcome.mjml b/runtime/pkg/email/templates/welcome_to_trial.mjml similarity index 75% rename from runtime/pkg/email/templates/welcome.mjml rename to runtime/pkg/email/templates/welcome_to_trial.mjml index fe42833562b..d7c58e5f650 100644 --- a/runtime/pkg/email/templates/welcome.mjml +++ b/runtime/pkg/email/templates/welcome_to_trial.mjml @@ -27,27 +27,20 @@ {{ .WelcomeText }} + Let’s get started exploring your environment: - {{ if .IsNew }} - - Get Started - - - {{ if .IsFromDeploy }} - You now have full access for 30 days to explore all features. Let us know if you need any help along the way. Enjoy! - {{ end }} - {{ end }} + + Get Started + - {{ if .IsNew }} - What is Rill → - - Learn more about Rill's product architecture and find out what makes us different. - - {{ end }} + What is Rill → + + Learn more about Rill's product architecture and find out what makes us different. + Documentation and tutorials → diff --git a/web-admin/src/features/billing/plans/utils.ts b/web-admin/src/features/billing/plans/utils.ts index 7d863ef7728..803b17a702c 100644 --- a/web-admin/src/features/billing/plans/utils.ts +++ b/web-admin/src/features/billing/plans/utils.ts @@ -21,8 +21,6 @@ export function formatUsageVsQuota( return `${formattedUsage} of ${formattedQuota} (${percent}%)`; } -// mapping of externalID (mapped to name in API response) present in plan to internal type. -// make sure to update admin/billing/orb.go to keep backend consistent export function isTrialPlan(plan: V1BillingPlan) { return plan.planType === V1BillingPlanType.BILLING_PLAN_TYPE_TRIAL; }