diff --git a/churros.go b/churros.go
index 69b972fd93c9d082eed31bd6ba5ee318e1a042b4..9d52447e45ee23a1bc965acab688a268abb73b97 100644
--- a/churros.go
+++ b/churros.go
@@ -48,12 +48,31 @@ func (id *ChurrosId) UnmarshalText(text []byte) error {
 }
 
 func (msg Message) CreateInDatabaseNotifications(groupId string, subs []Subscription) {
-	if config.DryRunMode {
+	if config.DryRunMode && len(config.DryRunExceptions) == 0 {
 		ll.Warn("dry run mode enabled, not creating notifications in database")
 		return
 	}
+
+	subsFilter := func(sub Subscription) bool { return true }
+
+	if len(config.DryRunExceptions) > 0 && config.DryRunMode {
+		ll.Warn("dry run mode enabled: only creating notifications in database for %+v", config.DryRunExceptions)
+		subsFilter = func(sub Subscription) bool {
+			for _, username := range config.DryRunExceptions {
+				if username == sub.Owner.Uid {
+					return true
+				}
+			}
+			return false
+		}
+	}
+
 	// Create sequentially: this is not something that has to be done fast, and parallelizing would swamp the database connections
 	for _, sub := range subs {
+		if !subsFilter(sub) {
+			continue
+		}
+
 		prisma.Notification.CreateOne(
 			db.Notification.Subscription.Link(
 				db.NotificationSubscription.Endpoint.Equals(sub.Webpush.Endpoint),
diff --git a/config.go b/config.go
index 8360bf412fe350b6ef5a8d10bce9bfe8dac40e70..e8841334af7200e908c6dc2e48f42265f183be8b 100644
--- a/config.go
+++ b/config.go
@@ -12,17 +12,18 @@ import (
 )
 
 type Configuration struct {
-	ChurrosDatabaseURL         string `env:"DATABASE_URL"`
-	RedisURL                   string `env:"REDIS_URL"`
-	NatsURL                    string `env:"NATS_URL" envDefault:"nats://localhost:4222"`
-	VapidPublicKey             string `env:"PUBLIC_VAPID_KEY"`
-	VapidPrivateKey            string `env:"VAPID_PRIVATE_KEY"`
-	ContactEmail               string `env:"CONTACT_EMAIL"`
-	FirebaseServiceAccount     string `env:"FIREBASE_SERVICE_ACCOUNT"`
-	StartupScheduleRestoration string `env:"STARTUP_SCHEDULE_RESTORATION" envDefault:"enabled"`
-	AppPackageId               string `env:"APP_PACKAGE_ID" envDefault:"app.churros"`
-	HealthCheckPort            int    `env:"HEALTH_CHECK_PORT" envDefault:"8080"`
-	DryRunMode                 bool   `env:"DRY_RUN" envDefault:"false"`
+	ChurrosDatabaseURL         string   `env:"DATABASE_URL"`
+	RedisURL                   string   `env:"REDIS_URL"`
+	NatsURL                    string   `env:"NATS_URL" envDefault:"nats://localhost:4222"`
+	VapidPublicKey             string   `env:"PUBLIC_VAPID_KEY"`
+	VapidPrivateKey            string   `env:"VAPID_PRIVATE_KEY"`
+	ContactEmail               string   `env:"CONTACT_EMAIL"`
+	FirebaseServiceAccount     string   `env:"FIREBASE_SERVICE_ACCOUNT"`
+	StartupScheduleRestoration string   `env:"STARTUP_SCHEDULE_RESTORATION" envDefault:"enabled"`
+	AppPackageId               string   `env:"APP_PACKAGE_ID" envDefault:"app.churros"`
+	HealthCheckPort            int      `env:"HEALTH_CHECK_PORT" envDefault:"8080"`
+	DryRunMode                 bool     `env:"DRY_RUN" envDefault:"false"`
+	DryRunExceptions           []string `env:"DRY_RUN_EXCEPTIONS"`
 }
 
 func LoadConfiguration() (Configuration, error) {
diff --git a/firebase.go b/firebase.go
index aa5199b4de50104cf97b90164b290b37da430fe1..d07cef8813a2c5d53906196bc94bc72466e6bdd9 100644
--- a/firebase.go
+++ b/firebase.go
@@ -32,10 +32,30 @@ func (msg Message) SendToFirebase(groupId string, subs []Subscription) error {
 		return fmt.Errorf("while initializing FCM client: %w", err)
 	}
 
+	if config.DryRunMode && len(config.DryRunExceptions) == 0 {
+		ll.Warn("dry run mode enabled, not sending FCM message to %d tokens", len(subs))
+		return nil
+	}
+
 	message := msg.FirebaseMessage(groupId)
-	tokens := make([]string, len(subs))
-	for i, sub := range subs {
-		tokens[i] = sub.FirebaseToken()
+	tokens := make([]string, 0, len(subs))
+	for _, sub := range subs {
+		if config.DryRunMode {
+			exempt := false
+			for _, username := range config.DryRunExceptions {
+				if username == sub.Owner.Uid {
+					exempt = true
+				}
+			}
+			if !exempt {
+				continue
+			}
+		}
+		tokens = append(tokens, sub.FirebaseToken())
+	}
+
+	if config.DryRunMode {
+		ll.Warn("dry run mode enabled, only sending FCM message to %d tokens (owned by %+v)", len(tokens), config.DryRunExceptions)
 	}
 
 	for _, tokensChunk := range chunkBy(tokens, MaxTokensPerRequest) {
@@ -44,10 +64,6 @@ func (msg Message) SendToFirebase(groupId string, subs []Subscription) error {
 				return
 			}
 			message.Tokens = tokens
-			if config.DryRunMode {
-				ll.Warn("dry run mode enabled, not sending FCM message to %d tokens", len(tokens))
-				return
-			}
 			resp, err := fcm.SendEachForMulticast(firebaseCtx, &message)
 			if err != nil {
 				ll.ErrorDisplay("while sending FCM message", err)
diff --git a/server/main.go b/server/main.go
index 76e9207445a542fbb4e7e66fe6ee24bd7b06aaa1..ee4f24da864a11c59c07204b9071d731dc587dee 100644
--- a/server/main.go
+++ b/server/main.go
@@ -28,10 +28,12 @@ func main() {
 	config, _ := notella.LoadConfiguration()
 
 	ll.Info("Server time is %s", time.Now().Format("2006-01-02 15:04:05 -07:00:00"))
-	if config.DryRunMode {
+	if config.DryRunMode && len(config.DryRunExceptions) > 0 {
+		ll.Info("Running [bold]in dry run mode, [red]except for %+v[reset] with")
+	} else if config.DryRunMode {
 		ll.Info("Running [bold]in dry run mode[reset] with")
 	} else {
-		ll.Info("Running with config ")
+		ll.Info("Running with config")
 	}
 	ll.Log("", "reset", "Schedule recovery: [bold][dim]at startup [reset][bold]%s[reset]", config.StartupScheduleRestoration)
 	ll.Log("", "reset", "contact email:     [bold]%s[reset]", config.ContactEmail)
diff --git a/typescript/configuration.ts b/typescript/configuration.ts
index b3637f45d9b17a1ab9b67f87ea6589b099cfc778..b6f44a57eebf80844ebea62ed5f985cfaefe4f59 100644
--- a/typescript/configuration.ts
+++ b/typescript/configuration.ts
@@ -3,6 +3,7 @@ export interface Configuration {
     CONTACT_EMAIL:                string;
     DATABASE_URL:                 string;
     DRY_RUN:                      boolean;
+    DRY_RUN_EXCEPTIONS:           string[];
     FIREBASE_SERVICE_ACCOUNT:     string;
     HEALTH_CHECK_PORT:            number;
     NATS_URL:                     string;
diff --git a/webpush.go b/webpush.go
index 4e2f96c3b4bbcd535829568c83ba5333c746cb25..6ae3833363573e0e6699fe14395cd1b5a903da08 100644
--- a/webpush.go
+++ b/webpush.go
@@ -77,9 +77,17 @@ func (msg Message) SendWebPush(groupId string, subs []Subscription) error {
 	for _, sub := range subs {
 		go func(wg *sync.WaitGroup, sub Subscription) {
 			if config.DryRunMode {
-				ll.Warn("dry run mode enabled, not sending webpush notification to %s", sub.Owner.Uid)
-				wg.Done()
-				return
+				exempt := false
+				for _, username := range config.DryRunExceptions {
+					if username == sub.Owner.Uid {
+						exempt = true
+					}
+				}
+				if !exempt {
+					ll.Warn("dry run mode enabled, not sending webpush notification to %s", sub.Owner.Uid)
+					wg.Done()
+					return
+				}
 			}
 			resp, err := webpush.SendNotification(jsoned, &sub.Webpush, &webpush.Options{
 				TTL:             30,