diff --git a/EnvelopeGenerator.Application/Common/Dto/UserPreferencesDto.cs b/EnvelopeGenerator.Application/Common/Dto/UserPreferencesDto.cs
new file mode 100644
index 00000000..ffc794f6
--- /dev/null
+++ b/EnvelopeGenerator.Application/Common/Dto/UserPreferencesDto.cs
@@ -0,0 +1,26 @@
+namespace EnvelopeGenerator.Application.Common.Dto;
+
+///
+/// Represents a user's UI preferences stored in the distributed cache (dbo.TBDD_CACHE).
+///
+public sealed class UserPreferencesDto
+{
+ ///
+ /// The active DevExpress theme name.
+ /// Allowed values: "blazing-berry", "fluent", "purple"
+ ///
+ public string ThemeName { get; set; } = "blazing-berry";
+
+ ///
+ /// Whether dark mode is active.
+ /// Default: false (light mode).
+ ///
+ public bool IsDarkMode { get; set; } = false;
+
+ ///
+ /// Persisted DxGrid layout states, keyed by a stable grid identifier.
+ /// Example keys: "sender.active-envelopes", "sender.completed-envelopes"
+ /// Values are opaque JSON strings produced by DxGrid.SaveLayoutAsync().
+ ///
+ public Dictionary GridLayouts { get; set; } = new();
+}
diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Services/IUserPreferencesService.cs b/EnvelopeGenerator.Application/Common/Interfaces/Services/IUserPreferencesService.cs
new file mode 100644
index 00000000..015e6670
--- /dev/null
+++ b/EnvelopeGenerator.Application/Common/Interfaces/Services/IUserPreferencesService.cs
@@ -0,0 +1,29 @@
+using EnvelopeGenerator.Application.Common.Dto;
+
+namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
+
+///
+/// Manages per-user UI preferences (theme, dark mode, grid layouts).
+/// Preferences are stored in the distributed cache (dbo.TBDD_CACHE) as JSON.
+/// Cache key schema: "signflow.user-preferences:{username}"
+///
+public interface IUserPreferencesService
+{
+ ///
+ /// Retrieves preferences for the given user.
+ /// Always returns a valid object — never throws, never returns null.
+ /// Returns defaults if no entry exists in cache.
+ ///
+ /// The sender's username (ClaimTypes.Name from JWT).
+ /// Cancellation token.
+ Task GetAsync(string username, CancellationToken ct = default);
+
+ ///
+ /// Persists the given preferences for the user in the distributed cache.
+ /// Failures are non-fatal and silently ignored.
+ ///
+ /// The sender's username (ClaimTypes.Name from JWT).
+ /// The preferences to persist.
+ /// Cancellation token.
+ Task SaveAsync(string username, UserPreferencesDto preferences, CancellationToken ct = default);
+}
diff --git a/EnvelopeGenerator.Infrastructure/DependencyInjection.cs b/EnvelopeGenerator.Infrastructure/DependencyInjection.cs
index 00cf5aba..560aa938 100644
--- a/EnvelopeGenerator.Infrastructure/DependencyInjection.cs
+++ b/EnvelopeGenerator.Infrastructure/DependencyInjection.cs
@@ -14,6 +14,7 @@ using EnvelopeGenerator.Infrastructure.Repositories;
using EnvelopeGenerator.Infrastructure.Executor;
using EnvelopeGenerator.Application.Common.Interfaces.Repositories;
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
+using EnvelopeGenerator.Application.Common.Interfaces.Services;
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
#elif NETFRAMEWORK
using System;
@@ -55,6 +56,9 @@ namespace EnvelopeGenerator.Infrastructure
services.TryAddScoped();
services.TryAddScoped();
services.TryAddScoped();
+
+ // User Preferences — distributed cache (dbo.TBDD_CACHE)
+ services.TryAddScoped();
#endif
services.AddDbRepository(opt =>
diff --git a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj
index 23ff4950..87dfe75f 100644
--- a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj
+++ b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj
@@ -26,6 +26,7 @@
+
diff --git a/EnvelopeGenerator.Infrastructure/UserPreferencesService.cs b/EnvelopeGenerator.Infrastructure/UserPreferencesService.cs
new file mode 100644
index 00000000..a8443a8c
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/UserPreferencesService.cs
@@ -0,0 +1,41 @@
+#if NET
+using System.Text.Json;
+using Microsoft.Extensions.Caching.Distributed;
+using EnvelopeGenerator.Application.Common.Dto;
+using EnvelopeGenerator.Application.Common.Interfaces.Services;
+
+namespace EnvelopeGenerator.Infrastructure;
+
+///
+/// Implements user preferences storage using the distributed cache (dbo.TBDD_CACHE).
+/// No DB schema changes required — uses the existing SQL Server distributed cache table.
+/// Cache key schema: "signflow.user-preferences:{username}"
+/// Expiration: indefinite (no TTL) — preferences persist until explicitly overwritten.
+///
+public sealed class UserPreferencesService(IDistributedCache cache) : IUserPreferencesService
+{
+ private const string KeyPrefix = "signflow.user-preferences:";
+
+ private static string BuildKey(string username) => $"{KeyPrefix}{username}";
+
+ ///
+ public async Task GetAsync(string username, CancellationToken ct = default)
+ {
+ var json = await cache.GetStringAsync(BuildKey(username), ct);
+
+ if (json is null)
+ return new UserPreferencesDto();
+
+ return JsonSerializer.Deserialize(json) ?? new UserPreferencesDto();
+ }
+
+ ///
+ public async Task SaveAsync(string username, UserPreferencesDto preferences, CancellationToken ct = default)
+ {
+ var json = JsonSerializer.Serialize(preferences);
+
+ // No expiration — preferences are stored indefinitely.
+ await cache.SetStringAsync(BuildKey(username), json, new DistributedCacheEntryOptions(), ct);
+ }
+}
+#endif
diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/UserPreferencesController.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/UserPreferencesController.cs
new file mode 100644
index 00000000..47f84b27
--- /dev/null
+++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/UserPreferencesController.cs
@@ -0,0 +1,49 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using EnvelopeGenerator.Domain.Constants;
+using EnvelopeGenerator.Server.Extensions;
+using EnvelopeGenerator.Application.Common.Dto;
+using EnvelopeGenerator.Application.Common.Interfaces.Services;
+
+namespace EnvelopeGenerator.Server.Controllers;
+
+///
+/// Manages per-user UI preferences (theme, dark mode, grid layouts).
+/// Preferences are stored in the distributed cache (dbo.TBDD_CACHE).
+///
+[ApiController]
+[Route("api/[controller]")]
+[Authorize(Policy = AuthPolicy.Sender)]
+public class UserPreferencesController(IUserPreferencesService preferencesService) : ControllerBase
+{
+ private static readonly HashSet AllowedThemes =
+ ["blazing-berry", "fluent", "purple"];
+
+ ///
+ /// Returns the current user's UI preferences.
+ /// Always returns 200 with defaults if no preferences have been saved yet.
+ ///
+ [HttpGet]
+ public async Task> Get(CancellationToken ct)
+ {
+ var username = User.GetUsername();
+ var prefs = await preferencesService.GetAsync(username, ct);
+ return Ok(prefs);
+ }
+
+ ///
+ /// Saves (full replace) the current user's UI preferences.
+ ///
+ [HttpPut]
+ public async Task> Put(
+ [FromBody] UserPreferencesDto dto,
+ CancellationToken ct)
+ {
+ if (!AllowedThemes.Contains(dto.ThemeName))
+ return BadRequest($"Invalid theme '{dto.ThemeName}'. Allowed: {string.Join(", ", AllowedThemes)}.");
+
+ var username = User.GetUsername();
+ await preferencesService.SaveAsync(username, dto, ct);
+ return Ok(dto);
+ }
+}
diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs
index 0a0769c6..c0fa7a2e 100644
--- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs
+++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs
@@ -318,6 +318,9 @@ try
.AddEnvelopeGeneratorServices(config);
#pragma warning restore CS0618
+ // User Preferences (theme, dark mode, grid layouts — stored in TBDD_CACHE)
+ // Registration handled by Infrastructure DependencyInjection (AddEnvelopeGeneratorInfrastructureServices)
+
// Business Services (Server specific)
builder.Services.AddScoped();
builder.Services.AddScoped();