Seamail is a full-stack email service designed around the @seamail.com domain. It provides secure, efficient, and user-friendly functionalities for managing emails through an intuitive interface, backed by JWT-based authentication with automatic token refresh, Redis for token storage and inbox caching, and a fully versioned REST API.
It is officially deployed on Amazon Web Services (AWS) using a custom domain.
- User Registration & Sign-in: Secure registration and login with server-side BCrypt password hashing.
- HTTPS Encryption & Deployment: Deployed on AWS with a valid SSL certificate issued by Let's Encrypt, ensuring all data is securely encrypted and protected from interception.
- OAuth2 Authentication: Allows users to optionally sign in with their Discord account via a custom server-side OAuth2 callback.
- JWT Authentication: Stateless Bearer token authentication with 30-minute access tokens and automatic silent refresh via rotating refresh tokens.
- Automatic Token Refresh: A centralised axios interceptor detects expired tokens, silently exchanges the refresh token for a new pair, and retries the original request without interrupting the user.
- Multi-language Support: Enhances accessibility by making the platform available in English, German, and French via i18next.
- Email Management: Allows users to view and manage inbox, outbox, and trashbox for efficient email organisation.
- Email Actions: Send, move to trash, and permanently delete emails directly from any mailbox.
- Email Sorting & Filtering: Sort emails by priority or date, and filter them by subject or sender, all via a single unified query endpoint.
- Password Management: Allows users to securely change their password to maintain account security.
- Account Management: Allows users to permanently delete their accounts and change their default profile picture (PNG or JPEG, max 5 MB).
- Redis Caching: Caches inbox emails per user using Redis Cloud with a 15-minute TTL to reduce database load and improve response times. Cache is automatically invalidated when emails are received or moved to trash. Redis also stores refresh tokens with a 7-day TTL and automatic rotation on every use.
βββββββββββββββββββββββββββββββββββββββββββ
β React (Vite) β
β TailwindCSS Β· React Router Β· i18next β
β apiClient.js Β· parseApiError.js β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β HTTPS / REST (JSON)
βββββββββββββββββββΌββββββββββββββββββββββββ
β Spring Boot /api/v1 β
β Controller β Service β Repository β
β JwtFilter Β· GlobalExceptionHandler β
β Spring Boot Actuator (health probes) β
ββββββββββ¬ββββββββββββββββββββ¬βββββββββββββ
β β
ββββββββββΌβββββββ ββββββββββΌββββββββββββββββββββ
β MySQL β β Redis Cloud β
β Spring Data β β Inbox cache TTL 15 min β
β JPA β β Refresh tokens TTL 7 days β
βββββββββββββββββ ββββββββββββββββββββββββββββββ
Key design decisions:
- Versioned REST API: all endpoints live under
/api/v1, making future versioning straightforward. - DTO layer: request/response objects are fully decoupled from JPA entities; no entity is ever serialised directly over the wire.
- Centralised exception handling: a single
@RestControllerAdvicemaps every custom domain exception to a consistent JSON error shape (ErrorResponse/ValidationErrorResponse) with HTTP status, machine-readable error code, message, path, and timestamp. - Centralised HTTP client: a single
apiClient.jsaxios instance handles Bearer header injection, 401 detection, silent token refresh with request queuing, and redirect to sign-in on refresh failure. - Refresh token rotation: every call to
POST /api/v1/auth/refreshdeletes the old Redis refresh token key and issues a new access + refresh pair, limiting the window of token reuse. - Focused caching: only the inbox (the highest-traffic read) is cached in Redis with a 15-minute TTL; cache is evicted automatically on send or trash actions.
- Stateless security: a custom
JwtFilter(extendingOncePerRequestFilter) validates Bearer tokens and populates theSecurityContextwith the user's email and authorities before every protected request; no session state is held server-side. - Service abstractions:
UserServiceandEmailServiceimplementIUserServiceandIEmailServiceinterfaces, keeping controllers thin and the service layer fully testable in isolation. - Health monitoring: Spring Boot Actuator exposes
/actuator/healthwith liveness and readiness probe groups (including DB and Redis checks) for AWS load-balancer integration. - SSL Termination: Nginx handles HTTPS requests using Let's Encrypt certificates, ensuring all traffic between the client and the server is encrypted.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, TailwindCSS, React Router v7, Axios |
| Internationalisation | i18next / react-i18next (EN, DE, FR) |
| Backend | Spring Boot 3.2.3, Java 17, Maven |
| Security | Spring Security, JWT HS256 (JJWT 0.11.5), BCrypt, Discord OAuth2 |
| Database | MySQL 8.0 |
| Caching & Token Store | Redis Cloud via Spring Cache + Spring Data Redis (@Cacheable / @CacheEvict / StringRedisTemplate) |
| Monitoring | Spring Boot Actuator (health, liveness, readiness) |
| Infrastructure | Docker, Nginx |
| Backend Testing | JUnit 5, @WebMvcTest, @DataJpaTest, Mockito, AssertJ, H2 |
| Frontend Testing | Vitest, React Testing Library, MSW (Mock Service Worker), axios-mock-adapter |
All protected endpoints require Authorization: Bearer <accessToken>.
The token is a 30-minute HS256 JWT. Use POST /api/v1/auth/refresh to renew it silently.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /sign-in |
Public | Authenticates a user; returns { accessToken, refreshToken } |
| POST | /sign-up |
Public | Registers a new @seamail.com account; returns { accessToken, refreshToken } (201) |
| POST | /auth/refresh |
Public | Exchanges a refresh token for a new access + refresh pair (rotation) |
| GET | /auth/discord |
Public | Discord OAuth2 callback; 302 redirects to frontend /home with tokens in query params |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /inbox |
Bearer | Returns all active inbox emails for the authenticated user |
| GET | /outbox |
Bearer | Returns all sent emails for the authenticated user |
| GET | /trashbox |
Bearer | Returns all trashed emails for the authenticated user |
| GET | /emails |
Bearer | Query endpoint; supports ?sort=priority|date, ?filterBy=subject|sender&filterValue= |
| POST | /send-email |
Bearer | Sends a new email to a specified recipient (201 empty body) |
| POST | /move-to-trash |
Bearer | Moves a specified email to trash (204) |
| DELETE | /delete-email |
Bearer | Permanently deletes a specified email (204) |
| Method | Path | Auth | Description |
|---|---|---|---|
| PUT | /change-password |
Bearer | Updates the authenticated user's password |
| PUT | /update-language |
Bearer | Updates the authenticated user's language preference |
| DELETE | /delete-account |
Bearer | Permanently deletes the authenticated user's account |
| POST | /{email}/profile-picture |
Bearer | Uploads a PNG or JPEG profile picture (raw bytes, max 5 MB) |
| GET | /{email}/profile-picture |
Bearer | Retrieves the profile picture as image/jpeg |
| Method | Path | Description |
|---|---|---|
| GET | /actuator/health |
Overall health status |
| GET | /actuator/health/liveness |
Liveness probe (AWS ALB) |
| GET | /actuator/health/readiness |
Readiness probe; includes DB and Redis checks |
Every error from the backend has one of two consistent JSON shapes:
Domain / server errors (ErrorResponse):
{
"status": 404,
"error": "USER_NOT_FOUND",
"message": "User not found",
"path": "/api/v1/sign-in",
"timestamp": "2026-05-23T14:30:00"
}Validation errors (ValidationErrorResponse):
{
"status": 400,
"error": "VALIDATION_FAILED",
"message": "Request validation failed.",
"path": "/api/v1/sign-up",
"timestamp": "2026-05-23T14:30:00",
"fieldErrors": ["email: must not be blank", "password: size must be between 8 and 255"]
}The frontend parseApiError.js utility normalises both shapes (and the Spring Security {"message":"Unauthorized"} shape) into a single consistent object for UI error display.
There are two ways to run Seamail:
| Method | Best For |
|---|---|
| π³ Docker | Quick setup, no local dependencies needed |
| π οΈ Manual | Local development with IntelliJ |
Seamail uses separate environment files depending on the context. Each file lives in the root Email-Service directory and is never committed to version control.
| File | Used By | When |
|---|---|---|
.env |
IDE / npm run dev |
Local development without Docker |
.env.docker |
Docker Compose | Local Docker |
.env.production |
Docker Compose | AWS production |
The
.envfiles insidefrontend-email-service/andbackendemailservice/are only read during IDE development. Docker Compose always reads from the root directory env file.
Docker runs the entire stack (MySQL + Spring Boot + React/Nginx) with a single command. No need to install Java, Node.js, or MySQL locally.
- Docker Desktop installed and running
1. Clone the repository
git clone https://github.com/MohamedMetwalli5/Email-Service.git
cd Email-Service2. Create the .env.docker file
Create a .env.docker file in the root Email-Service directory. Use .env.docker.example as a template. Then fill in your values.
Discord credentials can be obtained from the Discord Developer Portal.
Redis credentials can be obtained from Redis Cloud (free tier available).
Make sure
http://localhost:8081/api/v1/auth/discordis added as a redirect URI in your Discord Developer Portal under OAuth2 β Redirects. Set the same value asDISCORD_REDIRECT_URIin your env file andVITE_DISCORD_REDIRECT_URIfor the frontend.
3. Follow the rules in the nginx.conf file in the frontend-email-service directory
4. Build and run
docker compose --env-file .env.docker up --build5. Access the app
| Service | URL |
|---|---|
| Frontend | http://localhost |
| Backend API | http://localhost:8081 |
6. Subsequent runs (after the first build)
docker compose --env-file .env.docker up# Stop containers (data is preserved)
docker compose --env-file .env.docker down
# Stop containers and delete all data
docker compose --env-file .env.docker down -v
# View logs
docker compose logs -f
# Rebuild a specific service
docker compose --env-file .env.docker up --build backendNote: MySQL data is stored in a Docker volume and persists across restarts. It is only deleted when you run
docker compose down -v.
- Java 17+
- Maven 3.9+
- MySQL 8
- Node.js 20
- Redis Cloud account
Run the Tables.sql script in the "SQL Scripts" folder to set up your database tables.
- Create a free account on Redis Cloud
- Create a new database and note down your connection details (host, port, username, and password)
1. Navigate to the backend directory
cd backendemailservice2. Create .env
Create a .env file in the root backendemailservice directory. Use .env.example as a template. Then fill in your values.
Note:
DB_USER=roothere (local MySQL), vsDB_USER=seamail_userin Docker.
3. Configure IntelliJ run configuration
Install the EnvFile plugin in IntelliJ, then in your run configuration:
- EnvFile tab β enable and point to
backendemailservice/.env - Active profiles β set to
local
This activates application-local.properties which connects to your local MySQL instead of the Docker db host.
4. Run the backend
Either run directly from IntelliJ, or:
mvn spring-boot:run -Dspring-boot.run.profiles=localThe backend will start on http://localhost:8081
1. Navigate to the frontend directory
cd frontend-email-service2. Install dependencies
npm install3. Create .env
Create a .env file in the root frontend-email-service directory. Use .env.example as a template. Then fill in your values.
Set
VITE_DISCORD_REDIRECT_URI=http://localhost:8081/api/v1/auth/discordand make sure the same value is registered in the Discord Developer Portal.
4. Follow the rules in the nginx.conf file in the frontend-email-service directory
5. Start the frontend
npm run devThe frontend will start on http://localhost:8080
Tests use JUnit 5, Spring @WebMvcTest, @DataJpaTest, Mockito, and AssertJ. The test profile uses an in-memory H2 database (ddl-auto=create-drop) and simple in-memory cache instead of Redis, so no external services are needed to run the suite.
cd backendemailservice
mvn test| Layer | Tests |
|---|---|
| Services | UserServiceTest: registration, credential validation, domain enforcement |
EmailServiceTest: send, sort, filter, trash, delete logic |
|
| Repositories | UserRepositoryTest: custom query methods |
EmailRepositoryTest: moveToTrashBox bulk UPDATE, inbox/outbox/trash queries |
|
| Controllers | AccessControllerTest: sign-in / sign-up, validation, error shapes |
EmailsControllerTest: authorised and unauthorised email endpoints |
|
UsersControllerTest: account management and error paths |
|
| Integration | FullFlowIntegrationTest: end-to-end sign-up β send β inbox β trash β delete |
Tests use Vitest, React Testing Library, MSW (Mock Service Worker), and axios-mock-adapter. MSW intercepts all HTTP calls at the network level; no real requests are made.
cd frontend-email-service
npm test| Test file | What it covers |
|---|---|
parseApiError.test.js |
All 4 error shapes: ErrorResponse, ValidationErrorResponse, Spring Security 401, network error |
apiClient.test.js |
Bearer header injection, 401 β refresh β retry flow, redirect on refresh failure |
AppContext.test.jsx |
refreshToken storage, clearSession, sharedEmailToFullyView serialisation |
SignInPage.test.jsx |
Plain password sent, accessToken + refreshToken stored, error display |
SignUpPage.test.jsx |
Domain validation, conflict error, fieldErrors display |
SigninWithDiscord.test.jsx |
Redirect URL constructed with correct VITE_DISCORD_REDIRECT_URI |
HomePage.test.jsx |
Discord callback reads and stores refreshToken, strips query params |
EmailsSnippetView.test.jsx |
GET /emails?sort= and GET /emails?filterBy=, emailID list keys |
NewMessageComposer.test.jsx |
Send form, fieldErrors on validation failure |
SettingsMainContent.test.jsx |
Plain password on change, JPEG upload accepted |
Sidebar.test.jsx |
clearSession clears context and localStorage on sign-out |
Navbar.test.jsx |
Profile picture blob uses response Content-Type |
EmailFullView.test.jsx |
emailID field used throughout, move-to-trash and delete calls |
Email-Service/
βββ backendemailservice/ # Spring Boot API
β βββ pom.xml # Maven deps, Java 17, Boot 3.2.3
β βββ Dockerfile # Multi-stage build, exposes 8081
β βββ .env.example # Backend env template
β βββ src/main/java/.../
β βββ BackendemailserviceApplication.java # Entry point
β βββ config/ # SecurityConfig, CorsConfig, RedisConfig,
β β # DiscordOAuthProperties, CachingConfig
β βββ controller/ # AccessController, EmailsController,
β β # UsersController, OAuth2Controller
β βββ dto/ # Request / response records and beans
β βββ entity/ # User, Email (JPA entities)
β βββ exception/ # ApplicationException, ErrorResponse,
β β # ValidationErrorResponse, GlobalExceptionHandler
β βββ filter/ # JwtFilter (OncePerRequestFilter)
β βββ health/ # CustomRedisHealthIndicator
β βββ repository/ # UserRepository, EmailRepository
β βββ service/ # IUserService, IEmailService (interfaces)
β β # UserService, EmailService (implementations)
β β # CustomUserDetailsService
β βββ util/ # JwtUtil
βββ frontend-email-service/ # React SPA
β βββ package.json # Scripts: dev, build, test (vitest run)
β βββ vite.config.js # Port 8080, /api proxy, vitest config
β βββ Dockerfile # Node 20 build + nginx serve
β βββ nginx.conf # SPA fallback + /api/v1/ proxy to backend
β βββ .env.example # VITE_* variables
β βββ src/
β βββ main.jsx # Router: /, /sign-in, /home, /settings
β βββ AppContext.jsx # Auth + mailbox global state, localStorage
β βββ api/apiClient.js # Axios instance, Bearer header, refresh interceptor
β βββ utils/parseApiError.js # Normalises all backend error shapes
β βββ pages/ # SignUp, SignIn, Home, Settings screens
β βββ components/ # Layout, composer, email views, Discord button
β βββ i18n.js # English / German / French strings
β βββ tests/ # Vitest + MSW test suite
βββ SQL Scripts/
β βββ Tables.sql # MySQL schema init for Docker and manual setup
βββ docker-compose.yml # db + backend + frontend services
βββ .env.docker.example # Compose env template
The Seamail logo combines an envelope with dynamic wave patterns, symbolizing seamless communication. It reflects the efficient, modern, and user-friendly email service.
Mohamed Metwalli - Software Engineer & Technical Writer
π mohamedmetwalli.com Β· LinkedIn


