Attachment Storage
Relevant source files
The following files were used as context for generating this wiki page:
The Attachment Storage system is responsible for handling, persisting, and serving email attachments. It is designed with a flexible architecture that supports both local file system storage for simple deployments and an S3-compatible object storage for scalability and production environments. The system integrates into the email processing pipeline, where it validates attachments against configured rules (size, MIME type) before saving them.
Attachments are linked to their corresponding messages in the database. The system provides a secure API endpoint for downloading attachments, ensuring that only authorized users can access the files. The lifecycle of an attachment is tied to its parent message, including a soft-delete mechanism.
Architecture
The system employs a strategy pattern for its storage backend, determined by the S3_ENABLED configuration flag. If enabled, it uses an S3-compatible service; otherwise, it defaults to the local file system.
Sources: services/api/src/services/storage.ts, blueprint.md:73-74, deployment_guide.md:49-55
Storage Strategy
The following diagram illustrates the logic for selecting the storage provider.
This diagram shows the conditional flow for saving an attachment based on the S3_ENABLED environment variable.
Sources: services/api/src/services/storage.ts:11-13, services/api/src/config.ts:60
Storage Providers
| Provider | Description | Configuration |
|---|---|---|
DiskStorageService | Stores files on the local server's disk. The base directory is configured via the STORAGE_DIR environment variable. This is the default for simple or development setups. | S3_ENABLED=false |
S3StorageService | Stores files in an S3-compatible object storage bucket. Requires credentials and endpoint configuration. Recommended for production. | S3_ENABLED=true |
Sources: services/api/src/services/storage.ts:15-76, deployment_guide.md:49-55
Data Model
Attachment metadata is stored in the Attachment table in the PostgreSQL database. Each record is linked to a Message record.
Sources: blueprint.md:149-150, services/api/prisma/migrations/0002_abuse_controls/migration.sql:7
ER Diagram
The diagram below shows the relationship between Message and Attachment entities.
Attachment Table Schema
| Column | Type | Description |
|---|---|---|
id | TEXT (UUID) | Primary key for the attachment. |
messageId | TEXT (UUID) | Foreign key linking to the Message table. |
filename | TEXT | The original filename of the attachment. |
mimeType | TEXT | The MIME type of the attachment (e.g., application/pdf). |
size | INTEGER | The size of the attachment in bytes. |
storageKey | TEXT | The path or key where the file is stored (e.g., inboxId/messageId/filename.ext). |
deletedAt | TIMESTAMP | Timestamp for soft-deleting the attachment. If NULL, the attachment is active. |
Sources: blueprint.md:150, services/api/prisma/migrations/0002_abuse_controls/migration.sql:7,20-29
Attachment Processing Pipeline
When an email is processed by the EmailWorker, its attachments go through a series of validation and persistence steps.
Sources: services/api/src/worker.ts:1-125
Sequence Diagram
This diagram shows the sequence of operations when processing attachments from an inbound email.
Sources: services/api/src/worker.ts:80-125
Key Processing Steps
- Virus Scanning: Before any other processing, all attachment buffers are scanned for viruses. If a virus is detected, the entire email processing job fails.
Sources: services/api/src/worker.ts:87-100 - Size Validation: The size of each attachment is checked against the
MAX_ATTACHMENT_BYTESconfiguration. If it exceeds the limit, the job fails.
Sources: services/api/src/worker.ts:103-105, services/web/src/pages/Legal/AcceptableUse.tsx:90 - Type Validation: The attachment's MIME type and file extension are checked against the
ALLOWED_ATTACHMENT_MIME_PREFIXESandALLOWED_ATTACHMENT_EXTENSIONSallowlists. If the type is not permitted, the job fails.
Sources: services/api/src/worker.ts:106-111 - Persistence: The attachment content is saved using the configured storage service (
DiskorS3). ThestorageKeyis constructed using the format:inboxId/messageId/filename.
Sources: services/api/src/worker.ts:112-114 - Database Record: A new record is created in the
Attachmenttable with the file's metadata and thestorageKey.
Sources: services/api/src/worker.ts:116-124
API Endpoints
The system exposes an API endpoint to download attachments securely.
Sources: services/api/src/routes/messages.ts:333-356
GET /attachments/:id/download
This endpoint streams the content of a specific attachment. It requires authentication.
| Parameter | Type | Location | Description |
|---|---|---|---|
id | string (UUID) | Path | The unique identifier of the attachment to download. |
Responses:
200 OK: The file stream is returned withContent-Disposition: attachmentand the correctContent-Typeheaders.400 Bad Request: The provided ID is not a valid UUID.403 Forbidden: The authenticated user is not authorized to access this attachment.404 Not Found: The attachment ID does not exist or the file is not found in storage.
Sources: services/api/src/routes/messages.ts:333-356, services/web/src/components/MessageDetail.tsx:162-164
Deletion
Attachments follow a soft-delete pattern. When a message is deleted via the API, all its associated attachments are also marked as deleted by setting their deletedAt timestamp. The physical files are not immediately removed from storage but can be cleaned up by a separate process.
Sources: services/api/src/routes/messages.ts:269-271, services/api/prisma/migrations/0002_abuse_controls/migration.sql:7
Configuration
Attachment behavior is controlled by several environment variables.
| Variable | Description | Default Value | Source |
|---|---|---|---|
STORAGE_DIR | The local directory for storing attachments when S3_ENABLED is false. | ./storage | services/api/src/config.ts:59 |
S3_ENABLED | If true, enables S3-compatible object storage. | false | services/api/src/config.ts:60 |
S3_BUCKET | The name of the S3 bucket. | "" | services/api/src/config.ts:61 |
S3_REGION | The AWS region for the S3 bucket. | "" | services/api/src/config.ts:62 |
S3_ENDPOINT | The S3 endpoint URL (for non-AWS S3 services like MinIO). | "" | services/api/src/config.ts:63 |
S3_ACCESS_KEY_ID | The access key for the S3 service. | "" | services/api/src/config.ts:64 |
S3_SECRET_ACCESS_KEY | The secret key for the S3 service. | "" | services/api/src/config.ts:65 |
MAX_ATTACHMENT_BYTES | Maximum allowed size for a single attachment in bytes. | 5242880 (5MB) | services/api/src/config.ts:47 |
ALLOWED_ATTACHMENT_MIME_PREFIXES | A comma-separated list of allowed MIME type prefixes (e.g., image/). | "" (all allowed) | services/api/src/config.ts:48 |
ALLOWED_ATTACHMENT_EXTENSIONS | A comma-separated list of allowed file extensions (e.g., pdf,jpg). | "" (all allowed) | services/api/src/config.ts:49 |
Sources: services/api/src/config.ts:47-49, 59-65, deployment_guide.md:49-55
Summary
The attachment storage system is a core component that provides a robust and configurable mechanism for handling email attachments. Its support for both local disk and S3 storage allows it to scale from small development instances to large-scale production deployments. By integrating validation for size and type directly into the email processing pipeline, it enhances security and system stability. The lifecycle of attachments is managed alongside their parent emails, ensuring data consistency.