diff --git a/record-and-playback/core/lib/recordandplayback/workers/events_worker.rb b/record-and-playback/core/lib/recordandplayback/workers/events_worker.rb
old mode 100644
new mode 100755
index 555380e8f3ff6bf4a37e7f4e059c23b39916e9e0..43c5d8070e449cd984377e303601b471a8873d45
--- a/record-and-playback/core/lib/recordandplayback/workers/events_worker.rb
+++ b/record-and-playback/core/lib/recordandplayback/workers/events_worker.rb
@@ -20,10 +20,10 @@
 module BigBlueButton
   module Resque
     class EventsWorker < BaseWorker
-      @queue = 'rap:archive'
+      @queue = 'rap:events'
 
       def store_events(target_dir)
-        @logger.info("Keeping events for #{meeting_id}")
+        @logger.info("Keeping events for #{@meeting_id}")
         redis = BigBlueButton::RedisWrapper.new(@props['redis_host'], @props['redis_port'], @props['redis_password'])
         events_archiver = BigBlueButton::RedisEventsArchiver.new(redis)
 
@@ -32,7 +32,7 @@ module BigBlueButton
 
         # When the events script is responsible for archiving events, the sanity script (which normally clears the events
         # from redis) will not get run, so we have to clear them here.
-        events_archiver.delete_events(meeting_id) if @break_timestamp.nil?
+        events_archiver.delete_events(@meeting_id) if @break_timestamp.nil?
       end
 
       def perform
@@ -43,20 +43,21 @@ module BigBlueButton
             next true # We're inside a block and want to return to the block's caller, not return from perform
           end
 
+          remove_status_files
+
           target_dir = "#{@events_dir}/#{@meeting_id}"
           FileUtils.mkdir_p(target_dir)
 
+          raw_events_xml = "#{@recording_dir}/raw/#{@meeting_id}/events.xml"
           if File.exist?(raw_events_xml)
             # This is a recorded meeting. The archive step should have already run, so copy the events.xml from the raw
             # recording directory.
-            FileUtils.cp("#{@recording_dir}/raw/#{@meeting_id}/events.xml", "#{target_dir}/events.xml")
+            FileUtils.cp(raw_events_xml, "#{target_dir}/events.xml")
           else
             # This meeting was not recorded. We need to run the (incremental, if break_timestamp was provided) events archiving.
             store_events(target_dir)
           end
 
-          FileUtils.rm_f(@ended_done)
-
           # Only run post events scripts after full meeting ends, not during segments
           run_post_scripts(@post_scripts_path) if @break_timestamp.nil?
 
@@ -64,8 +65,13 @@ module BigBlueButton
         end
       end
 
+      def remove_status_files
+        FileUtils.rm_f(@ended_done)
+      end
+
       def initialize(opts)
         super(opts)
+        @single_step = true
         @step_name = 'events'
         @events_dir = @props['events_dir']
         @post_scripts_path = File.join(BigBlueButton.rap_scripts_path, 'post_events')
diff --git a/record-and-playback/core/scripts/rap-starter.rb b/record-and-playback/core/scripts/rap-starter.rb
index 2bd2686bf7594cbe3cc7cdf0a4dc1e31b80825f4..ecd459deb8dd3552bebd889506866897060e0df1 100755
--- a/record-and-playback/core/scripts/rap-starter.rb
+++ b/record-and-playback/core/scripts/rap-starter.rb
@@ -42,23 +42,30 @@ def parse_meeting_id(done_file)
   end
 end
 
-def keep_meeting_events(recording_dir, ended_done_file)
-  id = File.basename(ended_done_file, '.done')
-  BigBlueButton.logger.debug("Seen new ended done file for #{id}")
-  attrs = parse_meeting_id(id)
+def keep_meeting_events(recording_dir, file, from_archive)
+  # When keeping events from archive:
+  #   - <meeting_id>.done
+  #   - <meeting_id>.norecord
+  id = File.basename(file).split('.').first
+  attrs = parse_meeting_id("#{id}.done") # Inject .done to use the same parser
   return if attrs.nil?
 
