Skip to content

API Layer

The web application's API layer lives in src/api/ and consists of three main modules: a generic HTTP client, TypeScript type definitions, and a comprehensive set of TanStack Query hooks. Together, they provide type-safe data fetching with automatic caching and cache invalidation.

API Client (src/api/client.ts)

The client module exports a generic apiRequest<T>() function and a convenience api object with methods for each HTTP verb.

apiRequest<T>(endpoint, options?)

A generic fetch wrapper that:

  1. Prepends the base URL (VITE_API_URL or /api by default) to the endpoint
  2. Extracts the OIDC access token from sessionStorage (falling back to localStorage)
  3. Sets Content-Type: application/json and Authorization: Bearer <token> headers
  4. Handles error responses by throwing an ApiError with the HTTP status code
  5. Returns undefined for 204 No Content responses
  6. Parses and returns JSON for all other successful responses

Safe Storage Access

The client includes a safeStorageAccess() helper that wraps storage reads in a try/catch. This handles cases where sessionStorage or localStorage is unavailable, such as when the browser is in private/incognito mode with restricted storage access.

ApiError Class

typescript
class ApiError extends Error {
  status: number    // HTTP status code (e.g., 401, 404, 500)
  message: string   // Response body text or status text
}

TanStack Query hooks receive ApiError instances in their onError callbacks, making it straightforward to handle specific HTTP errors (e.g., showing a login prompt on 401).

api Convenience Object

The api object provides shorthand methods:

typescript
api.get<T>(endpoint)              // GET request
api.post<T>(endpoint, data)       // POST with JSON body
api.put<T>(endpoint, data)        // PUT with JSON body
api.patch<T>(endpoint, data)      // PATCH with JSON body
api.delete(endpoint)              // DELETE request
api.getHeaders()                  // Returns headers including auth token
api.baseUrl                       // The resolved API base URL

All methods are generic and return Promise<T>.

Type Definitions (src/api/types.ts)

This file re-exports all TypeScript interfaces from @relate/shared/api/types. By centralizing the re-export, components and hooks throughout the web app can import types from @/api/types without needing to know about the shared package path.

typescript
// All types are re-exported from the shared package
import type { EmailDetail, Profile, Label } from '@/api/types'

See Shared Types for the complete type reference.

TanStack Query Hooks (src/api/hooks.ts)

The hooks module contains 40+ hooks organized by feature domain. Every hook follows TanStack Query conventions: queries return UseQueryResult objects (with data, isLoading, error), and mutations return UseMutationResult objects (with mutate, mutateAsync, isPending).

Query Key Conventions

Query keys are structured arrays that enable granular cache invalidation:

PatternExampleInvalidated by
['emails', page, pageSize]['emails', 1, 20]Any email mutation
['email', id]['email', 'abc-123']Mark read, delete
['emails', 'search', ...]['emails', 'search', 'invoice', ...]Email mutations
['emails', 'sent', ...]['emails', 'sent', null, 1, 20]Outbound mutations
['profile']['profile']Profile update, address changes
['smtp-credentials']['smtp-credentials']Key create/revoke
['labels']['labels']Label CRUD
['filters']['filters']Filter CRUD
['preferences']['preferences']Preference update
['outbound', 'drafts', ...]['outbound', 'drafts', 1, 20]Draft CRUD, send
['outbound', 'outbox', ...]['outbound', 'outbox', 1, 20]Send operations
['thread', threadId]['thread', 'msg-456']Email mutations

Cache Invalidation Strategy

Mutations invalidate related queries on success using queryClient.invalidateQueries(). Some mutations also use optimistic updates via queryClient.setQueryData() for instant UI feedback:

  • useMarkEmailRead -- Sets the individual email cache entry directly, then invalidates the email list
  • useUpdateProfile -- Sets the profile cache directly (no list to invalidate)
  • useUpdatePreferences -- Sets the preferences cache directly
  • useUpdateDraft -- Sets the individual draft cache, then invalidates the drafts list

Default Configuration

  • staleTime: 30 seconds for search queries (prevents refetching during rapid filter changes). Other queries use TanStack Query's default.
  • gcTime: 5 minutes for search queries (keeps cached results available for back navigation)
  • retry: 1 (TanStack Query default for queries)
  • enabled: Many hooks use conditional enabling (e.g., useEmail(id) only fetches when id is truthy)

Hooks by Feature

Email Inbox

HookTypeDescription
useEmails(page, pageSize)QueryFetch paginated inbox
useSearchEmails(filters, page, pageSize)QuerySearch with text query, date range, attachment/read filters
useEmail(id)QueryFetch single email detail
useInfiniteEmails(pageSize)Infinite QueryInfinite-scroll email loading
useThread(threadId)QueryFetch all emails in a thread
useMarkEmailRead()MutationToggle read/unread status
useDeleteEmail()MutationDelete a single email
useBulkMarkRead()MutationMark multiple emails read/unread
useBulkDelete()MutationDelete multiple emails

User Profile

HookTypeDescription
useProfile()QueryFetch user profile
useUpdateProfile()MutationUpdate display name
useAddEmailAddress()MutationAdd an additional email address
useRemoveEmailAddress()MutationRemove an additional address
useSendVerification()MutationTrigger verification email
useVerifyEmailAddress()MutationSubmit verification code

SMTP Credentials

HookTypeDescription
useSmtpCredentials()QueryFetch connection info and API keys
useCreateSmtpApiKey()MutationGenerate a new API key
useRevokeSmtpApiKey()MutationRevoke an existing API key

Labels

HookTypeDescription
useLabels()QueryFetch all labels
useCreateLabel()MutationCreate a new label
useUpdateLabel()MutationUpdate label name/color/sort
useDeleteLabel()MutationDelete a label
useAddLabelToEmail()MutationAssign a label to an email
useRemoveLabelFromEmail()MutationRemove a label from an email
useEmailsByLabel(labelId, page, pageSize)QueryFetch emails with a specific label

Filters

HookTypeDescription
useFilters()QueryFetch all filters
useCreateFilter()MutationCreate a new filter
useUpdateFilter()MutationUpdate a filter
useDeleteFilter()MutationDelete a filter
useTestFilter(id, limit)QueryDry-run a filter (manual trigger only, enabled: false)

Preferences

HookTypeDescription
usePreferences()QueryFetch user preferences
useUpdatePreferences()MutationSave preference changes

Outbound Email

HookTypeDescription
useSentEmails(fromAddress, page, pageSize)QueryFetch sent emails, optionally filtered by from-address
useSentFromAddresses()QueryFetch list of addresses the user has sent from
useDrafts(page, pageSize)QueryFetch draft list
useDraft(id)QueryFetch single draft detail
useCreateDraft()MutationCreate a new draft
useUpdateDraft()MutationUpdate an existing draft
useDeleteDraft()MutationDelete a draft
useSendEmail()MutationSend a new email directly
useSendDraft()MutationQueue a draft for sending
useReplyToEmail()MutationReply to an email
useForwardEmail()MutationForward an email
useOutbox(page, pageSize)QueryFetch outbound queue
useOutboundSent(page, pageSize)QueryFetch sent outbound emails
useOutboundEmail(id)QueryFetch outbound email detail

Released under the MIT License.