diff --git a/core/src/main/java/se/su/dsv/scipro/mail/PrintingMailer.java b/core/src/main/java/se/su/dsv/scipro/mail/PrintingMailer.java
index a5c7f597be..179bffc02c 100644
--- a/core/src/main/java/se/su/dsv/scipro/mail/PrintingMailer.java
+++ b/core/src/main/java/se/su/dsv/scipro/mail/PrintingMailer.java
@@ -6,7 +6,7 @@ import se.su.dsv.scipro.file.FileDescription;
 import java.util.Arrays;
 import java.util.UUID;
 
-class PrintingMailer implements Mailer {
+public class PrintingMailer implements Mailer {
     @Override
     public MailResult mail(final String fromName, final String fromEmail, final String[] recipients, final String subject, final String message, final FileDescription attachment) {
         return new MailResult() {
diff --git a/core/src/main/java/se/su/dsv/scipro/match/AllowAllIdeaCreationJudge.java b/core/src/main/java/se/su/dsv/scipro/match/AllowAllIdeaCreationJudge.java
index f5e0035fa5..6a91588ff1 100644
--- a/core/src/main/java/se/su/dsv/scipro/match/AllowAllIdeaCreationJudge.java
+++ b/core/src/main/java/se/su/dsv/scipro/match/AllowAllIdeaCreationJudge.java
@@ -1,6 +1,6 @@
 package se.su.dsv.scipro.match;
 
-class AllowAllIdeaCreationJudge implements IdeaCreationJudge {
+public class AllowAllIdeaCreationJudge implements IdeaCreationJudge {
     @Override
     public Decision ruling(Idea idea) {
         return Decision.allowed();
diff --git a/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java b/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java
index 352c674419..5c721b2ffe 100755
--- a/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java
+++ b/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java
@@ -22,8 +22,7 @@ public abstract class AbstractWorker implements Worker {
     private WorkerData wd = null;
     private Date lastSuccessfulRun = new Date(0);
     private boolean successfulWorker = true;
-    private Provider<EntityManager> emProvider;
-    private EntityTransaction transaction;
+    private WorkerTransactionManager txManager;
 
     /**
      * Subclasses must be annotated with @Component or similar annotation in order for autowiring of dependencies to work
@@ -37,16 +36,13 @@ public abstract class AbstractWorker implements Worker {
     }
 
     @Inject
-    public void setTxManager(Provider<EntityManager> em) {
-        this.emProvider = em;
+    public void setTxManager(WorkerTransactionManager txManager) {
+        this.txManager = txManager;
     }
 
     @Override
     public void run() {
-        EntityManager em = emProvider.get();
         try {
-            transaction = em.getTransaction();
-
             wd = workerDataService.getWorkerDataByName(getName());
             if (wd != null) {
                 lastSuccessfulRun = wd.getLastSuccessfulRun();
@@ -72,9 +68,7 @@ public abstract class AbstractWorker implements Worker {
                 setSuccessfulWorker(false);
             }
             finally {
-                if (transaction.isActive() && em.isOpen()) {
-                    transaction.rollback();
-                }
+                txManager.rollbackIfActive();
             }
 
             // in case a job crashes or clears the cache (so the entity is detached)
@@ -87,9 +81,7 @@ public abstract class AbstractWorker implements Worker {
             saveWorkerData();
 
         } finally {
-            if (transaction.isActive() && em.isOpen()) {
-                transaction.rollback();
-            }
+            txManager.rollbackIfActive();
         }
     }
 
@@ -139,15 +131,15 @@ public abstract class AbstractWorker implements Worker {
     protected abstract void doWork();
 
     protected void beginTransaction() {
-        transaction.begin();
+        txManager.begin();
     }
 
     protected void commitTransaction() {
-        transaction.commit();
+        txManager.commit();
     }
 
     protected void rollbackTransaction() {
-        transaction.rollback();
+        txManager.rollback();
     }
 
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerModule.java b/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerModule.java
index 370d486d83..db17d05aec 100644
--- a/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerModule.java
+++ b/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerModule.java
@@ -15,9 +15,9 @@ public class WorkerModule extends AbstractModule {
 
     @Override
     protected void configure() {
-        Multibinder.newSetBinder(binder(), Lifecycle.class).addBinding().to(SchedulerImpl.class);
+        //Multibinder.newSetBinder(binder(), Lifecycle.class).addBinding().to(SchedulerImpl.class);
         bind(ScheduledExecutorService.class).toInstance(Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS));
-        bind(Scheduler.class).to(SchedulerImpl.class).in(Scopes.SINGLETON);
+        //bind(Scheduler.class).to(SchedulerImpl.class).in(Scopes.SINGLETON);
         bind(TemporaryWorkerScheduler.class).asEagerSingleton();
         bind(WorkerDataService.class).to(WorkerDataServiceImpl.class);
         bind(ThesisUploadDeadlineWorker.class);
diff --git a/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerTransactionManager.java b/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerTransactionManager.java
new file mode 100644
index 0000000000..1ff2b458d4
--- /dev/null
+++ b/core/src/main/java/se/su/dsv/scipro/workerthreads/WorkerTransactionManager.java
@@ -0,0 +1,20 @@
+package se.su.dsv.scipro.workerthreads;
+
+public interface WorkerTransactionManager {
+    void rollbackIfActive();
+
+    /**
+     * @throws IllegalStateException if a transaction is already active
+     */
+    void begin();
+
+    /**
+     * @throws IllegalStateException if a transaction is not active
+     */
+    void commit();
+
+    /**
+     * @throws IllegalStateException if a transaction is not active
+     */
+    void rollback();
+}
diff --git a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java
index 9c766ce817..f51c23592a 100644
--- a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java
+++ b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java
@@ -1,10 +1,6 @@
 package se.su.dsv.scipro.integration.daisy.workers;
 
 import com.google.common.eventbus.EventBus;
-import com.google.inject.persist.UnitOfWork;
-import com.google.inject.util.Providers;
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityTransaction;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -23,6 +19,7 @@ import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.system.UserService;
 import se.su.dsv.scipro.workerthreads.WorkerData;
 import se.su.dsv.scipro.workerthreads.WorkerDataService;
+import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
 
 import java.util.*;
 
@@ -47,21 +44,16 @@ public class GradingCompletedMilestoneActivatorTest {
     @Mock
     private EventBus eventBus;
     @Mock
-    private UnitOfWork unitOfWork;
-    @Mock
-    private EntityManager entityManager;
-    @Mock
-    private EntityTransaction entityTransaction;
+    private WorkerTransactionManager workerTransactionManager;
     @Mock
     private WorkerDataService workerDataService;
 
     @BeforeEach
     public void setup() {
         gradingCompletedMilestoneActivator = new GradingCompletedMilestoneActivator(projectService, daisyAPI, eventBus, userService);
-        gradingCompletedMilestoneActivator.setTxManager(Providers.of(entityManager));
+        gradingCompletedMilestoneActivator.setTxManager(workerTransactionManager);
         gradingCompletedMilestoneActivator.setWorkerDataService(workerDataService);
         when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
-        when(entityManager.getTransaction()).thenReturn(entityTransaction);
 
         project.setId(3493L);
         project.setIdentifier(234);
diff --git a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java
index d2f53aca29..dfd523ad4f 100644
--- a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java
+++ b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java
@@ -1,10 +1,6 @@
 package se.su.dsv.scipro.integration.daisy.workers;
 
-import com.google.inject.persist.UnitOfWork;
-import com.google.inject.util.Providers;
 import com.querydsl.core.types.Predicate;
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityTransaction;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -26,6 +22,7 @@ import se.su.dsv.scipro.system.Unit;
 import se.su.dsv.scipro.system.User;
 import se.su.dsv.scipro.workerthreads.WorkerData;
 import se.su.dsv.scipro.workerthreads.WorkerDataService;
+import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
 
 import java.time.LocalDate;
 import java.util.*;
@@ -45,10 +42,8 @@ public class ProjectExporterTest {
     private @Mock ExporterFacade exporterFacade;
     private @Mock DaisyAPI daisyAPI;
     private @Mock ExternalExporter externalExporter;
-    private @Mock EntityManager entityManager;
-    private @Mock EntityTransaction entityTransaction;
+    private @Mock WorkerTransactionManager workerTransactionManager;
     private @Mock WorkerDataService workerDataService;
-    private @Mock UnitOfWork unitOfWork;
     private @Mock FinalSeminarService finalSeminarService;
     private @Mock FinalThesisService finalThesisService;
 
@@ -61,10 +56,9 @@ public class ProjectExporterTest {
     @BeforeEach
     public void setUp() {
         projectExporter = new ProjectExporter(projectRepo, exporterFacade, daisyAPI, externalExporter, finalSeminarService, finalThesisService);
-        projectExporter.setTxManager(Providers.of(entityManager));
+        projectExporter.setTxManager(workerTransactionManager);
         projectExporter.setWorkerDataService(workerDataService);
         when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
-        when(entityManager.getTransaction()).thenReturn(entityTransaction);
 
         Unit unit = new Unit();
         unit.setIdentifier(239478);
diff --git a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java
index 52b7094897..048d2165e7 100644
--- a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java
+++ b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java
@@ -1,7 +1,5 @@
 package se.su.dsv.scipro.integration.daisy.workers;
 
-import com.google.inject.persist.UnitOfWork;
-import com.google.inject.util.Providers;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -18,8 +16,8 @@ import se.su.dsv.scipro.project.QProject;
 import se.su.dsv.scipro.workerthreads.WorkerData;
 import se.su.dsv.scipro.workerthreads.WorkerDataService;
 
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityTransaction;
+import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -38,9 +36,7 @@ public class ProjectFinalizerTest {
 
     private @Mock DaisyAPI daisyAPI;
     private @Mock ProjectService projectService;
-    private @Mock UnitOfWork unitOfWork;
-    private @Mock EntityManager entityManager;
-    private @Mock EntityTransaction entityTransaction;
+    private @Mock WorkerTransactionManager workerTransactionManager;
     private @Mock WorkerDataService workerDataService;
     private @Mock ThesisApprovedHistoryService thesisApprovedHistoryService;
 
@@ -48,10 +44,9 @@ public class ProjectFinalizerTest {
     @BeforeEach
     public void setup() {
         projectFinalizer = new ProjectFinalizer(projectService, daisyAPI, thesisApprovedHistoryService);
-        projectFinalizer.setTxManager(Providers.of(entityManager));
+        projectFinalizer.setTxManager(workerTransactionManager);
         projectFinalizer.setWorkerDataService(workerDataService);
         when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
-        when(entityManager.getTransaction()).thenReturn(entityTransaction);
 
         project.setId(3493L);
         project.setIdentifier(234);
diff --git a/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java b/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java
index 360419ac35..12860dcade 100644
--- a/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java
+++ b/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java
@@ -21,8 +21,6 @@ import se.su.dsv.scipro.sukat.LDAP;
 import se.su.dsv.scipro.sukat.Sukat;
 
 import java.time.Clock;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 
 @Configuration
 @ComponentScan(
@@ -34,13 +32,6 @@ import java.util.concurrent.ScheduledExecutorService;
                 ".*GradingServiceImpl"
         }))
 public class CoreConfig {
-    private static final int NUMBER_OF_WORKER_THREADS = 4;
-
-    @Bean
-    public ScheduledExecutorService scheduledExecutorService() {
-        return Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS);
-    }
-
     @Bean
     public EventBus eventBus() {
         return new EventBus();
diff --git a/war/src/main/java/se/su/dsv/scipro/war/MailConfig.java b/war/src/main/java/se/su/dsv/scipro/war/MailConfig.java
new file mode 100644
index 0000000000..35b7a0ff64
--- /dev/null
+++ b/war/src/main/java/se/su/dsv/scipro/war/MailConfig.java
@@ -0,0 +1,45 @@
+package se.su.dsv.scipro.war;
+
+import jakarta.mail.Session;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import se.su.dsv.scipro.data.facade.MailFacade;
+import se.su.dsv.scipro.file.FileService;
+import se.su.dsv.scipro.generalsystemsettings.GeneralSystemSettingsService;
+import se.su.dsv.scipro.mail.Mail;
+import se.su.dsv.scipro.mail.Mailer;
+import se.su.dsv.scipro.mail.PrintingMailer;
+import se.su.dsv.scipro.mail.RedirectingMailer;
+import se.su.dsv.scipro.profiles.CurrentProfile;
+
+import java.util.Properties;
+
+@Configuration
+public class MailConfig {
+    @Bean
+    public MailFacade mailFacade() {
+        return new MailFacade();
+    }
+
+    @Bean
+    public Mailer mailer(
+            CurrentProfile currentProfile,
+            Session session,
+            FileService fileDescriptionService)
+    {
+        return switch (currentProfile.getCurrentProfile()) {
+            case DEV -> new PrintingMailer();
+            case PROD -> new Mail(session, fileDescriptionService);
+            case TEST -> new RedirectingMailer(session, "scipro-mailtest@dsv.su.se", fileDescriptionService);
+        };
+    }
+
+    @Bean
+    public Session session(GeneralSystemSettingsService generalSystemSettings) {
+        String smtpHost = generalSystemSettings.getGeneralSystemSettingsInstance().getSmtpServer();
+        Properties properties = new Properties();
+        properties.setProperty("mail.smtp.host", smtpHost);
+        properties.setProperty("mail.smtp.sendpartial", Boolean.toString(true));
+        return Session.getDefaultInstance(properties);
+    }
+}
diff --git a/war/src/main/java/se/su/dsv/scipro/war/Main.java b/war/src/main/java/se/su/dsv/scipro/war/Main.java
index 3f629e21ab..ca339abb8b 100644
--- a/war/src/main/java/se/su/dsv/scipro/war/Main.java
+++ b/war/src/main/java/se/su/dsv/scipro/war/Main.java
@@ -37,7 +37,7 @@ import java.util.Set;
 
 @SpringBootApplication
 @EntityScan("se.su.dsv.scipro")
-@Import({CoreConfig.class, ApiConfig.class})
+@Import({CoreConfig.class, ApiConfig.class, WorkerConfig.class, MailConfig.class})
 public class Main extends SpringBootServletInitializer implements ServletContainerInitializer {
     @Override
     public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
diff --git a/war/src/main/java/se/su/dsv/scipro/war/SpringManagedWorkerTransactions.java b/war/src/main/java/se/su/dsv/scipro/war/SpringManagedWorkerTransactions.java
new file mode 100644
index 0000000000..ae66a50e36
--- /dev/null
+++ b/war/src/main/java/se/su/dsv/scipro/war/SpringManagedWorkerTransactions.java
@@ -0,0 +1,50 @@
+package se.su.dsv.scipro.war;
+
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
+
+public class SpringManagedWorkerTransactions implements WorkerTransactionManager {
+    private final PlatformTransactionManager platformTransactionManager;
+
+    private TransactionStatus activeTransaction;
+
+    public SpringManagedWorkerTransactions(PlatformTransactionManager platformTransactionManager) {
+        this.platformTransactionManager = platformTransactionManager;
+    }
+
+    @Override
+    public void rollbackIfActive() {
+        if (this.activeTransaction != null) {
+            platformTransactionManager.rollback(this.activeTransaction);
+            this.activeTransaction = null;
+        }
+    }
+
+    @Override
+    public void begin() {
+        if (this.activeTransaction != null) {
+            throw new IllegalStateException("A transaction is already active");
+        }
+        this.activeTransaction = platformTransactionManager.getTransaction(TransactionDefinition.withDefaults());
+    }
+
+    @Override
+    public void commit() {
+        if (this.activeTransaction == null) {
+            throw new IllegalStateException("A transaction is not active");
+        }
+        platformTransactionManager.commit(this.activeTransaction);
+        this.activeTransaction = null;
+    }
+
+    @Override
+    public void rollback() {
+        if (this.activeTransaction == null) {
+            throw new IllegalStateException("A transaction is not active");
+        }
+        platformTransactionManager.rollback(this.activeTransaction);
+        this.activeTransaction = null;
+    }
+}
diff --git a/war/src/main/java/se/su/dsv/scipro/war/WorkerConfig.java b/war/src/main/java/se/su/dsv/scipro/war/WorkerConfig.java
new file mode 100644
index 0000000000..400fc754a7
--- /dev/null
+++ b/war/src/main/java/se/su/dsv/scipro/war/WorkerConfig.java
@@ -0,0 +1,94 @@
+package se.su.dsv.scipro.war;
+
+import jakarta.inject.Provider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.context.annotation.Scope;
+import org.springframework.transaction.PlatformTransactionManager;
+import se.su.dsv.scipro.file.FileService;
+import se.su.dsv.scipro.mail.MailEventWorker;
+import se.su.dsv.scipro.match.AllowAllIdeaCreationJudge;
+import se.su.dsv.scipro.match.IdeaCreationJudge;
+import se.su.dsv.scipro.plagiarism.PlagiarismRequestRepository;
+import se.su.dsv.scipro.plagiarism.PlagiarismSubmitter;
+import se.su.dsv.scipro.plagiarism.urkund.StatusPollingWorker;
+import se.su.dsv.scipro.plagiarism.urkund.UrkundService;
+import se.su.dsv.scipro.plagiarism.urkund.UrkundSettings;
+import se.su.dsv.scipro.plagiarism.urkund.UrkundSettingsRepository;
+import se.su.dsv.scipro.projectpartner.RemoveFulfilledPartnerAdsWorker;
+import se.su.dsv.scipro.reviewing.ReviewerDecisionReminderWorker;
+import se.su.dsv.scipro.workerthreads.GradeFinalSeminarParticipantReminderWorker;
+import se.su.dsv.scipro.workerthreads.IdeaExportWorker;
+import se.su.dsv.scipro.workerthreads.ManualMatchRemindWorker;
+import se.su.dsv.scipro.workerthreads.NotificationCompilationWorker;
+import se.su.dsv.scipro.workerthreads.Scheduler;
+import se.su.dsv.scipro.workerthreads.TemporaryWorkerScheduler;
+import se.su.dsv.scipro.workerthreads.ThesisUploadDeadlineWorker;
+import se.su.dsv.scipro.workerthreads.ThesisUploadReminderWorker;
+import se.su.dsv.scipro.workerthreads.WorkerTransactionManager;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+@Configuration
+@ComponentScan(
+        basePackages = "se.su.dsv.scipro",
+        includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Worker$"),
+        excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*[Dd]aisy.*")
+)
+public class WorkerConfig {
+    private static final int NUMBER_OF_WORKER_THREADS = 4;
+
+    @Bean
+    public ScheduledExecutorService scheduledExecutorService() {
+        return Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS);
+    }
+
+    @Bean
+    public TemporaryWorkerScheduler temporaryWorkerScheduler(
+            Scheduler scheduler,
+            Provider<MailEventWorker> mailEventWorker,
+            Provider<NotificationCompilationWorker> notificationCompilationWorker,
+            Provider<IdeaExportWorker> ideaExportWorker,
+            Provider<ThesisUploadReminderWorker> thesisUploadReminderWorker,
+            Provider<ThesisUploadDeadlineWorker> thesisUploadDeadlineWorker,
+            Provider<ManualMatchRemindWorker> manualMatchRemindWorkerProvider,
+            Provider<ReviewerDecisionReminderWorker> reviewerDecisionReminderWorker,
+            Provider<PlagiarismSubmitter> plagiarismSubmitter,
+            Provider<StatusPollingWorker> urkundPoller,
+            Provider<RemoveFulfilledPartnerAdsWorker> removeFulfilledPartnerAds,
+            Provider<GradeFinalSeminarParticipantReminderWorker> gradeFinalSeminarParticipantReminderWorkerProvider)
+    {
+        return new TemporaryWorkerScheduler(scheduler, mailEventWorker, notificationCompilationWorker, ideaExportWorker, thesisUploadReminderWorker, thesisUploadDeadlineWorker, manualMatchRemindWorkerProvider, reviewerDecisionReminderWorker, plagiarismSubmitter, urkundPoller, removeFulfilledPartnerAds, gradeFinalSeminarParticipantReminderWorkerProvider);
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(IdeaCreationJudge.class)
+    public IdeaCreationJudge ideaCreationJudge() {
+        return new AllowAllIdeaCreationJudge();
+    }
+
+    @Bean
+    public PlagiarismSubmitter plagiarismSubmitter(
+            Provider<UrkundSettings> urkundSettings,
+            PlagiarismRequestRepository plagiarismRequestRepository,
+            UrkundService urkundService,
+            FileService fileService)
+    {
+        return new PlagiarismSubmitter(urkundSettings, plagiarismRequestRepository, urkundService, fileService);
+    }
+
+    @Bean
+    public UrkundSettings urkundSettings(UrkundSettingsRepository urkundSettingsRepository) {
+        return urkundSettingsRepository.getSettings();
+    }
+
+    @Bean
+    @Scope("prototype")
+    public WorkerTransactionManager workerTransactionManager(PlatformTransactionManager platformTransactionManager) {
+        return new SpringManagedWorkerTransactions(platformTransactionManager);
+    }
+}
diff --git a/core/src/main/java/se/su/dsv/scipro/workerthreads/SchedulerImpl.java b/war/src/main/java/se/su/dsv/scipro/workerthreads/SchedulerImpl.java
similarity index 59%
rename from core/src/main/java/se/su/dsv/scipro/workerthreads/SchedulerImpl.java
rename to war/src/main/java/se/su/dsv/scipro/workerthreads/SchedulerImpl.java
index d8ecf35252..8cd979ede0 100755
--- a/core/src/main/java/se/su/dsv/scipro/workerthreads/SchedulerImpl.java
+++ b/war/src/main/java/se/su/dsv/scipro/workerthreads/SchedulerImpl.java
@@ -1,12 +1,24 @@
 package se.su.dsv.scipro.workerthreads;
 
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityManagerFactory;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+import org.springframework.orm.jpa.EntityManagerHolder;
+import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 import se.su.dsv.scipro.system.Lifecycle;
 
 import jakarta.inject.Inject;
 import jakarta.inject.Provider;
 import jakarta.inject.Singleton;
+
+import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
@@ -19,13 +31,15 @@ public class SchedulerImpl implements Lifecycle, Scheduler {
     private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerImpl.class);
 
     private final ScheduledExecutorService scheduledExecutorService;
+    private final EntityManagerFactory emf;
 
     private final Set<Task> tasks = new TreeSet<>(new Task.ByDescriptionComparator());
     private final Set<Task> runningWorkers = Collections.synchronizedSet(new HashSet<>());
 
     @Inject
-    public SchedulerImpl(ScheduledExecutorService scheduledExecutorService) {
+    public SchedulerImpl(ScheduledExecutorService scheduledExecutorService, EntityManagerFactory emf) {
         this.scheduledExecutorService = scheduledExecutorService;
+        this.emf = emf;
     }
 
     @Override
@@ -43,6 +57,14 @@ public class SchedulerImpl implements Lifecycle, Scheduler {
     private Runnable tracked(final Task task) {
         return () -> {
             try {
+                // Since we're not in a web request when running a scheduled job, Spring's OpenEntityManagerInView
+                // filter is not active. Therefore, we need to manually bind a new EntityManager to the current thread
+                // to be shared by all DAOs. If we do not, we will get problems with detached entity passed to persist
+                // since every read and write from the database will be in a new EntityManager.
+                EntityManager em = emf.createEntityManager();
+                EntityManagerHolder emHolder = new EntityManagerHolder(em);
+                TransactionSynchronizationManager.bindResource(emf, emHolder);
+
                 runningWorkers.add(task);
                 task.execute();
             }
@@ -51,6 +73,11 @@ public class SchedulerImpl implements Lifecycle, Scheduler {
             }
             finally {
                 runningWorkers.remove(task);
+
+                // Clean up the shared EntityManager
+                EntityManagerHolder emHolder = (EntityManagerHolder)
+                        TransactionSynchronizationManager.unbindResource(emf);
+                EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
             }
         };
     }
diff --git a/core/src/main/java/se/su/dsv/scipro/workerthreads/TaskScheduling.java b/war/src/main/java/se/su/dsv/scipro/workerthreads/TaskScheduling.java
similarity index 100%
rename from core/src/main/java/se/su/dsv/scipro/workerthreads/TaskScheduling.java
rename to war/src/main/java/se/su/dsv/scipro/workerthreads/TaskScheduling.java
diff --git a/core/src/test/java/se/su/dsv/scipro/workerthreads/SchedulerImplTest.java b/war/src/test/java/se/su/dsv/scipro/workerthreads/SchedulerImplTest.java
similarity index 100%
rename from core/src/test/java/se/su/dsv/scipro/workerthreads/SchedulerImplTest.java
rename to war/src/test/java/se/su/dsv/scipro/workerthreads/SchedulerImplTest.java