diff --git a/job/components.go b/job/components.go
new file mode 100644
index 00000000..8a371adc
--- /dev/null
+++ b/job/components.go
@@ -0,0 +1,16 @@
+package job
+
+import (
+	"time"
+
+	"github.com/flanksource/duty/context"
+)
+
+func CleanupSoftDeletedComponents(ctx context.Context, olderThan time.Duration) (int, error) {
+	tx := ctx.DB().Exec("DELETE FROM components WHERE deleted_at < NOW() - interval '1 SECONDS' * ?", int64(olderThan.Seconds()))
+	if tx.Error != nil {
+		return 0, tx.Error
+	}
+
+	return int(tx.RowsAffected), nil
+}
diff --git a/job/job.go b/job/job.go
index 65bd525e..afb63e1c 100644
--- a/job/job.go
+++ b/job/job.go
@@ -17,6 +17,11 @@ import (
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+const (
+	ResourceTypeComponent     = "components"
+	ResourceTypeCheckStatuses = "check_statuses"
+)
+
 var RetentionHour = Retention{
 	Success:  1,
 	Failed:   3,
diff --git a/notification.go b/notification.go
deleted file mode 100644
index efe0186f..00000000
--- a/notification.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package duty
-
-import (
-	"fmt"
-
-	"github.com/flanksource/duty/models"
-)
-
-// DeleteNotificationSendHistory deletes notification send history
-// older than the given duration.
-func DeleteNotificationSendHistory(ctx DBContext, days int) (int64, error) {
-	tx := ctx.DB().
-		Model(&models.NotificationSendHistory{}).
-		Where(fmt.Sprintf("created_at < NOW() - INTERVAL '%d DAYS'", days)).
-		Delete(&models.NotificationSendHistory{})
-	return tx.RowsAffected, tx.Error
-}
diff --git a/tests/job_components_test.go b/tests/job_components_test.go
new file mode 100644
index 00000000..04867f80
--- /dev/null
+++ b/tests/job_components_test.go
@@ -0,0 +1,47 @@
+package tests
+
+import (
+	"time"
+
+	"github.com/flanksource/duty/job"
+	"github.com/flanksource/duty/models"
+	"github.com/flanksource/duty/tests/fixtures/dummy"
+	"github.com/google/uuid"
+	ginkgo "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/samber/lo"
+)
+
+var _ = ginkgo.Describe("Soft deleted components", ginkgo.Ordered, func() {
+	var softDeletedComponents []models.Component
+
+	ginkgo.It("should populated dummy deleted components", func() {
+		data := dummy.GenerateDynamicDummyData(DefaultContext.DB())
+		for i := range data.Components {
+			data.Components[i].AgentID = uuid.Nil
+
+			if i == 0 {
+				data.Components[i].DeletedAt = lo.ToPtr(dummy.CurrentTime.Add(-10 * time.Minute))
+				continue
+			}
+
+			data.Components[i].DeletedAt = lo.ToPtr(dummy.CurrentTime.Add(-time.Hour * 24 * 7))
+		}
+
+		softDeletedComponents = data.Components
+		err := DefaultContext.DB().Create(&softDeletedComponents).Error
+		Expect(err).ToNot(HaveOccurred())
+	})
+
+	ginkgo.It("should delete soft deleted components", func() {
+		count, err := job.CleanupSoftDeletedComponents(DefaultContext, time.Hour*24)
+		Expect(err).ToNot(HaveOccurred())
+
+		Expect(count).To(Equal(len(softDeletedComponents) - 1))
+	})
+
+	ginkgo.It("should cleanup the newly added soft deleted components", func() {
+		err := DefaultContext.DB().Delete(&softDeletedComponents).Error
+		Expect(err).ToNot(HaveOccurred())
+	})
+})