-  # If a meeting was recorded, the events will be archived by the archive step, and
-  # the events script will be run after that (it'll grab the already archived events
-  # from the recording raw directory)
-  if File.exist?("#{recording_dir}/status/recorded/#{id}.done") || File.exist?("#{recording_dir}/raw/#{attrs[:meeting_id]}")
-    BigBlueButton.logger.info("Meeting #{id} had recording enabled, events will be handled after recording archive")
-    return
-  end
+  if from_archive
+    ended_done_file = "#{recording_dir}/status/ended/#{attrs[:meeting_id]}.done"
+    # Skip if keep events is not enabled for this meeting
+    return unless File.exist?(ended_done_file)
+
+    BigBlueButton.logger.info("Enqueueing job to keep events from archive #{attrs.inspect}")
+    Resque.enqueue(BigBlueButton::Resque::EventsWorker, attrs)
+  else
+    recorded_done_file = "#{recording_dir}/status/recorded/#{attrs[:meeting_id]}.done"
+    raw_dir = "#{recording_dir}/raw/#{attrs[:meeting_id]}"
+    # Skip if events will be archived
+    return if File.exist?(recorded_done_file) || File.exist?(raw_dir)
 
-  BigBlueButton.logger.info("Enqueueing job to keep events #{attrs.inspect}")
-  Resque.enqueue(BigBlueButton::Resque::EventsWorker, attrs)
-  FileUtils.rm_f(ended_done_file)
+    BigBlueButton.logger.info("Enqueueing job to keep events #{attrs.inspect}")
+    Resque.enqueue(BigBlueButton::Resque::EventsWorker, attrs)
+  end
 end
 
 def archive_recorded_meetings(recording_dir, done_file)
@@ -86,29 +93,45 @@ begin
   BigBlueButton.logger.debug('Running rap-starter...')
 
   recording_dir = props['recording_dir']
-  ended_status_dir = "#{recording_dir}/status/ended"
   recorded_status_dir = "#{recording_dir}/status/recorded"
 
-  # Check for missed "ended" .done files
-  ended_done_files = Dir.glob("#{ended_status_dir}/*.done")
-  ended_done_files.each do |ended_done_file|
-    keep_meeting_events(recording_dir, ended_done_file)
-  end
-
   # Check for missed "recorded" .done files
   recorded_done_files = Dir.glob("#{recorded_status_dir}/*.done")
   recorded_done_files.each do |recorded_done_file|
     archive_recorded_meetings(recording_dir, recorded_done_file)
   end
 
+  ended_status_dir = "#{recording_dir}/status/ended"
+
+  # Check for missed "ended" .done files
+  ended_done_files = Dir.glob("#{ended_status_dir}/*.done")
+  ended_done_files.each do |ended_done_file|
+    keep_meeting_events(recording_dir, ended_done_file, false)
+  end
+
   # Listen the directories for when new files are created
-  BigBlueButton.logger.info("Setting up inotify watch on #{ended_status_dir}")
   notifier = INotify::Notifier.new
+
+  # - record=false
+  # - events.xml will be created from redis records
+  BigBlueButton.logger.info("Setting up inotify watch on #{ended_status_dir}")
   notifier.watch(ended_status_dir, :moved_to, :create) do |event|
     next unless event.name.end_with?('.done')
 
-    keep_meeting_events(recording_dir, event.absolute_name)
+    keep_meeting_events(recording_dir, event.absolute_name, false)
+  end
+
+  archived_status_dir = "#{recording_dir}/status/archived"
+
+  # - record=true
+  # - events.xml will be copied from archived
+  BigBlueButton.logger.info("Setting up inotify watch on #{archived_status_dir}")
+  notifier.watch(archived_status_dir, :moved_to, :create) do |event|
+    next if event.name.end_with?('.fail')
+
+    keep_meeting_events(recording_dir, event.absolute_name, true)
   end
+
   BigBlueButton.logger.info("Setting up inotify watch on #{recorded_status_dir}")
   notifier.watch(recorded_status_dir, :moved_to, :create) do |event|
     next unless event.name.end_with?('.done')
diff --git a/record-and-playback/core/systemd/bbb-rap-resque-worker.service b/record-and-playback/core/systemd/bbb-rap-resque-worker.service
index 71467bfc8ff03aa0cf34b5882467f7aa257042bd..d092b65a80e67fb6498397cec47fa8fef7c369b4 100644
--- a/record-and-playback/core/systemd/bbb-rap-resque-worker.service
+++ b/record-and-playback/core/systemd/bbb-rap-resque-worker.service
@@ -5,7 +5,7 @@ Description=BigBlueButton resque worker for recordings
 Type=simple
 ExecStart=/bin/sh -c '/usr/bin/rake -f ../Rakefile resque:workers >> /var/log/bigbluebutton/bbb-rap-worker.log'
 WorkingDirectory=/usr/local/bigbluebutton/core/scripts
-Environment=QUEUE=rap:archive,rap:publish,rap:process,rap:sanity,rap:captions
+Environment=QUEUE=rap:archive,rap:publish,rap:process,rap:sanity,rap:captions,rap:events
 Environment=COUNT=1
 # Environment=VVERBOSE=1
 User=bigbluebutton