Scheduling email sending with Pg-boss

Project Overview I have developed a feature that allows sending a list of tasks via email to the logged-in user using Mailgun. I now want to enhance this functionality by implementing a subscription service for users to receive a digest of new tasks based on a search profile. Key Requirements 1. Subscription Feature: - Users can subscribe to a search profile. - Each search profile will have criteria such as minPrice and maxPrice to filter tasks. 2. Email Digest: - A digest of new tasks meeting the search profile criteria will be sent via email. - Users can choose the frequency of the email: daily or weekly. 3. Scheduling with pg-boss: - Use pg-boss for scheduling the email dispatch. - The scheduling should support both daily (cron string: 0 0 * * *) and weekly (cron string: 0 0 * * 5) options based on user preference. Assistance Needed I need assistance with the following: 1. Implementation of the Subscription Logic: - How to allow users to create and manage their search profiles. - Storing user preferences for email frequency. 2. Integration with pg-boss: - Setting up pg-boss to handle the scheduling based on user preferences. - Ensuring the correct cron string is used for each user’s chosen frequency. 3. Email Sending: - Filtering tasks based on the search profile criteria (minPrice and maxPrice). - Formatting the tasks into a digest and sending the email using Mailgun. Request Kapa.ai Could you provide a detailed implementation plan or example code snippets to achieve this?
9 Replies
kapa.ai
kapa.ai6mo ago
Hi @OrBlatt. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the information provided in the knowledge sources, I can guide you through the process of setting up a job with pg-boss in Wasp, which can be used for scheduling your email dispatch. However, the knowledge sources do not provide specific information on implementing the subscription logic, filtering tasks based on search profile criteria, or sending emails using Mailgun. Here's how you can set up a job with pg-boss: 1. Declare the Job: In your main.wasp file, declare a job that uses pg-boss as the executor. The perform field should import the function that will be executed when the job runs. This function should be responsible for filtering tasks and sending the email digest. The schedule field should contain the cron string for scheduling the job. The args field can be used to pass arguments to the function. The entities field should list any entities that the function needs to interact with. Here's an example:
job emailDigest {
executor: PgBoss,
perform: {
fn: import { sendEmailDigest } from "@ext/workers/emailSender"
},
schedule: {
cron: "0 0 * * *", // This should be dynamic based on user preference
args: {=json { "minPrice": 100, "maxPrice": 500 } json=} // This should be dynamic based on user's search profile
},
entities: [Task]
}
job emailDigest {
executor: PgBoss,
perform: {
fn: import { sendEmailDigest } from "@ext/workers/emailSender"
},
schedule: {
cron: "0 0 * * *", // This should be dynamic based on user preference
args: {=json { "minPrice": 100, "maxPrice": 500 } json=} // This should be dynamic based on user's search profile
},
entities: [Task]
}
2. Implement the Job Function: The sendEmailDigest function should be implemented in the emailSender.js file in the ext/workers directory. This function should filter tasks based on the search profile criteria and send the email digest using Mailgun. The function will receive the arguments specified in the args field of the job declaration.
export const sendEmailDigest = async ({ minPrice, maxPrice }, context) => {
const tasks = await context.entities.Task.findMany({ where: { price: { gte: minPrice, lte: maxPrice } } })
// Format tasks into a digest and send email using Mailgun
}
export const sendEmailDigest = async ({ minPrice, maxPrice }, context) => {
const tasks = await context.entities.Task.findMany({ where: { price: { gte: minPrice, lte: maxPrice } } })
// Format tasks into a digest and send email using Mailgun
}
Please note that the above code snippets are just examples and might need to be adjusted based on your application's specific needs. For the subscription logic, user preferences, and email sending, you might need to refer to other resources or documentation as they are not covered in the provided knowledge sources. For more information on using pg-boss with Wasp, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/jobs#pg-boss-considerations). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @OrBlatt, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
OrBlatt
OrBlattOP6mo ago
How should i inject the cronInterval and args? job emailDigest { executor: PgBoss, perform: { fn: import { sendEmailDigest } from "@ext/workers/emailSender" }, schedule: { cron: cronInterval, // This should be dynamic based on user preference args: {=json args json=} // This should be dynamic based on user's search profile }, entities: [Task] } @kapa.ai
kapa.ai
kapa.ai6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Currently, Wasp does not support dynamic values directly in the main.wasp file. The main.wasp file is a static configuration file and does not support dynamic values or JavaScript expressions. However, you can design your job function (sendEmailDigest in this case) to fetch the user's preferences and search profile criteria at runtime. This way, even though the job is scheduled at a fixed interval, the function it executes can adapt its behavior based on the current state of the application. Here's an example of how you might implement this:
export const sendEmailDigest = async (_, context) => {
// Fetch user preferences and search profile criteria
const userPreferences = await context.entities.UserPreferences.findUnique({ where: { userId: context.user.id } })
const searchProfile = await context.entities.SearchProfile.findUnique({ where: { userId: context.user.id } })

// Use user preferences and search profile criteria to filter tasks and send email
const tasks = await context.entities.Task.findMany({ where: { price: { gte: searchProfile.minPrice, lte: searchProfile.maxPrice } } })
// Format tasks into a digest and send email using Mailgun
}
export const sendEmailDigest = async (_, context) => {
// Fetch user preferences and search profile criteria
const userPreferences = await context.entities.UserPreferences.findUnique({ where: { userId: context.user.id } })
const searchProfile = await context.entities.SearchProfile.findUnique({ where: { userId: context.user.id } })

// Use user preferences and search profile criteria to filter tasks and send email
const tasks = await context.entities.Task.findMany({ where: { price: { gte: searchProfile.minPrice, lte: searchProfile.maxPrice } } })
// Format tasks into a digest and send email using Mailgun
}
In this example, UserPreferences and SearchProfile are hypothetical entities that you would need to define in your application. UserPreferences could store the user's email frequency preference, and SearchProfile could store the search profile criteria. Please note that this approach requires the job to be scheduled at the smallest common interval. For example, if the user can choose between daily and weekly emails, the job should be scheduled to run daily. The sendEmailDigest function should then check if it's time to send the email based on the user's preference. For more information on using pg-boss with Wasp, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/jobs#pg-boss-considerations). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @OrBlatt, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
OrBlatt
OrBlattOP6mo ago
I want to send the email only to the user who registered the search profile. How can I enforce that? @kapa.ai
kapa.ai
kapa.ai6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- To ensure that the email is sent only to the user who registered the search profile, you can modify the sendEmailDigest function to fetch the user's email address and use it when sending the email. Here's an example:
export const sendEmailDigest = async (_, context) => {
// Fetch user preferences and search profile criteria
const userPreferences = await context.entities.UserPreferences.findUnique({ where: { userId: context.user.id } })
const searchProfile = await context.entities.SearchProfile.findUnique({ where: { userId: context.user.id } })

// Use user preferences and search profile criteria to filter tasks
const tasks = await context.entities.Task.findMany({ where: { price: { gte: searchProfile.minPrice, lte: searchProfile.maxPrice } } })

// Fetch user's email address
const user = await context.entities.User.findUnique({ where: { id: context.user.id } })

// Format tasks into a digest and send email using Mailgun
// Use user.email when specifying the recipient
}
export const sendEmailDigest = async (_, context) => {
// Fetch user preferences and search profile criteria
const userPreferences = await context.entities.UserPreferences.findUnique({ where: { userId: context.user.id } })
const searchProfile = await context.entities.SearchProfile.findUnique({ where: { userId: context.user.id } })

// Use user preferences and search profile criteria to filter tasks
const tasks = await context.entities.Task.findMany({ where: { price: { gte: searchProfile.minPrice, lte: searchProfile.maxPrice } } })

// Fetch user's email address
const user = await context.entities.User.findUnique({ where: { id: context.user.id } })

// Format tasks into a digest and send email using Mailgun
// Use user.email when specifying the recipient
}
In this example, User is an entity that represents the user in your application. It should include an email field that stores the user's email address. You would need to define this entity in your application. Please note that this approach assumes that the context.user.id is available and correctly identifies the user who registered the search profile. This would typically be the case if the job is being executed in the context of a logged-in user. For more information on how to define entities in Wasp, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/auth/email#2-add-the-user-entity). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @OrBlatt, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
OrBlatt
OrBlattOP6mo ago
To be able to send email digest only to the user who registered the search profile, should I add the User entitity to the job declaration like this? job emailDigest { executor: PgBoss, perform: { fn: import { sendEmailDigest } from "@ext/workers/emailSender" }, schedule: { cron: "0 0 * * *", // This should be dynamic based on user preference args: {=json { "minPrice": 100, "maxPrice": 500 } json=} // This should be dynamic based on user's search profile }, entities: [Task, User] } @kapa.ai
kapa.ai
kapa.ai6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, you are correct. If your sendEmailDigest function needs to interact with the User entity (for example, to fetch the user's email address), you should include User in the entities field of the job declaration. Here's how you can do it:
job emailDigest {{
executor: PgBoss,
perform: {{
fn: import {{ sendEmailDigest }} from "@ext/workers/emailSender"
}},
schedule: {{
cron: "0 0 * * *", // This should be dynamic based on user preference
args: {{=json {{ "minPrice": 100, "maxPrice": 500 }} json=}} // This should be dynamic based on user's search profile
}},
entities: [Task, User]
}}
job emailDigest {{
executor: PgBoss,
perform: {{
fn: import {{ sendEmailDigest }} from "@ext/workers/emailSender"
}},
schedule: {{
cron: "0 0 * * *", // This should be dynamic based on user preference
args: {{=json {{ "minPrice": 100, "maxPrice": 500 }} json=}} // This should be dynamic based on user's search profile
}},
entities: [Task, User]
}}
This tells Wasp that the sendEmailDigest function needs to use the Task and User entities. Wasp will then make sure to provide these entities to the function when it is executed. For more information on how to use entities in jobs, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/jobs#pg-boss-considerations). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @OrBlatt, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
martinsos
martinsos6mo ago
@OrBlatt the main thing to notice here is that you can't have dynamic schedule for each user if you are using jobs as a feature. I can think of two ways to handle this: 1. Have Wasp job have a schedule that is frequent enough and then each time it runs manually check via the database which users are ready to be sent emails and do so. I am guessing users would have something like "mohtly" or "weekly" or something like that, a couple of options. You can check which users are ready to be sent emails. 2. Similar to (1) but have one job per frequency. So if you have "weekly", "daily" and "monthly", you could have 3 jobs, each one pulling different users from the database based on this setting. 3. Don't use Wasp job feature but instead schedule your own jobs dynamically per user, probably also using pg-boss. You might end up with a lot of jobs here though, so this might not be the best approach. I think you probbaly don't want to do (3), and choosing between (1) and (2) will depend on exactly what you need and how tailored your schedules are, how they work.
OrBlatt
OrBlattOP6mo ago
Thank you Martin. I agree, option 1 or 2 are what I want. 🙏🙏🙏
Want results from more Discord servers?
Add your server