Skip to content

Commit e5e1195

Browse files
authored
V0.5.0 release (#335)
* adding exports to backend package, page icons update * Integrity report PDF generation * Fixed inline attachment images not displaying in the email preview by modifying `EmailPreview.svelte`. The email HTML references embedded images via `cid:` URIs (e.g., `src="cid:ii_19c6d5f8d5eee7bd6d91"`), but the component never resolved those `cid:` references to actual image data, even though `postal-mime` already parses inline attachments with their `contentId` and binary `content`. The `emailHtml` derived value now calls `resolveContentIdReferences()` before rendering, so inline/embedded images display correctly in the iframe preview. * feat: strip non-inline attachments from EML before storage Add nodemailer dependency and emlUtils helper to remove non-inline attachments from .eml buffers during ingestion. This avoids double-storing attachment data since attachments are already stored separately. * upload error handing for file based ingestion * Use Postgres for sync session management * Google workspace / MS 365 duplicate check, avoid extra API call when previous ingestion fails * OpenAPI specs for API docs * code formatting * ran duplicate check for IMAP import, optimize message listing * Version update
1 parent 20ef9a4 commit e5e1195

118 files changed

Lines changed: 18353 additions & 7097 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ ORIGIN=$APP_URL
1313
SYNC_FREQUENCY='* * * * *'
1414
# Set to 'true' to include Junk and Trash folders in the email archive. Defaults to false.
1515
ALL_INCLUSIVE_ARCHIVE=false
16+
# Number of mailbox jobs that run concurrently in the ingestion worker. Increase on servers with more RAM.
17+
INGESTION_WORKER_CONCURRENCY=5
1618

1719
# --- Docker Compose Service Configuration ---
1820
# These variables are used by docker-compose.yml to configure the services. Leave them unchanged if you use Docker services for Postgresql, Valkey (Redis) and Meilisearch. If you decide to use your own instances of these services, you can substitute them with your own connection credentials.
@@ -43,7 +45,11 @@ REDIS_USER=notdefaultuser
4345
# --- Storage Settings ---
4446
# Choose your storage backend. Valid options are 'local' or 's3'.
4547
STORAGE_TYPE=local
46-
# The maximum request body size to accept in bytes including while streaming. The body size can also be specified with a unit suffix for kilobytes (K), megabytes (M), or gigabytes (G). For example, 512K or 1M. Defaults to 512kb. Or the value of Infinity if you don't want any upload limit.
48+
# The maximum request body size the SvelteKit frontend server will accept (including file uploads via streaming).
49+
# Accepts a numeric value in bytes, or a unit suffix: K (kilobytes), M (megabytes), G (gigabytes).
50+
# Set to 'Infinity' to remove the limit entirely (recommended for archiving large PST/Mbox files).
51+
# Examples: 512K, 100M, 5G, Infinity. Defaults to 512K if not set.
52+
# For very large files (multi-GB), consider using the "Local Path" ingestion option which bypasses this limit entirely.
4753
BODY_SIZE_LIMIT=100M
4854

4955
# --- Local Storage Settings ---

docs/.vitepress/config.mts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { defineConfig } from 'vitepress';
2+
import { useSidebar } from 'vitepress-openapi';
3+
import spec from '../api/openapi.json';
24

35
export default defineConfig({
46
head: [
@@ -95,7 +97,12 @@ export default defineConfig({
9597
{ text: 'Integrity Check', link: '/api/integrity' },
9698
{ text: 'Search', link: '/api/search' },
9799
{ text: 'Storage', link: '/api/storage' },
100+
{ text: 'Upload', link: '/api/upload' },
98101
{ text: 'Jobs', link: '/api/jobs' },
102+
{ text: 'Users', link: '/api/users' },
103+
{ text: 'IAM', link: '/api/iam' },
104+
{ text: 'API Keys', link: '/api/api-keys' },
105+
{ text: 'Settings', link: '/api/settings' },
99106
],
100107
},
101108
{

docs/.vitepress/theme/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import DefaultTheme from 'vitepress/theme';
2+
import type { EnhanceAppContext } from 'vitepress';
3+
import { theme, useOpenapi } from 'vitepress-openapi/client';
4+
import 'vitepress-openapi/dist/style.css';
5+
import spec from '../../api/openapi.json';
6+
7+
export default {
8+
...DefaultTheme,
9+
enhanceApp({ app, router, siteData }: EnhanceAppContext) {
10+
// Delegate to DefaultTheme first
11+
DefaultTheme.enhanceApp?.({ app, router, siteData });
12+
13+
// Install vitepress-openapi theme: registers i18n plugin + all OA components
14+
theme.enhanceApp({ app, router, siteData });
15+
16+
// Initialize the global OpenAPI spec
17+
useOpenapi({ spec });
18+
},
19+
};

docs/api/api-keys.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
aside: false
3+
---
4+
5+
# API Keys
6+
7+
Generate and manage API keys for programmatic access to the Open Archiver API. API keys are scoped to the user that created them and carry the same permissions as that user. The raw key value is only shown once at creation time.
8+
9+
## Generate an API Key
10+
11+
<OAOperation operationId="generateApiKey" />
12+
13+
## List API Keys
14+
15+
<OAOperation operationId="getApiKeys" />
16+
17+
## Delete an API Key
18+
19+
<OAOperation operationId="deleteApiKey" />

docs/api/archived-email.md

Lines changed: 11 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,19 @@
1-
# Archived Email Service API
1+
---
2+
aside: false
3+
---
24

3-
The Archived Email Service is responsible for retrieving archived emails and their details from the database and storage.
5+
# Archived Email API
46

5-
## Endpoints
7+
Endpoints for retrieving and deleting archived emails. All endpoints require authentication and the appropriate `archive` permission.
68

7-
All endpoints in this service require authentication.
9+
## List Emails for an Ingestion Source
810

9-
### GET /api/v1/archived-emails/ingestion-source/:ingestionSourceId
11+
<OAOperation operationId="getArchivedEmails" />
1012

11-
Retrieves a paginated list of archived emails for a specific ingestion source.
13+
## Get a Single Email
1214

13-
**Access:** Authenticated
15+
<OAOperation operationId="getArchivedEmailById" />
1416

15-
#### URL Parameters
17+
## Delete an Email
1618

17-
| Parameter | Type | Description |
18-
| :------------------ | :----- | :------------------------------------------------ |
19-
| `ingestionSourceId` | string | The ID of the ingestion source to get emails for. |
20-
21-
#### Query Parameters
22-
23-
| Parameter | Type | Description | Default |
24-
| :-------- | :----- | :------------------------------ | :------ |
25-
| `page` | number | The page number for pagination. | 1 |
26-
| `limit` | number | The number of items per page. | 10 |
27-
28-
#### Responses
29-
30-
- **200 OK:** A paginated list of archived emails.
31-
32-
```json
33-
{
34-
"items": [
35-
{
36-
"id": "email-id",
37-
"subject": "Test Email",
38-
"from": "sender@example.com",
39-
"sentAt": "2023-10-27T10:00:00.000Z",
40-
"hasAttachments": true,
41-
"recipients": [{ "name": "Recipient 1", "email": "recipient1@example.com" }]
42-
}
43-
],
44-
"total": 100,
45-
"page": 1,
46-
"limit": 10
47-
}
48-
```
49-
50-
- **500 Internal Server Error:** An unexpected error occurred.
51-
52-
### GET /api/v1/archived-emails/:id
53-
54-
Retrieves a single archived email by its ID, including its raw content and attachments.
55-
56-
**Access:** Authenticated
57-
58-
#### URL Parameters
59-
60-
| Parameter | Type | Description |
61-
| :-------- | :----- | :---------------------------- |
62-
| `id` | string | The ID of the archived email. |
63-
64-
#### Responses
65-
66-
- **200 OK:** The archived email details.
67-
68-
```json
69-
{
70-
"id": "email-id",
71-
"subject": "Test Email",
72-
"from": "sender@example.com",
73-
"sentAt": "2023-10-27T10:00:00.000Z",
74-
"hasAttachments": true,
75-
"recipients": [{ "name": "Recipient 1", "email": "recipient1@example.com" }],
76-
"raw": "...",
77-
"attachments": [
78-
{
79-
"id": "attachment-id",
80-
"filename": "document.pdf",
81-
"mimeType": "application/pdf",
82-
"sizeBytes": 12345
83-
}
84-
]
85-
}
86-
```
87-
88-
- **404 Not Found:** The archived email with the specified ID was not found.
89-
- **500 Internal Server Error:** An unexpected error occurred.
90-
91-
## Service Methods
92-
93-
### `getArchivedEmails(ingestionSourceId: string, page: number, limit: number): Promise<PaginatedArchivedEmails>`
94-
95-
Retrieves a paginated list of archived emails from the database for a given ingestion source.
96-
97-
- **ingestionSourceId:** The ID of the ingestion source.
98-
- **page:** The page number for pagination.
99-
- **limit:** The number of items per page.
100-
- **Returns:** A promise that resolves to a `PaginatedArchivedEmails` object.
101-
102-
### `getArchivedEmailById(emailId: string): Promise<ArchivedEmail | null>`
103-
104-
Retrieves a single archived email by its ID, including its raw content and attachments.
105-
106-
- **emailId:** The ID of the archived email.
107-
- **Returns:** A promise that resolves to an `ArchivedEmail` object or `null` if not found.
19+
<OAOperation operationId="deleteArchivedEmail" />

docs/api/auth.md

Lines changed: 11 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,19 @@
1-
# Auth Service API
1+
---
2+
aside: false
3+
---
24

3-
The Auth Service is responsible for handling user authentication, including login and token verification.
5+
# Auth API
46

5-
## Endpoints
7+
Handles user authentication including initial setup, login, and application setup status.
68

7-
### POST /api/v1/auth/login
9+
## Setup
810

9-
Authenticates a user and returns a JWT if the credentials are valid.
11+
<OAOperation operationId="authSetup" />
1012

11-
**Access:** Public
13+
## Login
1214

13-
**Rate Limiting:** This endpoint is rate-limited to prevent brute-force attacks.
15+
<OAOperation operationId="authLogin" />
1416

15-
#### Request Body
17+
## Check Setup Status
1618

17-
| Field | Type | Description |
18-
| :--------- | :----- | :------------------------ |
19-
| `email` | string | The user's email address. |
20-
| `password` | string | The user's password. |
21-
22-
#### Responses
23-
24-
- **200 OK:** Authentication successful.
25-
26-
```json
27-
{
28-
"accessToken": "your.jwt.token",
29-
"user": {
30-
"id": "user-id",
31-
"email": "user@example.com",
32-
"role": "user"
33-
}
34-
}
35-
```
36-
37-
- **400 Bad Request:** Email or password not provided.
38-
39-
```json
40-
{
41-
"message": "Email and password are required"
42-
}
43-
```
44-
45-
- **401 Unauthorized:** Invalid credentials.
46-
47-
```json
48-
{
49-
"message": "Invalid credentials"
50-
}
51-
```
52-
53-
- **500 Internal Server Error:** An unexpected error occurred.
54-
55-
```json
56-
{
57-
"message": "An internal server error occurred"
58-
}
59-
```
60-
61-
## Service Methods
62-
63-
### `verifyPassword(password: string, hash: string): Promise<boolean>`
64-
65-
Compares a plain-text password with a hashed password to verify its correctness.
66-
67-
- **password:** The plain-text password.
68-
- **hash:** The hashed password to compare against.
69-
- **Returns:** A promise that resolves to `true` if the password is valid, otherwise `false`.
70-
71-
### `login(email: string, password: string): Promise<LoginResponse | null>`
72-
73-
Handles the user login process. It finds the user by email, verifies the password, and generates a JWT upon successful authentication.
74-
75-
- **email:** The user's email.
76-
- **password:** The user's password.
77-
- **Returns:** A promise that resolves to a `LoginResponse` object containing the `accessToken` and `user` details, or `null` if authentication fails.
78-
79-
### `verifyToken(token: string): Promise<AuthTokenPayload | null>`
80-
81-
Verifies the authenticity and expiration of a JWT.
82-
83-
- **token:** The JWT string to verify.
84-
- **Returns:** A promise that resolves to the token's `AuthTokenPayload` if valid, otherwise `null`.
19+
<OAOperation operationId="authStatus" />

docs/api/authentication.md

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1+
---
2+
aside: false
3+
---
4+
15
# API Authentication
26

3-
To access protected API endpoints, you need to include a user-generated API key in the `X-API-KEY` header of your requests.
7+
The API supports two authentication methods. Use whichever fits your use case.
48

5-
## 1. Creating an API Key
9+
## Method 1: JWT (User Login)
610

7-
You can create, manage, and view your API keys through the application's user interface.
11+
Obtain a short-lived JWT by calling `POST /v1/auth/login` with your email and password, then pass it as a Bearer token in the `Authorization` header.
812

9-
1. Navigate to **Settings > API Keys** in the dashboard.
10-
2. Click the **"Generate API Key"** button.
11-
3. Provide a descriptive name for your key and select an expiration period.
12-
4. The new API key will be displayed. **Copy this key immediately and store it in a secure location. You will not be able to see it again.**
13+
**Example:**
1314

14-
## 2. Making Authenticated Requests
15+
```http
16+
GET /api/v1/dashboard/stats
17+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
18+
```
1519

16-
Once you have your API key, you must include it in the `X-API-KEY` header of all subsequent requests to protected API endpoints.
20+
## Method 2: API Key
21+
22+
Long-lived API keys are suited for automated scripts and integrations. Create one in **Settings > API Keys**, then pass it in the `X-API-KEY` header.
1723

1824
**Example:**
1925

@@ -22,4 +28,13 @@ GET /api/v1/dashboard/stats
2228
X-API-KEY: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
2329
```
2430

25-
If the API key is missing, expired, or invalid, the API will respond with a `401 Unauthorized` status code.
31+
### Creating an API Key
32+
33+
1. Navigate to **Settings > API Keys** in the dashboard.
34+
2. Click **"Generate API Key"**.
35+
3. Provide a descriptive name and select an expiration period (max 2 years).
36+
4. Copy the key immediately — it will not be shown again.
37+
38+
---
39+
40+
If the token or API key is missing, expired, or invalid, the API responds with `401 Unauthorized`.

0 commit comments

Comments
 (0)