diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee
index ae8b62e86..784f4c099 100644
--- a/server/routes/mail.coffee
+++ b/server/routes/mail.coffee
@@ -1,23 +1,213 @@
 mail = require '../commons/mail'
 MailTask = require '../mail/tasks/MailTask'
+MailSent = require '../mail/sent/MailSent'
 User = require '../users/User'
+async = require 'async'
 errors = require '../commons/errors'
 config = require '../../server_config'
 LevelSession = require '../levels/sessions/LevelSession'
 Level = require '../levels/Level'
 log = require 'winston'
 sendwithus = require '../sendwithus'
-if config.isProduction || true
-  redis = require 'redis'
-  redisClient = redis.createClient(config.redis.port,config.redis.host)
+if config.isProduction or config.redis.host isnt "localhost"
+  lockManager = require '../commons/LockManager'
+  
 
 module.exports.setup = (app) ->
   app.all config.mail.mailchimpWebhook, handleMailchimpWebHook
   app.get '/mail/cron/ladder-update', handleLadderUpdate
   app.post '/mail/task', createMailTask
-
-  #setInterval(handleScheduledMail, 5000)
+  if lockManager
+    setupScheduledEmails()
   
+setupScheduledEmails = ->
+  testForLockManager()
+  mailTaskMap = 
+    "test_mail_task": candidateUpdateProfileTask
+    
+  MailTask.find({}).lean().exec (err, mailTasks) ->
+    if err? then throw "Failed to schedule mailTasks! #{err}"
+    for mailTask in mailTasks
+      setInterval mailTaskMap[mailTask.url], mailTask.frequency*2
+      
+testForLockManager = -> unless lockManager then throw "The system isn't configured to do distributed locking!"
+  
+### Candidate Update Reminder Task ###
+emailTimeRange = (timeRange, finalCallback) ->
+  waterfallContext =
+    "timeRange": timeRange
+    "mailTaskName": @mailTaskName
+  async.waterfall [
+    findAllCandidatesWithinTimeRange.bind(waterfallContext)
+    (unfilteredCandidates, cb) -> #now filter the candidates to see if they are eligible
+      async.reject unfilteredCandidates, candidateFilter.bind(waterfallContext), (filtered) -> cb null, filtered
+    (filteredCandidates, cb) -> #Now send emails to the eligible candidates and record.
+      async.each filteredCandidates, sendReminderEmailToCandidate.bind(waterfallContext), cb
+  ], finalCallback
+  
+findAllCandidatesWithinTimeRange = (cb) ->
+  findParameters =
+    "jobProfile.updated":
+      $gt: @timeRange.start
+      $lte: @timeRange.end
+  selection =  "_id email jobProfile.name jobProfile.updated"
+  User.find(findParameters).select(selection).lean().exec cb
+  
+candidateFilter = (candidate, sentEmailFilterCallback) ->
+  findParameters =
+    "user": candidate._id
+    "mailTask": @mailTaskName
+    "metadata.timeRangeName": @timeRange.name
+    "metadata.updated": candidate.jobProfile.updated
+  MailSent.find(findParameters).lean().exec (err, sentMail) ->
+    if err? then return errors.serverError("Error fetching sent mail in email task")
+    sentEmailFilterCallback Boolean(sentMail.length)
+
+findEmployersSignedUpAfterDate = (dateObject, cb) ->
+  countParameters =
+    $or: [{"dateCreated": {$gte: dateObject}},{"signedEmployerAgreement":{$gte: dateObject}}]
+    employerAt: {$exists: true}
+    permissions: "employer"
+  User.count countParameters, cb
+  
+sendReminderEmailToCandidate = (candidate, sendEmailCallback) ->
+  findEmployersSignedUpAfterDate new Date(candidate.jobProfile.updated), (err, employersAfterCount) =>
+    context =
+      email_id: "tem_CtTLsKQufxrxoPMn7upKiL"
+      recipient:
+        address: candidate.email
+        name: candidate.jobProfile.name
+      email_data:
+        profile_updated: candidate.jobProfile.updated #format nicely
+        new_company: employersAfterCount
+    log.info "Sending #{@timeRange.name} update reminder to #{context.recipient.name}(#{context.recipient.address})"
+    newSentMail =
+      mailTask: @mailTaskName
+      user: candidate._id
+      metadata:
+        timeRangeName: @timeRange.name
+        updated: candidate.jobProfile.updated
+    MailSent.create newSentMail, (err) ->
+      if err? then return sendEmailCallback err
+      sendwithus.api.send context, (err, result) ->
+        log.error "Error sending ladder update email: #{err} with result #{result}" if err
+        sendEmailCallback err
+
+generateWeekOffset = (originalDate, numberOfWeeks) ->
+  return (new Date(originalDate.getTime() - numberOfWeeks * 7 * 24 * 60 * 60 * 1000)).toISOString()
+
+candidateUpdateProfileTask = ->
+  mailTaskName = "candidateUpdateProfileTask"
+  lockDurationMs = 6000
+  currentDate = new Date()
+  timeRanges = []
+  for weekPair in [[4, 2,'two weeks'], [8, 4, 'four weeks'], [8, 52, 'eight weeks']]
+    timeRanges.push 
+      start: generateWeekOffset currentDate, weekPair[0]
+      end: generateWeekOffset currentDate, weekPair[1]
+      name: weekPair[2]
+  lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
+    if err? then return log.error "Error getting a task lock!"
+    async.each timeRanges, emailTimeRange.bind(mailTaskName: mailTaskName), (err) ->
+      if err then return log.error JSON.stringify err else log.info "Sent candidate update reminders!"
+      lockManager.releaseLock mailTaskName, (err, result) -> if err? then return log.error err
+        
+### End Candidate Update Reminder Task ###
+### Internal Candidate Update Reminder Email ###
+
+emailInternalCandidateUpdateReminder = (cb) ->
+  currentTime = new Date()
+  beginningOfUTCDay = new Date()
+  beginningOfUTCDay.setUTCHours(0,0,0,0)
+  asyncContext =
+    "beginningOfUTCDay": beginningOfUTCDay
+    "currentTime": currentTime
+    "mailTaskName": @mailTaskName
+    
+  async.waterfall [
+    findCandidatesWhoUpdatedJobProfileToday.bind(asyncContext)
+    (unfilteredCandidates, cb) ->
+      async.reject unfilteredCandidates, candidatesUpdatedTodayFilter.bind(asyncContext), cb.bind(null,null)
+    (filteredCandidates, cb) ->
+      async.each filteredCandidates, sendInternalCandidateUpdateReminder.bind(asyncContext), cb
+  ], cb
+    
+findCandidatesWhoUpdatedJobProfileToday = (cb) ->
+  findParameters = 
+    "jobProfile.updated":
+      $lte: @currentTime.toISOString()
+      gt: @beginningOfUTCDay.toISOString()
+  User.find(findParameters).select("_id jobProfile.name jobProfile.updated").lean().exec cb
+  
+candidatesUpdatedTodayFilter = (candidate, cb) ->
+  findParameters =
+    "user": candidate._id
+    "mailTask": @mailTaskName
+    "metadata.beginningOfUTCDay": @beginningOfUTCDay
+  MailSent.find(findParameters).lean().exec (err, sentMail) ->
+    if err? then return errors.serverError("Error fetching sent mail in #{@mailTaskName}")
+    cb Boolean(sentMail.length)
+    
+sendInternalCandidateUpdateReminder = (candidate, cb) ->
+  context =
+    email_id: "tem_Ac7nhgKqatTHBCgDgjF5pE"
+    recipient:
+      address: "team@codecombat.com" #Change to whatever email address is necessary
+      name: "The CodeCombat Team"
+  log.info "Sending candidate updated reminder for #{candidate.jobProfile.name}"
+  
+  newSentMail =
+    mailTask: @mailTaskName
+    user: candidate._id
+    metadata:
+      beginningOfUTCDay: @beginningOfUTCDay
+    
+  MailSent.create newSentMail, (err) ->
+    if err? then return cb err
+    sendwithus.api.send context, (err, result) ->
+      log.error "Error sending ladder update email: #{err} with result #{result}" if err
+      cb err
+  
+internalCandidateUpdateTask = ->
+  mailTaskName = "internalCandidateUpdateTask"
+  lockDurationMs = 6000
+  lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
+    if err? then return log.error "Error getting a task lock!"
+    emailInternalCandidateUpdateReminder.apply {"mailTaskName":mailTaskName}, (err) ->
+      if err? then return log.error "There was an error sending the internal candidate update reminder."
+      lockManager.releaseLock mailTaskName, (err, result) -> if err? then return log.error err
+### End Internal Candidate Update Reminder Email ###
+  
+### Employer New Candidates Available Email ###
+employerNewCandidatesAvailableTask = ->
+  #  tem_CCcHKr95Nvu5bT7c7iHCtm
+  #initialize featuredDate to job profile updated
+  mailTaskName = "employerNewCandidatesAvailableTask"
+  lockDurationMs = 6000
+  lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
+
+### End Employer New Candidates Available Email ###
+  
+### New Recruit Leaderboard Email ###
+newRecruitLeaderboardEmailTask = ->
+  # tem_kMQFCKX3v4DNAQDsMAsPJC
+  #maxRank and maxRankTime should be recorded if isSimulating is false
+  mailTaskName = "newRecruitLeaderboardEmailTask"
+  lockDurationMs = 6000
+  lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
+
+### End New Recruit Leaderboard Email ###
+  
+### Employer Matching Candidate Notification Email ### 
+employerMatchingCandidateNotificationTask = ->
+  # tem_mYsepTfWQ265noKfZJcbBH
+  #save email filters in their own collection
+  mailTaskName = "employerMatchingCandidateNotificationTask"
+  lockDurationMs = 6000
+  lockManager.setLock mailTaskName, lockDurationMs, (err, lockResult) ->
+
+### End Employer Matching Candidate Notification Email ###
+### Ladder Update Email ###
 
 DEBUGGING = false
 LADDER_PREGAME_INTERVAL = 2 * 3600 * 1000  # Send emails two hours before players last submitted.
@@ -169,6 +359,7 @@ getScoreHistoryGraphURL = (session, daysAgo) ->
   chartData = times.join(',') + '|' + scores.join(',')
   "https://chart.googleapis.com/chart?chs=600x75&cht=lxy&chtt=Score%3A+#{currentScore}&chts=222222,12,r&chf=a,s,000000FF&chls=2&chd=t:#{chartData}&chxt=y&chxr=0,#{minScore},#{maxScore}"
 
+### End Ladder Update Email ###
 handleMailchimpWebHook = (req, res) ->
   post = req.body