Repository Implementations
The Repositories/ directory contains 9 repository classes that implement the interfaces defined in the Core layer. All repositories are registered as scoped services (one instance per request/scope) and use AppDbContext for database access.
EmailRepository
The largest and most complex repository, EmailRepository handles all inbound email operations.
Pagination
GetByUserIdAsync returns emails for a user's inbox, ordered by ReceivedAt descending, with skip/take pagination. The query joins through EmailRecipient to find emails where the user is a recipient, and eagerly loads Recipients and Attachments.
Access Control
GetByIdWithUserAccessAsync verifies that the requesting user is either the sender (SentByUserId) or a recipient before returning the email. This is used by the POP3 and IMAP handlers to ensure users can only access their own mail.
Text Search
SearchByUserIdAsync applies EmailSearchFilters to build a dynamic query:
Queryis matched using case-insensitiveContains()(translates to SQLILIKE) acrossFromAddress,Subject,TextBody, andHtmlBody.FromDate/ToDatefilter onReceivedAt.HasAttachmentschecks the count of relatedEmailAttachmentrecords.IsReadjoins toEmailRecipientfor the user's read status.
Thread Lookup
GetByThreadIdAsync returns all emails in a thread, ordered by ReceivedAt, for a specific user. This supports the conversation view in the UI.
Bulk Operations
BulkMarkReadAsyncuses EF Core'sExecuteUpdateAsyncto setIsReadonEmailRecipientrecords in a single database round-trip.BulkDeleteAsyncusesExecuteDeleteAsyncto remove emails efficiently.
Both methods return the number of affected records.
Streaming
StreamByUserIdAsync returns an IAsyncEnumerable<Email> that yields emails one at a time without loading them all into memory. This is used for MBOX export and large data operations. Optional date range filters can be applied.
Sent Mail
GetSentByUserIdAsync queries emails where SentByUserId matches the user, providing a view of messages the user sent through the SMTP server. Additional methods support filtering by FromAddress and listing distinct sender addresses.
UserRepository
UserRepository manages user accounts with both OIDC and email-based lookups.
Key Queries
GetByOidcSubjectAsync-- Unique lookup by OIDC issuer + subject. The database has a unique index on these columns.GetByEmailWithApiKeysAsync-- Looks up a user by email address and eagerly loads their active (non-revoked) API keys. This is the primary query used by protocol authentication.GetAllEmailAddressesAsync-- Returns the primary email plus all additional verified addresses for a user.
Email Address Management
The repository provides CRUD operations for UserEmailAddress records, supporting the flow of adding, verifying, and removing additional email addresses from a user's profile.
SmtpApiKeyRepository
SmtpApiKeyRepository implements the two-phase API key lookup:
Prefix-Based Lookup
- Extract the first 12 characters of the raw API key as the prefix.
- Query the database for active (non-revoked) keys matching this prefix -- this is an indexed O(1) lookup.
- For each matching key, verify the full raw key against the BCrypt hash stored in
KeyHash. - Optionally check that the key has the required scope.
This approach avoids BCrypt verification against every key for every user, which would be prohibitively expensive.
Scope Handling
ParseScopesdeserializes the JSON array stored inSmtpApiKey.Scopes(e.g.,["smtp","pop3"]).HasScopechecks if a key's scope list includes a specific permission.
LabelRepository
Standard CRUD operations for labels, with GetByUserIdAsync returning labels ordered by SortOrder for consistent UI display.
EmailLabelRepository
Manages the many-to-many relationship between emails and labels:
GetEmailsByLabelIdAsync-- Returns paginated emails with a specific label, joining through theEmailLabeljunction table. Eagerly loads recipients and attachments.GetEmailCountByLabelIdAsync-- Count query for pagination UI.AddAsync/DeleteAsync-- Add or remove a label from an email.
EmailFilterRepository
GetByUserIdAsync-- All filters for a user, ordered by priority.GetEnabledByUserIdAsync-- Only active filters, used during incoming email processing.
UserPreferenceRepository
GetByUserIdAsync-- Fetches the user's preferences.UpsertAsync-- Uses EF Core's change tracking to either insert a new preference record or update an existing one. SetsUpdatedAton save.
PushSubscriptionRepository
GetByEndpointAsync-- Looks up a subscription by its push endpoint URL and user ID. Used to prevent duplicate subscriptions from the same browser.UpdateLastUsedAtAsync-- Updates theLastUsedAttimestamp when a notification is sent.
OutboundEmailRepository
Queue Query
GetQueuedForDeliveryAsync is the critical query used by the DeliveryQueueProcessor:
SELECT TOP(@batchSize) *
FROM OutboundEmails
WHERE Status = 'Queued'
AND (NextRetryAt IS NULL OR NextRetryAt <= @now)
ORDER BY QueuedAtThis fetches the oldest queued emails that are due for delivery (either never attempted or past their retry delay). Recipients and attachments are eagerly loaded to minimize database round-trips during delivery.
Status-Based Views
GetDraftsByUserIdAsync-- Emails with statusDraftGetOutboxByUserIdAsync-- Emails with statusQueuedorSendingGetSentByUserIdAsync-- Emails with statusSent
Each includes a corresponding count method for pagination.
Delivery Logging
AddDeliveryLogAsync appends a DeliveryLog record for each delivery attempt, creating an audit trail of MX hosts contacted, SMTP response codes, and timing information.