Skip to content

[server] Retention emails #5550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open

[server] Retention emails #5550

wants to merge 25 commits into from

Conversation

mngshm
Copy link
Member

@mngshm mngshm commented Apr 7, 2025

No description provided.

@mngshm mngshm marked this pull request as ready for review April 18, 2025 11:49
@mngshm mngshm requested a review from ua741 April 20, 2025 20:58
rows, err := repo.DB.Query(`
SELECT users.user_id, users.encrypted_email, users.email_decryption_nonce, users.email_hash, usage.storage_consumed, subscriptions.storage
FROM users
INNER JOIN usage
ON users.user_id = usage.user_id
INNER JOIN subscriptions
ON users.user_id = subscriptions.user_id AND usage.storage_consumed > subscriptions.storage AND users.encrypted_email IS NOT NULL AND users.family_admin_id IS NULL;
`)
ON users.user_id = subscriptions.user_id AND usage.storage_consumed >= (subscriptions.storage * $1 / 100.0) AND users.encrypted_email is not null AND users.family_admin_id is NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* $1 / 100.0 Is this intentional?

Copy link
Member Author

@mngshm mngshm Apr 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this logic is intentional and it filters users based on the storage they have consumed. The queries were ran manually to test if they work while I was writing the code. Here's a proof with 90% and 100% consumptions.

Capture-Apr-21-Apr-04-1745230664

then the code below this does further logic. Please let me know if there are any inconsistencies you think are still there.

Also, thank you for raising the question. There was a small bug (which could've had bigger impact) regarding the comparison usage.storage_consumed >= (subscription.storage * 90 /100). The >= operator was the bug here leading to get both 90% and 100% consumed list of users when percentageThreshold == 90

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood.
I don't think equal will work as user storage might never reach exact 90%. Even if it reaches, the cron runs at fixed interval, it would increase after that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was re-iterating over this once again. There's a catch

if we use just usage.storage_consumed = (subscription.storage * 90 /100), your argument is valid.

but if we use >=, the users between 90% - 100% consumption range will be considered for receiving an email.

One way I can think of is, handling both the thresholds separately.

@mngshm mngshm force-pushed the retention-emails branch 2 times, most recently from 665160f to e7b5815 Compare April 21, 2025 10:16
@mngshm mngshm requested a review from ua741 April 23, 2025 11:49
previously all users without families were fetched and later 30 days check was being done.
With this modification, in the future custom duration can be passed and depending on that one could get the list of users. The compiler decides if more time has passed dependeing on what is the underlying nanoseconds value of time.Time value. Older dates are less in size compared to Newer Dates. Hence, s.created_at >=  is an appropriate calculation
@mngshm mngshm force-pushed the retention-emails branch from ce37e9d to f609cef Compare April 23, 2025 12:12
@@ -128,6 +129,38 @@ func (repo *UserRepository) GetAll(sinceTime int64, tillTime int64) ([]ente.User
return users, nil
}

// GetSubscribedUsers will return the list of all subscribed.
func (repo *UserRepository) GetSubscribedUsersWithoutFamily(duration string) ([]ente.User, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the created_at does not get updated when user switches from free to paid plan.

func (repo *UserRepository) GetSubscribedUsersWithoutFamily(accountAgeInDays int) ([]int64, error) {
	rows, err := repo.DB.Query(`SELECT u.user_id, u.encrypted_email, u.email_decryption_nonce, u.email_hash, s.created_at 
	FROM subscriptions s 
	INNER JOIN users u ON s.user_id = u.user_id 
	WHERE u.creation_time <= $1
	AND u.family_admin_id IS NULL
	AND u.encrypted_email IS NOT NULL
	AND s.product_id != 'free'`, time.MicrosecondBeforeDays(accountAgeInDays))
	
	...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this query, the condition happens between u.creation_time <= $1. This is not what was intended. The user might chose to not subscribe and consume the free 10GB storage first. Hence, I went ahead with using s.created_at. Ideally, the mail has to be sent to those who have crossed 30 days after subscribing and still do not have a family onboarded.

Comment on lines +137 to +138
WHERE s.created_at >= $1
AND u.family_admin_id IS NULL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.created_at >= $1 should be s.created_at <= $1 (fixed it in the suggested query) and also added check for deleted users.

@mngshm mngshm force-pushed the retention-emails branch from 0e4db1c to 5d5e418 Compare April 24, 2025 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants