export interface EmailHeader { name: string; value: string; } export interface Email { id: string; threadId: string; mailboxIds: Record; keywords: Record; size: number; receivedAt: string; from?: EmailAddress[]; to?: EmailAddress[]; cc?: EmailAddress[]; bcc?: EmailAddress[]; replyTo?: EmailAddress[]; subject?: string; sentAt?: string; preview?: string; textBody?: EmailBodyPart[]; htmlBody?: EmailBodyPart[]; bodyValues?: Record; attachments?: Attachment[]; hasAttachment: boolean; // Extended header information messageId?: string; inReplyTo?: string[]; references?: string[]; headers?: Record; // Security headers parsed authenticationResults?: AuthenticationResults; spamScore?: number; spamStatus?: string; spamLLM?: { verdict: string; explanation: string; }; // S/MIME support blobId?: string; bodyStructure?: EmailBodyPart; // Unified mailbox support - set when displaying emails from multiple accounts accountId?: string; accountLabel?: string; } export interface AuthenticationResults { spf?: { result: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none' | 'temperror' | 'permerror'; domain?: string; ip?: string; }; dkim?: { result: 'pass' | 'fail' | 'policy' | 'neutral' | 'temperror' | 'permerror'; domain?: string; selector?: string; }; dmarc?: { result: 'pass' | 'fail' | 'none'; policy?: 'reject' | 'quarantine' | 'none'; domain?: string; }; iprev?: { result: 'pass' | 'fail'; ip?: string; }; } export interface EmailBodyValue { value: string; isEncodingProblem?: boolean; isTruncated?: boolean; } export interface EmailAddress { name?: string; email: string; } export interface EmailBodyPart { partId: string; blobId: string; size: number; name?: string; type: string; charset?: string; disposition?: string; cid?: string; language?: string[]; location?: string; subParts?: EmailBodyPart[]; } export interface Attachment { partId: string; blobId: string; size: number; name?: string; type: string; charset?: string; cid?: string; disposition?: string; } export interface Mailbox { id: string; originalId?: string; // Original JMAP ID (for shared mailboxes) name: string; parentId?: string; role?: string; sortOrder: number; totalEmails: number; unreadEmails: number; totalThreads: number; unreadThreads: number; myRights: { mayReadItems: boolean; mayAddItems: boolean; mayRemoveItems: boolean; maySetSeen: boolean; maySetKeywords: boolean; mayCreateChild: boolean; mayRename: boolean; mayDelete: boolean; maySubmit: boolean; }; isSubscribed: boolean; // Shared folder support accountId?: string; accountName?: string; isShared?: boolean; } export interface Thread { id: string; emailIds: string[]; } // Thread grouping for UI display export interface ThreadGroup { threadId: string; emails: Email[]; // Emails in this thread (sorted by receivedAt desc) latestEmail: Email; // Most recent email participantNames: string[];// Unique participant names hasUnread: boolean; // Any unread emails in thread hasStarred: boolean; // Any starred emails in thread hasAttachment: boolean; // Any email has attachment hasAnswered: boolean; // Any email has been replied to hasForwarded: boolean; // Any email has been forwarded emailCount: number; // Total emails in thread } export interface Identity { id: string; name: string; email: string; replyTo?: EmailAddress[]; bcc?: EmailAddress[]; textSignature?: string; htmlSignature?: string; mayDelete: boolean; // See `Calendar.localAccountId` - set when the Pro shell aggregates // identities from multiple connected accounts so we can route sends // back through the owning JMAP client. `accountName` is the // user-facing label for the dropdown's optgroup. localAccountId?: string; accountName?: string; } // RFC 9553 JSContact / RFC 9610 JMAP for Contacts export interface ContactCard { id: string; originalId?: string; uid?: string; addressBookIds: Record; kind?: 'individual' | 'group' | 'org' | 'location' | 'device' | 'application'; accountId?: string; accountName?: string; isShared?: boolean; // Local account-store ID - set when the Pro shell aggregates contacts // from multiple connected accounts. See `Calendar.localAccountId`. localAccountId?: string; language?: string; name?: ContactName; nicknames?: Record; emails?: Record; phones?: Record; onlineServices?: Record; preferredLanguages?: Record; organizations?: Record; titles?: Record; addresses?: Record; anniversaries?: Record; personalInfo?: Record; notes?: Record; media?: Record; cryptoKeys?: Record; directories?: Record; links?: Record; relatedTo?: Record; keywords?: Record; members?: Record; speakToAs?: { grammaticalGender?: string; pronouns?: Record }>; }; calendarUri?: string; schedulingUri?: string; freeBusyUri?: string; source?: string; prodId?: string; created?: string; updated?: string; } export interface ContactName { components?: NameComponent[]; isOrdered?: boolean; full?: string; defaultSeparator?: string; } export interface NameComponent { kind: 'given' | 'surname' | 'prefix' | 'suffix' | 'additional' | 'separator' | 'credential' | 'title' | 'middle' | 'given2' | 'surname2' | 'generation'; value: string; } export interface ContactEmail { address: string; contexts?: Record; label?: string; pref?: number; } export interface ContactPhone { number: string; contexts?: Record; features?: Record; label?: string; pref?: number; } export interface ContactOnlineService { service?: string; uri: string; user?: string; contexts?: Record; label?: string; pref?: number; } export interface ContactLanguagePref { language: string; contexts?: Record; pref?: number; } export interface ContactOrganization { name?: string; units?: Array<{ name: string }>; sortAs?: string; } export interface ContactTitle { name: string; kind?: 'title' | 'role'; organizationId?: string; } // RFC 9553 AddressComponent export interface AddressComponent { kind: 'room' | 'apartment' | 'floor' | 'building' | 'number' | 'name' | 'block' | 'subDistrict' | 'district' | 'locality' | 'region' | 'postcode' | 'country' | 'direction' | 'landmark' | 'postOfficeBox' | 'separator' | string; value: string; phonetic?: string; } export interface ContactAddress { // RFC 9553 format components?: AddressComponent[]; full?: string; isOrdered?: boolean; defaultSeparator?: string; // Legacy flat fields (from vCard import) street?: string; locality?: string; region?: string; postcode?: string; country?: string; countryCode?: string; fullAddress?: string; coordinates?: string; timeZone?: string; contexts?: Record; label?: string; pref?: number; } export interface ContactNickname { name: string; contexts?: Record; } export interface ContactNote { note: string; created?: string; author?: { name?: string; uri?: string }; } export interface ContactMedia { kind: 'photo' | 'sound' | 'logo'; uri: string; mediaType?: string; } // RFC 9553 PartialDate export interface PartialDate { '@type'?: 'PartialDate'; year?: number; month?: number; day?: number; calendarScale?: string; } // RFC 9553 Timestamp export interface Timestamp { '@type': 'Timestamp'; utc: string; } export type AnniversaryDate = string | PartialDate | Timestamp; export interface ContactAnniversary { '@type'?: 'Anniversary'; kind: 'birth' | 'death' | 'wedding' | 'other'; date: AnniversaryDate; place?: ContactAddress; } export interface ContactPersonalInfo { kind: 'expertise' | 'hobby' | 'interest' | 'other'; value: string; level?: 'high' | 'medium' | 'low'; } export interface ContactCryptoKey { uri: string; mediaType?: string; contexts?: Record; } export interface ContactDirectory { uri: string; kind?: 'directory' | 'entry'; mediaType?: string; } export interface ContactLink { uri: string; kind?: 'contact' | 'generic'; mediaType?: string; contexts?: Record; label?: string; pref?: number; } export interface ContactRelation { relation?: Record; } export interface AddressBook { id: string; originalId?: string; name: string; description?: string | null; sortOrder?: number; isDefault?: boolean; isSubscribed?: boolean; myRights?: AddressBookRights; shareWith?: Record | null; accountId?: string; accountName?: string; isShared?: boolean; // See `Calendar.localAccountId` - same purpose for address books. localAccountId?: string; } export interface AddressBookRights { mayRead: boolean; mayWrite: boolean; mayShare?: boolean; mayDelete: boolean; } // JMAP Principals (RFC 9670) export interface Principal { id: string; type: 'individual' | 'group' | 'resource' | 'location' | 'other'; name: string; description?: string | null; email?: string | null; timeZone?: string | null; capabilities?: Record; accountId?: string; } export interface VacationResponse { id: string; isEnabled: boolean; fromDate: string | null; toDate: string | null; subject: string; textBody: string; htmlBody: string | null; } export interface EmailSubmission { id: string; identityId: string; emailId: string; threadId?: string; envelope: { mailFrom: EmailAddress; rcptTo: EmailAddress[]; }; sendAt?: string; undoStatus: "pending" | "final" | "canceled"; deliveryStatus?: Record; dsnBlobIds?: string[]; mdnBlobIds?: string[]; } export interface DeliveryStatus { smtpReply: string; delivered: "queued" | "yes" | "no" | "unknown"; displayed: "unknown" | "yes"; } // JMAP Calendar Types (RFC 8984 JSCalendar + RFC 9553 JMAP Calendars) export interface Calendar { id: string; originalId?: string; name: string; description: string | null; color: string | null; sortOrder: number; isSubscribed: boolean; isVisible: boolean; isDefault: boolean; includeInAvailability: 'all' | 'attending' | 'none'; defaultAlertsWithTime: Record | null; defaultAlertsWithoutTime: Record | null; timeZone: string | null; shareWith: Record | null; myRights: CalendarRights; accountId?: string; accountName?: string; isShared?: boolean; // Local account-store ID (per JMAP server connection). Populated when the // Pro shell aggregates calendars from multiple connected accounts so we // can route mutations to the right client. Distinct from `accountId` // which is the JMAP server's own account UUID. localAccountId?: string; } export interface CalendarRights { mayReadFreeBusy: boolean; mayReadItems: boolean; mayWriteAll: boolean; mayWriteOwn: boolean; mayUpdatePrivate: boolean; mayRSVP: boolean; mayShare: boolean; mayDelete: boolean; } export interface CalendarEvent { id: string; originalId?: string; calendarIds: Record; originalCalendarIds?: Record; accountId?: string; accountName?: string; isShared?: boolean; // See `Calendar.localAccountId` - same purpose for events. localAccountId?: string; isDraft: boolean; isOrigin: boolean; utcStart: string | null; utcEnd: string | null; '@type': 'Event'; uid: string; title: string; description: string; descriptionContentType: string; created: string | null; updated: string; sequence: number; start: string; duration: string; timeZone: string | null; showWithoutTime: boolean; status: 'tentative' | 'confirmed' | 'cancelled'; freeBusyStatus: 'free' | 'busy'; privacy: 'public' | 'private' | 'secret'; color: string | null; keywords: Record | null; categories: Record | null; locale: string | null; replyTo: Record | null; organizerCalendarAddress: string | null; participants: Record | null; mayInviteSelf: boolean; mayInviteOthers: boolean; hideAttendees: boolean; recurrenceId: string | null; recurrenceIdTimeZone: string | null; recurrenceRules: CalendarRecurrenceRule[] | null; recurrenceOverrides: Record> | null; excludedRecurrenceRules: CalendarRecurrenceRule[] | null; useDefaultAlerts: boolean; alerts: Record | null; locations: Record | null; virtualLocations: Record | null; links: Record | null; relatedTo: Record | null; } export interface CalendarParticipant { '@type': 'Participant'; name: string; email: string; calendarAddress: string | null; description: string | null; sendTo: Record | null; kind: 'individual' | 'group' | 'location' | 'resource'; roles: Record; participationStatus: 'accepted' | 'declined' | 'tentative' | 'delegated' | 'needs-action'; participationComment: string | null; expectReply: boolean; scheduleAgent: 'server' | 'client' | 'none'; scheduleForceSend: boolean; scheduleId: string | null; scheduleSequence: number; scheduleStatus: string[] | null; scheduleUpdated: string | null; invitedBy: string | null; delegatedTo: Record | null; delegatedFrom: Record | null; memberOf: Record | null; locationId: string | null; language: string | null; links: Record | null; } export interface CalendarRecurrenceRule { '@type': 'RecurrenceRule'; frequency: 'yearly' | 'monthly' | 'weekly' | 'daily' | 'hourly' | 'minutely' | 'secondly'; interval: number; rscale: string; skip: 'omit' | 'backward' | 'forward'; firstDayOfWeek: 'mo' | 'tu' | 'we' | 'th' | 'fr' | 'sa' | 'su'; byDay: CalendarNDay[] | null; byMonthDay: number[] | null; byMonth: string[] | null; byYearDay: number[] | null; byWeekNo: number[] | null; byHour: number[] | null; byMinute: number[] | null; bySecond: number[] | null; bySetPosition: number[] | null; count: number | null; until: string | null; } export interface CalendarNDay { day: string; nthOfPeriod?: number; } export interface CalendarEventAlert { '@type': 'Alert'; trigger: CalendarOffsetTrigger | CalendarAbsoluteTrigger; action: 'display' | 'email'; acknowledged: string | null; relatedTo: Record | null; } export interface CalendarOffsetTrigger { '@type': 'OffsetTrigger'; offset: string; relativeTo: 'start' | 'end'; } export interface CalendarAbsoluteTrigger { '@type': 'AbsoluteTrigger'; when: string; } export interface CalendarLocation { '@type': 'Location'; name: string; description: string | null; locationTypes: Record | null; coordinates: string | null; timeZone: string | null; links: Record | null; relativeTo: 'start' | 'end' | null; } export interface CalendarVirtualLocation { '@type': 'VirtualLocation'; name: string | null; description: string | null; uri: string; features: Record | null; } export interface CalendarLink { '@type': 'Link'; href: string; cid: string | null; contentType: string | null; size: number | null; rel: string | null; display: string | null; title: string | null; } export interface CalendarRelation { '@type': 'Relation'; relation: Record | null; } export interface CalendarTask { id: string; calendarIds: Record; '@type': 'Task'; uid: string; title: string; description: string; due: string | null; start: string | null; duration: string | null; timeZone: string | null; showWithoutTime: boolean; progress: 'needs-action' | 'in-process' | 'completed' | 'cancelled'; progressUpdated: string | null; priority: number; privacy: 'public' | 'private' | 'secret'; keywords: Record | null; categories: Record | null; color: string | null; created: string | null; updated: string; recurrenceRules: CalendarRecurrenceRule[] | null; alerts: Record | null; relatedTo: Record | null; } export interface CalendarParticipantIdentity { id: string; name: string; scheduleId: string; sendTo: Record; isDefault: boolean; } export interface CalendarEventNotification { id: string; created: string; changedBy: { name: string; email: string; principalId: string | null; scheduleId: string | null; }; comment: string | null; type: 'created' | 'updated' | 'destroyed'; calendarEventId: string; isDraft: boolean; event?: CalendarEvent; eventPatch?: Record; } export interface CalendarEventFilter { inCalendars?: string[]; after?: string; before?: string; text?: string; title?: string; description?: string; location?: string; owner?: string; attendee?: string; participationStatus?: string; uid?: string; types?: string[]; } // JMAP Push Notification Types (RFC 8620 Section 7) export interface StateChange { '@type': 'StateChange'; changed: { [accountId: string]: { Email?: string; Mailbox?: string; Thread?: string; EmailDelivery?: string; EmailSubmission?: string; Identity?: string; ContactCard?: string; AddressBook?: string; Calendar?: string; CalendarEvent?: string; SieveScript?: string; }; }; } export interface PushSubscription { id: string; deviceClientId: string; url: string; keys: { p256dh: string; auth: string; } | null; expires: string | null; types: string[] | null; } // For tracking last known states export interface AccountStates { [accountId: string]: { Email?: string; Mailbox?: string; Thread?: string; }; } // JMAP FileNode types (draft-ietf-jmap-filenode / Stalwart implementation) export interface FileNode { id: string; parentId: string | null; name: string; type: string; // "d" for directory, MIME type for files blobId: string | null; size: number; created: string; updated: string; } export interface FileNodeFilter { parentId?: string | null; name?: string; type?: string; } // Unified mailbox virtual IDs and types export const UNIFIED_INBOX = '__unified_inbox__'; export const UNIFIED_SENT = '__unified_sent__'; export const UNIFIED_DRAFTS = '__unified_drafts__'; export const UNIFIED_TRASH = '__unified_trash__'; export const UNIFIED_ARCHIVE = '__unified_archive__'; export const UNIFIED_JUNK = '__unified_junk__'; export type UnifiedMailboxRole = 'inbox' | 'sent' | 'drafts' | 'trash' | 'archive' | 'junk'; export const UNIFIED_MAILBOX_IDS: Record = { inbox: UNIFIED_INBOX, sent: UNIFIED_SENT, drafts: UNIFIED_DRAFTS, trash: UNIFIED_TRASH, archive: UNIFIED_ARCHIVE, junk: UNIFIED_JUNK, }; export const UNIFIED_ROLE_BY_ID: Record = Object.fromEntries( Object.entries(UNIFIED_MAILBOX_IDS).map(([role, id]) => [id, role as UnifiedMailboxRole]) ) as Record; export function isUnifiedMailboxId(id: string): boolean { return id in UNIFIED_ROLE_BY_ID; }