Merged in 5661-PLG-play-with-others (pull request #67)

PLG email for 2+ session

Approved-by: Seth Call
This commit is contained in:
Nuwan Chaturanga 2025-08-16 23:50:48 +00:00 committed by Seth Call
commit a84a55f178
15 changed files with 372 additions and 1 deletions

View File

@ -0,0 +1,12 @@
class AddGroupSessionReminderColumnsToUsers < ActiveRecord::Migration
def self.up
execute "ALTER TABLE users ADD COLUMN group_session_reminder1_sent_at TIMESTAMP"
execute "ALTER TABLE users ADD COLUMN group_session_reminder2_sent_at TIMESTAMP"
execute "ALTER TABLE users ADD COLUMN group_session_reminder3_sent_at TIMESTAMP"
end
def self.down
execute "ALTER TABLE users DROP COLUMN group_session_reminder1_sent_at"
execute "ALTER TABLE users DROP COLUMN group_session_reminder2_sent_at"
execute "ALTER TABLE users DROP COLUMN group_session_reminder3_sent_at"
end
end

View File

@ -124,6 +124,7 @@ require "jam_ruby/lib/email_profile_reminder"
require "jam_ruby/lib/email_signup_survey"
require "jam_ruby/lib/gear_setup_reminder"
require "jam_ruby/lib/test_gear_reminder"
require "jam_ruby/lib/group_session_reminder"
require "jam_ruby/amqp/amqp_connection_manager"
require "jam_ruby/database"
require "jam_ruby/message_factory"

View File

@ -504,6 +504,30 @@ module JamRuby
end
end
def group_session_reminder1(user)
@user = user
mail(:to => user.email, :subject => I18n.t('user_mailer.group_session_reminder1.subject')) do |format|
format.text
format.html { render layout: "user_mailer_beta" }
end
end
def group_session_reminder2(user)
@user = user
mail(:to => user.email, :subject => I18n.t('user_mailer.group_session_reminder2.subject')) do |format|
format.text
format.html { render layout: "user_mailer_beta" }
end
end
def group_session_reminder3(user)
@user = user
mail(:to => user.email, :subject => I18n.t('user_mailer.group_session_reminder3.subject')) do |format|
format.text
format.html { render layout: "user_mailer_beta" }
end
end
def signup_survey(user)
@user = user
@subject = I18n.t('user_mailer.signup_survey.subject')

View File

@ -0,0 +1,44 @@
<p><%=I18n.t('user_mailer.group_session_reminder1.greeting') -%> <%= @user.first_name -%> -</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph1').html_safe -%>
</p>
<p>
<h2><%=I18n.t('user_mailer.group_session_reminder1.office_hours') -%></h2>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph2').html_safe -%>
</p>
<p>
<h2><%=I18n.t('user_mailer.group_session_reminder1.hosted_sessions') -%></h2>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph3').html_safe -%>
</p>
<p>
<h2><%=I18n.t('user_mailer.group_session_reminder1.inviting_your_friends') -%></h2>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph4').html_safe -%>
</p>
<p>
<h2><%=I18n.t('user_mailer.group_session_reminder1.finding_good_matches') -%></h2>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph5').html_safe -%>
</p>
<p>
<h2><%=I18n.t('user_mailer.group_session_reminder1.scheduling_sessions') -%></h2>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph6').html_safe -%>
</p>
<p>
<h2><%=I18n.t('user_mailer.group_session_reminder1.asking_for_help') -%></h2>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph7').html_safe -%>
</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph8').html_safe -%>
</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder1.regards') -%>,<br />
<%=I18n.t('user_mailer.group_session_reminder1.signature') -%>
</p>

View File

@ -0,0 +1,26 @@
<%=I18n.t('user_mailer.group_session_reminder1.greeting') -%> <%= @user.first_name -%> -
<%=I18n.t('user_mailer.group_session_reminder1.paragraph1') -%>
<%=I18n.t('user_mailer.group_session_reminder1.office_hours') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph2') -%>
<%=I18n.t('user_mailer.group_session_reminder1.hosted_sessions') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph3') -%>
<%=I18n.t('user_mailer.group_session_reminder1.inviting_your_friends') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph4') -%>
<%=I18n.t('user_mailer.group_session_reminder1.finding_good_matches') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph5') -%>
<%=I18n.t('user_mailer.group_session_reminder1.scheduling_sessions') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph6') -%>
<%=I18n.t('user_mailer.group_session_reminder1.asking_for_help') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph7') -%>
<%=I18n.t('user_mailer.group_session_reminder1.paragraph8') -%>
<%=I18n.t('user_mailer.group_session_reminder1.regards') -%>,
<%=I18n.t('user_mailer.group_session_reminder1.signature') -%>

View File

@ -0,0 +1,18 @@
<p><%=I18n.t('user_mailer.group_session_reminder2.greeting') -%> <%= @user.first_name -%> -</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder2.paragraph1').html_safe -%>
</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder2.paragraph2').html_safe -%>
</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder2.paragraph3').html_safe -%>
</p>
<p>
<%=I18n.t('user_mailer.group_session_reminder2.regards') -%>,<br />
<%=I18n.t('user_mailer.group_session_reminder2.signature') -%>
</p>

View File

@ -0,0 +1,10 @@
<%=I18n.t('user_mailer.group_session_reminder2.greeting') -%> <%= @user.first_name -%> -
<%=I18n.t('user_mailer.group_session_reminder2.paragraph1') -%>
<%=I18n.t('user_mailer.group_session_reminder2.paragraph2') -%>
<%=I18n.t('user_mailer.group_session_reminder2.paragraph3') -%>
<%=I18n.t('user_mailer.group_session_reminder2.regards') -%>,
<%=I18n.t('user_mailer.group_session_reminder2.signature') -%>

View File

@ -0,0 +1,10 @@
<%= I18n.t('user_mailer.group_session_reminder3.greeting') -%> <%= @user.first_name -%> -
<%= I18n.t('user_mailer.group_session_reminder3.paragraph1') -%>
<%= I18n.t('user_mailer.group_session_reminder3.paragraph2') -%>
<%= I18n.t('user_mailer.group_session_reminder3.paragraph3') -%>
<%= I18n.t('user_mailer.group_session_reminder3.regards') -%>,
<%= I18n.t('user_mailer.group_session_reminder3.signature') -%>

View File

@ -0,0 +1,10 @@
<%=I18n.t('user_mailer.group_session_reminder3.greeting') -%> <%= @user.first_name -%> -
<%=I18n.t('user_mailer.group_session_reminder3.paragraph1') -%>
<%=I18n.t('user_mailer.group_session_reminder3.paragraph2') -%>
<%=I18n.t('user_mailer.group_session_reminder3.paragraph3') -%>
<%=I18n.t('user_mailer.group_session_reminder3.regards') -%>,
<%=I18n.t('user_mailer.group_session_reminder3.signature') -%>

View File

@ -0,0 +1,47 @@
module JamRuby
class GroupSessionReminder
@@log = Logging.logger[GroupSessionReminder]
def self.send_reminders
begin
cutoff_date = Date.parse(Rails.application.config.group_session_reminders_effective_from_date) # Define a cutoff date for the group session reminder emails
reminder1_users(cutoff_date).find_each(batch_size:100) do |user|
UserMailer.group_session_reminder1(user).deliver_now
User.where(id: user.id).update_all(group_session_reminder1_sent_at: Time.now)
end
reminder2_users(cutoff_date).find_each(batch_size:100) do |user|
UserMailer.group_session_reminder2(user).deliver_now
User.where(id: user.id).update_all(group_session_reminder2_sent_at: Time.now)
end
reminder3_users(cutoff_date).find_each(batch_size:100) do |user|
UserMailer.group_session_reminder3(user).deliver_now
User.where(id: user.id).update_all(group_session_reminder3_sent_at: Time.now)
end
rescue Exception => e
@@log.error("unable to send GroupSessionReminder email #{e}")
puts "unable to send GroupSessionReminder email #{e}"
end
end
def self.prospect_users
User.where("users.first_certified_gear_at IS NOT NULL AND users.first_real_music_session_at IS NULL")
end
def self.reminder1_users(cutoff_date)
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.first_music_session_at IS NOT NULL AND users.group_session_reminder1_sent_at IS NULL", cutoff_date)
end
def self.reminder2_users(cutoff_date)
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder1_sent_at IS NOT NULL AND users.group_session_reminder2_sent_at IS NULL AND users.first_music_session_at < ?", cutoff_date, 3.days.ago)
end
def self.reminder3_users(cutoff_date)
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder2_sent_at IS NOT NULL AND users.group_session_reminder3_sent_at IS NULL AND users.first_music_session_at < ?", cutoff_date, 5.days.ago)
end
end
end

View File

@ -17,6 +17,7 @@ module JamRuby
EmailSignupSurvey.send_survey
GearSetupReminder.send_reminders
TestGearReminder.send_reminders
GroupSessionReminder.send_reminders
ConnectionManager.new.cleanup_dangling
@@log.info("done")

View File

@ -16,7 +16,7 @@ describe UserMailer do
UserMailer.deliveries.clear
end
describe "should send confirm email", focus: true do
describe "should send confirm email" do
let (:mail) { UserMailer.deliveries[0] }
let (:signup_confirmation_url) { "/confirm" }
@ -210,4 +210,116 @@ describe UserMailer do
# it { mail.text_part.body.include?("New JamKazam Musicians in your Area").should be_true }
# end
describe "sends group session reminder 1", focus: true do
let(:mail) { ActionMailer::Base.deliveries.last }
before(:each) do
ActionMailer::Base.deliveries.clear
UserMailer.group_session_reminder1(user).deliver_now
end
it "sends exactly one email" do
expect(ActionMailer::Base.deliveries.length).to eq(1)
end
it "has the correct from address" do
#expect(mail.from).to eq([UserMailer::DEFAULT_SENDER])
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
end
it "has the correct recipient" do
expect(mail.to).to eq([user.email])
end
it "is multipart" do
expect(mail).to be_multipart
end
it "has the expected subject" do
expect(mail.subject).to include("Its time to start playing music with others online!")
end
it "includes the correct content in the HTML part" do
expect(mail.html_part.body.to_s).to include("Now that you have set up your gearand checked it out in a solo session on your own")
end
it "includes the correct content in the text part" do
expect(mail.text_part.body.to_s).to include("Now that you have set up your gearand checked it out in a solo session on your own")
end
end
describe "sends group session reminder 2", focus: true do
let(:mail) { ActionMailer::Base.deliveries.last }
before(:each) do
ActionMailer::Base.deliveries.clear
UserMailer.group_session_reminder2(user).deliver_now
end
it "sends exactly one email" do
expect(ActionMailer::Base.deliveries.length).to eq(1)
end
it "has the correct from address" do
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
end
it "has the correct recipient" do
expect(mail.to).to eq([user.email])
end
it "is multipart" do
expect(mail).to be_multipart
end
it "has the expected subject" do
expect(mail.subject).to include("Take the last step to play online with others now, and experience the joy")
end
it "includes the correct content in the HTML part" do
expect(mail.html_part.body.to_s).to include("If you signed up for JamKazam to play music with others online")
end
it "includes the correct content in the text part" do
expect(mail.text_part.body.to_s).to include("If you signed up for JamKazam to play music with others online")
end
end
describe "sends group session reminder 3", focus: true do
let(:mail) { ActionMailer::Base.deliveries.last }
before(:each) do
ActionMailer::Base.deliveries.clear
UserMailer.group_session_reminder3(user).deliver_now
end
it "sends exactly one email" do
expect(ActionMailer::Base.deliveries.length).to eq(1)
end
it "has the correct from address" do
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
end
it "has the correct recipient" do
expect(mail.to).to eq([user.email])
end
it "is multipart" do
expect(mail).to be_multipart
end
it "has the expected subject" do
expect(mail.subject).to include("Dont waste your free 30-day premium gold planget in a session with others now!")
end
it "includes the correct content in the HTML part" do
expect(mail.html_part.body.to_s).to include("When you sign up for JamKazam, we give you a free 30-day premium")
end
it "includes the correct content in the text part" do
expect(mail.text_part.body.to_s).to include("When you sign up for JamKazam, we give you a free 30-day premium")
end
end
end

View File

@ -521,9 +521,12 @@ if defined?(Bundler)
config.user_match_monitoring_email = "user_match_monitoring_email@jamkazam.com"
config.send_user_match_mail_only_to_jamkazam_team = true
config.signup_survey_url = "https://www.surveymonkey.com/r/WVBKLYL"
config.signup_survey_cutoff_date = "2025-06-10"
config.gear_setup_reminders_effective_from_date = "2025-06-10"
config.test_gear_reminders_effective_from_date = "2025-07-24"
config.group_session_reminders_effective_from_date = "2025-08-12"
config.action_mailer.asset_host = config.action_controller.asset_host
end
end

View File

@ -130,4 +130,5 @@ SampleApp::Application.configure do
config.signup_survey_cutoff_date = "2025-06-10"
config.gear_setup_reminders_effective_from_date = "2025-06-10"
config.test_gear_reminders_effective_from_date = "2025-07-24"
config.group_session_reminders_effective_from_date = "2025-08-12"
end

View File

@ -196,6 +196,58 @@ en:
If you have any trouble or feel confused while testing things out, you can email us for help at <a href="mailto:support@jamkazam.com">support@jamkazam.com</a>. You can also visit with a JamKazam support team member in our <a href="https://us02web.zoom.us/j/5967470315?pwd=eHZZL2hmVW1haUU5aTZTUUJobjFIdz09">weekly Zoom office hours</a>, which is offered every Wednesday from 11am to 12pm US Central Time.
regards: "Best Regards,"
signature: "JamKazam Team"
group_session_reminder1:
subject: "Its time to start playing music with others online!"
greeting: "Hello"
paragraph1: |
Now that you have set up your gear and checked it out in a solo session on your own, you are ready to get into the fun part actually playing music online with other musicians! If youre not sure how to go about this, here are a few good ways to get connected and start playing with others.
office_hours: Office Hours
paragraph2: |
JamKazam hosts an “office hours” session every Tuesday 7-8pm US Central Time. You can join this JamKazam session as a safe space with a JamKazam support team member to experience and learn how to play in online sessions, or to ask questions about particular features or issues youre having. To find this session, start the JamKazam app, click the Find Session tile, then click the Open Jams tile, and look for the session called “JamKazam Office Hours”, and click the Join icon on this session.
hosted_sessions: Hosted Sessions
paragraph3: |
Members of the JamKazam community host sessions regularly across a variety of genres and artists (e.g. blues, jazz, bluegrass, country, metal, etc). These are public sessions, intended for anyone to join. To see what sessions are upcoming, go to the JamKazam blog here: <a href="https://jamkazam.com/blog/">https://jamkazam.com/blog/</a>. Click on the most recent JamKazam News post, and scroll down to find the Upcoming Events section. Youll find a list of hosted sessions with dates/times, and you can click the link of any hosted session for a description and instructions on how to find and join the hosted session.
inviting_your_friends: Inviting Your Friends
paragraph4: |
If you have bandmates or friends who play, invite them to sign up at https://jamkazam.com. Our support team can help them get ready to go, and then youll be able to play with people you already know and love.
finding_good_matches: Finding Good Matches in the JamKazam Community
paragraph5: |
If you dont already have bandmates or musical friends, or if you do but would like to continue to grow your musical network and connections, we can help you connect with the “right” musicians who are already here in the JamKazam community. JamKazam has a unique feature you can use to search/filter our community by: 1) instruments played and skill level, 2) preferred genres, 3) date last active, and 4) latency so you can make sure youll have good sessions with minimal lag. Once you find musicians who area good match, you can view the details of their profiles and message them to introduce yourself and see if theyd like to try playing together in sessions. <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000526937">Read this help article</a> to learn how to use these search and messaging features.
scheduling_sessions: Scheduling Sessions
paragraph6: |
Another way to find others who want to play the kinds of music youre interested in is to schedule future sessions and see if others in the JamKazam community RSVP to join you in your session. <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000124830">Read this help article</a> to learn how to use this feature.
asking_for_help: Asking Us For Help
paragraph7: |
If youre having trouble finding good musical matches and connections, you can always ask us for help. To do this, either open a help ticket here: <a href="https://www.jamkazam.com/help_desk">https://www.jamkazam.com/help_desk</a>. Or send us an email at <a href="mailto:support@jamkazam.com">support@jamkazam.com</a>. We can reach out into our community to help you try to find the right musicians. In your message, please tell us what genre(s) and what specific musical artists music you want to play, what other instruments youre looking for, and what skill level (e.g. beginner, intermediate, advanced) youre looking for.
paragraph8: |
As a final suggestion, dont be shy! If youre worried you wont play well enough, or you might get embarrassed, please set those intrusive thoughts aside. The musicians in the JamKazam community are very friendly, patient, supportive, and welcoming. This is the culture we all aspire to, and youll find it here 99% of the time (theres always that one guy, right?). Reach out and make new musical friends. Youll be glad you did!
regards: "Best Regards,"
signature: "JamKazam Team"
group_session_reminder2:
subject: "Take the last step to play online with others now, and experience the joy"
greeting: "Hello"
paragraph1: |
If you signed up for JamKazam to play music with others online, youre now just one step short of doing it. Take that step now to experience the joy of playing more often, to build new musical friendships, and to develop your musical skills! Theres so much to gain, and really nothing at all to lose. We have many stories of years-long friendships that have developed on JamKazam and resulted in plane trips and meetups in the real world to hang out, visit, and play.
paragraph2: |
Dont let any worries get in your way. The musicians in the JamKazam community are very friendly, patient, supportive, and welcoming. Reach out and make new musical friends. Youll be glad you did!
paragraph3: |
We sent you an email a couple days ago with several suggestions on great ways to get connected. Refer back to that email for all those detailed instructions and tips. And if you get stuck, dont hesitate to reach out to us to ask for help at <a href="mailto:support@jamkazam.com">support@jamkazam.com</a>.
regards: "Best Regards,"
signature: "JamKazam Team"
group_session_reminder3:
subject: "Dont waste your free 30-day premium gold planget in a session with others now!"
greeting: "Hello"
paragraph1: |
When you sign up for JamKazam, we give you a free 30-day premium gold plan so you can fully experience how amazing our online sessions are and how JamKazam can help you play more music with more people to bring more musical joy to your life.
paragraph2: |
Dont waste your 30-day window! Get connected to other musicians who want to play the kinds of music youre into, and get rolling. We sent you an email a couple days ago with several suggestions on great ways to get connected. Refer back to that email for all those detailed instructions and tips.
paragraph3: |
And if you get stuck, dont hesitate to reach out to us to ask for help at <a href="mailto:support@jamkazam.com">support@jamkazam.com</a>.
regards: "Best Regards,"
signature: "JamKazam Team"
signup_survey:
subject: "Let us help you to be successful on JamKazam"
greeting: "Hi"