Skip to main content

Checking for expired documents and sending email reminders

Rails • Asked by Stephen Sizer

Hi,
I've got a system where users upload documents with expiry dates. I want to send the user an email reminder 1 month and 1 week before expiration to remind them to upload a new document.

What's the best way to do this?

Thanks in advance.
Steve


Hey Stephen,

Since you'll be storing the expiration date on a Document model (along with the reference to the file), you can always verify the document hasn't expired before linking to the file and you can use that same attribute to send the emails.

I would use a Date column to store the expiration.

In this situation, what's easiest is to write two scopes, one for the month, one for the week reminder.

scope :expires_in_one_month, ->{ where(expires_at: 1.month.from_now.to_date) }
scope :expires_in_one_week, ->{ where(expires_at: 1.week.from_now.to_date) }

Now you have two queries that let you find documents that expire in a month and a week.

You can build a cron job that runs once per day and grabs all the documents for each of those expirations and emails the users.

def email_reminders
  Document.expires_in_one_month.each do |document|
      UserMailer.upload_new_reminder(document)
    end

    Document.expires_in_one_week.each do |document|
      UserMailer.upload_new_reminder(document)
    end
end

At a high level, that's all there is to it. I'm sure there will be plenty of smaller details you'll need to implement, but this should get you started.


Another option if you'd prefer not to run crons is to schedule a background job to send out an email on the date you'd like it to be run at. You'd want to ensure you have a datetime on the object named expires_at.

If you had a Job named ExpiringDocumentReminderJob, you could tell it to run at a specific date/time like so:

ExpiringDocumentReminderJob.set(wait_until: (document.expires_at - 7.days)).perform_later(document)

for example using ActiveJob's api could work. There are tradeoffs to both approaches, as you have to clean up the jobs if you/the user deletes/modifies the documents expires_at and some other things.


The downside of scheduling jobs is that they're very hard to cancel and manage if you need to ever do that. I find that generally outweighs a nightly cron by a lot and almost never go with the jobs route for a situation like this.


Personally, I think cron jobs are a great solution for only one server deployments. it keeps things simple to start for sure.


Cron jobs are good for multi-server setups too. Just have one server run cron jobs and your background jobs. They'll all share the same database anyways so there's nothing server-specific.


Whats the best way to tackle cron jobs on Heroku as the whenever gem isn't supported?


Use the Heroku Scheduler.


I'm not a fan of cron because it's fragile when worker nodes die. For years now I've been using a delayed_job that runs once a minute. Upon waking up, it has two tasks:

  1. Schedule for immediate execution any tasks whose time has come, and
  2. Reschedule itself for the start of the next minute.

This was originally built for timed workflow transitions, but (1) also includes scheduling jobs that handle other one-shot events, such as sending notifications that are due but not yet marked as sent. This scheme also avoids having to perform cleanup when the schedule changes.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 24,647+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.