A TypeScript-based Express.js server for sending emails via SMTP, rendering templates, managing database records with MySQL, robust validation, error handling, modular architecture, and advanced logging.
- Express.js API with modular routing
- TypeScript for type safety
- Zod schema validation for all request payloads
- Nodemailer integration for SMTP email delivery
- Handlebars template rendering with production caching
- MySQL database integration with connection pooling
- SSL/TLS support for secure database and SMTP connections
- Centralized error handling middleware
- Environment variable configuration with validation
- Rate limiting for mail and render endpoints
- Utility functions for string formatting and error extraction
- Advanced logging with Pino and multistream:
- Pretty terminal output with colorization
- Structured JSON logs per level (error, info, warn, debug)
- Automatic log file creation in
logs/directory - Custom timestamp format (MM-DD-YYYY HH:mm:ss)
- Comprehensive error logging and debugging
- HTTPS server support with SSL/TLS certificates
- Graceful shutdown handling with signal interception
- Matrix-themed UI components (dashboard and 404 page)
This tool is intended exclusively for authorized penetration testing, red team operations, and security research on systems you own or have explicit written permission to test.
Unauthorized use of this tool against systems without prior consent is illegal and may violate computer crime laws including but not limited to the Computer Fraud and Abuse Act (CFAA), the UK Computer Misuse Act, and equivalent legislation in your jurisdiction.
The author(s) assume no liability for any misuse, damage, or illegal activity conducted with this tool. By using this software, you agree that you are solely responsible for compliance with all applicable local, state, national, and international laws.
Use responsibly and ethically.
.
├── assets/
│ ├── facebook.ico
│ └── matrix.ico
├── backup/
│ └── logs/ # (if present at runtime)
├── certs/
│ ├── db/
│ │ ├── ca-cert.pem
│ │ ├── client-cert.pem
│ │ └── client-key.pem
│ ├── server/
│ │ ├── cert.pem
│ │ └── key.pem
│ └── README.md
├── db/
│ ├── offser_passwords.sql
│ ├── offser_routines.sql
│ └── README.md
├── src/
│ ├── __mocks__/
│ │ ├── index.ts
│ │ ├── nodemailer.ts
│ │ └── server.ts
│ ├── config/
│ │ ├── __mocks__/
│ │ │ └── env.ts
│ │ ├── env.spec.ts
│ │ └── env.ts
│ ├── controllers/
│ │ ├── app.controller.ts
│ │ ├── db.controller.ts
│ │ ├── index.ts
│ │ ├── mail.controller.spec.ts
│ │ ├── mail.controller.ts
│ │ ├── template.controller.spec.ts
│ │ └── template.controller.ts
│ ├── errors/
│ │ ├── db.error.ts
│ │ ├── index.ts
│ │ ├── mail.error.ts
│ │ ├── server.error.ts
│ │ ├── template.error.ts
│ │ └── types/
│ │ ├── error.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── middleware/
│ │ ├── error.middleware.spec.ts
│ │ ├── error.middleware.ts
│ │ ├── index.ts
│ │ ├── not-found.middleware.spec.ts
│ │ └── not-found.middleware.ts
│ ├── routes/
│ │ ├── app.routes.ts
│ │ ├── db.routes.ts
│ │ ├── health.routes.spec.ts
│ │ ├── health.routes.ts
│ │ ├── index.ts
│ │ ├── mail.routes.ts
│ │ └── template.routes.ts
│ ├── schemas/
│ │ ├── __mocks__/
│ │ │ └── index.ts
│ │ ├── app.schema.ts
│ │ ├── db.schema.ts
│ │ ├── index.ts
│ │ ├── mail.schema.spec.ts
│ │ ├── mail.schema.ts
│ │ ├── template.schema.spec.ts
│ │ └── template.schema.ts
│ ├── services/
│ │ ├── __mocks__/
│ │ │ └── index.ts
│ │ ├── db.service.spec.ts
│ │ ├── db.service.ts
│ │ ├── index.ts
│ │ ├── mail.service.spec.ts
│ │ ├── mail.service.ts
│ │ ├── template.service.spec.ts
│ │ └── template.service.ts
│ ├── templates/
│ │ ├── dashboard.hbs
│ │ ├── facebook-login.hbs
│ │ ├── not-found.hbs
│ │ ├── offers.hbs
│ │ ├── sales.hbs
│ │ └── shipment.hbs
│ └── utils/
│ ├── __mocks__/
│ │ └── (if present)
│ ├── error.util.spec.ts
│ ├── error.util.ts
│ ├── format.util.spec.ts
│ ├── format.util.ts
│ ├── index.ts
│ ├── logger.util.spec.ts
│ ├── logger.util.ts
│ ├── shutdown.util.spec.ts
│ └── shutdown.util.ts
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── container.yml
│ ├── publish.yml
│ └── release.yml
├── .dockerignore
├── .env
├── .env.container
├── .env.example
├── .gitignore
├── .prettierrc
├── .nvmrc
├── Dockerfile
├── LICENSE
├── README.md
├── esbuild.cli.mjs
├── esbuild.container.mjs
├── eslint.config.js
├── package.json
├── package-lock.json
├── tsconfig.json
├── tsconfig.test.json
├── vitest.config.ts
- Node.js (v18+ recommended)
- npm
- SMTP server credentials (e.g., Gmail with App Password, SendGrid, etc.)
- MySQL database (v8.0+ recommended)
- (Optional) SSL/TLS certificates for HTTPS server and secure MySQL connections
You can build and run Offser in a Docker container for easy deployment.
From the project root, run:
docker build -t offser .docker run --env-file .env -p 8080:8080 -v $(pwd)/certs:/app/certs offser--env-file .envloads environment variables (see.env.examplefor required values)-p 8080:8080maps the container port to your host-v $(pwd)/certs:/app/certsmounts your localcertsdirectory for SSL/DB certificates
If your MySQL database is running on your host machine (not in Docker), set in your .env:
DB_HOST=host.docker.internalThis allows the container to connect to your host's MySQL instance. If your database is in another container, use the Docker network service name.
The build uses a multi-stage Dockerfile and a .dockerignore file to exclude unnecessary files (e.g., node_modules, logs, dist, local .env).
For more details, see the Dockerfile and .dockerignore in the project root.
-
Clone the repository
git clone https://github.com/marcomg-byte/offser.git cd offser -
Install dependencies
npm install
-
Configure environment variables
Create a
.envfile based on.env.example:# Server Configuration NODE_ENV=development PORT=8080 # SMTP Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password MAIL_FROM=your-email@gmail.com # Rate Limiting MAIL_SERVICE_RATE_LIMIT=10 MAIL_SERVICE_RATE_WINDOW=15 RENDER_SERVICE_RATE_LIMIT=20 RENDER_SERVICE_RATE_WINDOW=10 # Database Configuration DB_HOST=localhost DB_PORT=3306 DB_USER=your_db_user DB_PASS=your_db_password DB_NAME=your_database_name DB_CONNECTION_LIMIT=10 DB_QUEUE_LIMIT=0 DB_WAIT_FOR_CONNECTIONS=true DB_KEEP_ALIVE=true DB_KEEP_ALIVE_INITIAL_DELAY=0 # Database SSL (Optional) DB_SSL_ENABLED=false # DB_SSL_CA=certs/db/ca.pem # DB_SSL_CERT=certs/db/client-cert.pem # DB_SSL_KEY=certs/db/client-key.pem # DB_SSL_REJECT_UNAUTHORIZED=true # HTTPS Configuration (Optional) HTTPS_ENABLED=false # HTTPS_KEY_PATH=certs/server/key.pem # HTTPS_CERT_PATH=certs/server/cert.pem
-
Set up the database
Run the SQL scripts from the
db/directory:# Create the passwords table mysql -u your_db_user -p your_database_name < db/offser_passwords.sql # Create stored procedures mysql -u your_db_user -p your_database_name < db/offser_routines.sql
Or manually execute the scripts in your MySQL client. See db/README.md for detailed setup instructions.
-
Build the project
npm run build
-
Start the server
npm start
The server will run on the port specified in
.env(default: 3000).
For development with automatic reloads and template watching:
npm run devThis runs both the TypeScript compiler and template watcher simultaneously using concurrently.
npm run build- Compile TypeScript to JavaScript and copy templates to distnpm run build:templates- Copy Handlebars templates to dist directorynpm run dev- Start development server with hot reload (Nodemon + tsx)npm start- Run the production buildnpm run lint- Check code with ESLintnpm run lint:fix- Fix ESLint issues automaticallynpm test- Run unit and integration tests with Vitest (watch mode)npm run test:run- Run tests oncenpm run test:coverage- Generate test coverage reportnpm run test:ui- Open Vitest UI for interactive testingnpm run clean- Remove dist foldernpm run clean:logs- Remove logs foldernpm run backup:logs- Archive log files to backup/logs directorynpm run watch:templates- Watch templates directory for changesnpm run prepare- Clean and build (runs before publishing)
This project uses Pino with multistream for advanced logging:
- Terminal output: Pretty-printed, colored logs with pino-pretty for development.
- Log files: Structured JSON logs per level in the
logs/directory:error.log- Error-level messagesinfo.log- Info-level messageswarn.log- Warning-level messagesdebug.log- Debug-level messages
- Automatic log directory creation: No manual setup required.
- Custom timestamp format:
MM-DD-YYYY HH:mm:ssfor easy readability. - Uppercase log levels: Consistent formatting across all log outputs.
- Log archiving: Use
npm run backup:logsto archive logs tobackup/logs/before cleaning.
All log files are automatically created in the logs/ directory. The logger is configured in logger.util.ts.
- Controllers - Handle HTTP requests and responses, coordinate between services
- Services - Business logic (email sending, template rendering, database operations)
- Routes - Define API endpoints, apply rate limiting, and middleware
- Schemas - Zod-based data validation rules for requests and responses
- Middleware - Cross-cutting concerns (error handling, 404 pages, request logging)
- Utils - Reusable utility functions (logging, formatting, error extraction, shutdown)
- Config - Application configuration and environment variable validation
- Templates - Handlebars templates for dynamic HTML emails and pages
- Errors - Custom error classes for different failure scenarios with context
Centralized error handling middleware in error.middleware.ts catches and processes:
- Zod Validation Errors → 400 Bad Request with prettified validation details
- Database Errors (connection, query, SSL config, transporter creation) → 500 Internal Server Error with context
- Mail/Template Errors → 500 Internal Server Error with detailed context
- SSL/Certificate Errors (missing files, invalid paths) → 500 Internal Server Error with file information
- Unknown Errors → 500 Internal Server Error with fallback message
All errors are logged using the Pino logger with:
- Complete stack traces
- Error context (query parameters, request body excerpts)
- Custom error properties (cause, transporter info, template data)
- Timestamps and log levels
See extractErrorInfo for error normalization logic.
The database layer uses MySQL 2 with connection pooling:
- Connection Pooling: Configurable MySQL connection pool for efficient resource usage
- SSL/TLS Support: Optional encrypted database connections with certificate validation
- Stored Procedures: All database operations use MySQL stored procedures:
INSERT_PASSWORD(mail, password)- Insert new password recordREAD_PASSWORDS(upperLimit, lowerLimit)- Read password records with rangeDELETE_ENTRY(lowerLimit, upperLimit)- Delete password records by ID range
- Transaction Support: Automatic transaction handling with BEGIN, COMMIT, and ROLLBACK on errors
- Connection Verification: Startup verification ensures database connectivity before accepting requests
- Error Handling: Database errors are wrapped in custom error classes with query context
Configuration is managed in db.service.ts. See db/README.md and certs/README.md for setup details.
Handlebars templates with production caching:
- Development Mode: Templates are reloaded on every request for immediate feedback
- Production Mode: Templates are preloaded and cached in memory for performance
- Custom Helpers:
toCSVhelper converts password records to CSV format for export - Template Compilation: Handlebars compiles templates with data injection
- Error Handling: Template compilation errors include template name and data context
See template.service.ts for implementation.
- Endpoint:
GET /health - Description: Check server operational status, uptime, and current timestamp
Success Response (200):
{
"status": "ok",
"timestamp": "2024-02-05T09:07:03.000Z",
"localDate": "02-05-2024 09:07:03",
"uptime": 120.47
}- Endpoint:
POST /records/insert - Description: Insert a new password record into the database
- Rate Limiting: No rate limit
Request Body:
{
"mail": "user@example.com",
"password": "securePassword123"
}Validation:
mail: Must be a valid email addresspassword: Must be a non-empty string
Success Response (200):
{
"title": "Data Inserted Successfully!",
"data": {
"mail": "user@example.com",
"password": "securePassword123"
}
}- Endpoint:
GET /records/read - Description: Retrieve password records within a specified ID range
- Rate Limiting: No rate limit
Request Body:
{
"lowerLimit": 1,
"upperLimit": 10
}Validation:
lowerLimit: Optional positive integer >= 1upperLimit: Required positive integer >= 1
Success Response (200):
{
"title": "Data Read Successfully!",
"data": [
{
"ID": 1,
"MAIL": "user@example.com",
"PASSWORD": "securePassword123"
}
]
}- Endpoint:
DELETE /records/delete - Description: Delete password records by ID or ID range
- Rate Limiting: No rate limit
Request Body:
{
"lowerLimit": 5,
"upperLimit": 10
}Validation:
lowerLimit: Required positive integer >= 1upperLimit: Optional positive integer >= 1
Success Response (200):
{
"title": "Data Deleted Successfully!",
"lowerLimit": 5,
"upperLimit": 10
}- Endpoint:
GET /records/health - Description: Verify database connectivity and return connection status
- Rate Limiting: No rate limit
Success Response (200):
{
"status": "OK",
"timestamp": "2024-02-05T09:07:03.000Z",
"localDate": "02-05-2024 09:07:03",
"uptime": 120.47
}Error Response (503):
{
"status": "ERROR",
"timestamp": "2024-02-05T09:07:03.000Z",
"localDate": "02-05-2024 09:07:03",
"uptime": 120.47
}- Endpoint:
GET /app/dashboard?lowerLimit=1&upperLimit=100 - Description: Render a Matrix-themed dashboard displaying password records with CSV export functionality
- Rate Limiting: Configurable via
RENDER_SERVICE_RATE_LIMITandRENDER_SERVICE_RATE_WINDOW
Query Parameters:
lowerLimit(optional, number) - Starting ID for recordsupperLimit(required, number) - Ending ID for records
Success Response (200):
- Returns rendered HTML page with:
- Matrix-style green-on-black theme with falling code animation
- Data grid displaying ID, email, and password for each record
- CSV export button (appears only if data is present)
- Responsive design for mobile devices
- Empty state message when no data is available
Features:
- Client-side CSV export using the
toCSVHandlebars helper - Automatic download of
password_records.csvfile - Matrix-themed UI with glow effects and animations
- Endpoint:
POST /mail/send - Description: Send an email through the configured SMTP server
- Rate Limiting: Configurable via
MAIL_SERVICE_RATE_LIMITandMAIL_SERVICE_RATE_WINDOW
Request Body:
{
"to": "recipient@example.com",
"subject": "Email Subject",
"text": "Plain text content",
"html": "<p>HTML content</p>",
"templateName": "offers",
"templateData": {
"customerName": "John",
"offers": [
{ "title": "Discount", "description": "10% off" }
],
"ctaUrl": "https://example.com/offer"
}
}Request Fields:
to(required, string) - Recipient email address (valid email format)subject(required, string) - Email subject line (min 1 character)text(optional, string) - Plain text email bodyhtml(optional, string) - HTML email bodytemplateName(optional, string) - Name of Handlebars template to use (without .hbs extension)templateData(optional, object) - Data object for template variables
Success Response (200):
{
"title": "Email Sent Successfully!",
"data": {
"to": "recipient@example.com",
"subject": "Email Subject",
"templateName": "offers"
},
"mailResponse": {
"messageId": "<abc123@gmail.com>",
"accepted": ["recipient@example.com"],
"response": "250 Message accepted"
}
}Validation Error Response (400):
{
"title": "Request Validation Error",
"error": "[Expected string at 'to', Expected string at 'subject']"
}Server Error Response (500):
{
"title": "Mail Service Error",
"name": "MailDeliveryError",
"message": "Failed to send email to recipient@example.com",
"stack": "...",
"cause": {
"code": "EAUTH",
"message": "Invalid login credentials"
}
}- Endpoint:
GET /render/:templateName - Description: Render a Handlebars template with provided data
- Rate Limiting: Configurable via
RENDER_SERVICE_RATE_LIMITandRENDER_SERVICE_RATE_WINDOW
Request Example:
GET /render/offers
Content-Type: application/json
{
"customerName": "John",
"offers": [
{ "title": "10% Discount", "description": "On all items" }
],
"ctaUrl": "https://example.com/offer"
}Available Templates:
sales- Sales email with product offers (requires: offers[], ctaUrl)offers- Promotional offers email (requires: customerName, offers[], ctaUrl)shipment- Order shipment confirmation (requires: customerName, orderNumber, shippingAddress, estimatedDelivery, items[], trackingUrl)dashboard- Password records dashboard with Matrix theme (requires: data[])not-found- 404 error page with Matrix theme (no data required)
Success Response (200):
- Returns rendered HTML string
Validation Error Response (400):
{
"title": "Request Validation Error",
"error": "[Expected string at 'customerName']"
}Template Not Found (500):
{
"title": "Template Compilation Error",
"name": "TemplateCompileError",
"message": "Template 'invalid-template' not found",
"stack": "..."
}capitalizeWord(word)- Capitalize first letter of a word, lowercase the restcapitalizeString(value)- Capitalize first letter of each word in a stringformatDate(date)- Format Date object asMM-DD-YYYY HH:mm:sslogger- Pino logger instance with multistream (terminal + files)extractErrorInfo(error)- Normalize error objects for consistent logginggracefulShutdown(server, signal)- Handle graceful server shutdown on SIGTERM/SIGINT
Error Extraction:
The extractErrorInfo utility handles various error types:
ZodError- Prettifies validation errors with field paths- Custom errors (Mail, Template, Database) - Extracts name, message, stack, and custom properties
- Native errors - Standard error information
- Unknown types - Converts to string representation
To enable HTTPS for the Express server:
-
Generate or obtain SSL certificates:
# For development (self-signed): openssl req -x509 -newkey rsa:4096 -keyout certs/server/key.pem -out certs/server/cert.pem -days 365 -nodes -
Place certificates in
certs/server/directory -
Update
.envfile:HTTPS_ENABLED=true HTTPS_KEY_PATH=certs/server/key.pem HTTPS_CERT_PATH=certs/server/cert.pem
-
Application will:
- Start HTTPS server on configured port
- Log certificate loading status
- Throw
CertificateNotFoundErrorif files are missing - Automatically verify certificate validity
See certs/README.md for detailed certificate management instructions.
To enable SSL/TLS for encrypted database connections:
-
Obtain SSL certificates from your database provider (e.g., AWS RDS, Google Cloud SQL)
-
Place certificates in
certs/db/directory:ca.pem- Certificate Authority certificateclient-cert.pem- Client certificateclient-key.pem- Client private key
-
Update
.envfile:DB_SSL_ENABLED=true DB_SSL_CA=certs/db/ca.pem DB_SSL_CERT=certs/db/client-cert.pem DB_SSL_KEY=certs/db/client-key.pem DB_SSL_REJECT_UNAUTHORIZED=true
-
Configuration options:
DB_SSL_ENABLED=true- Enables SSL for database connectionsDB_SSL_CA- Path to CA certificate (required for SSL)DB_SSL_CERT- Path to client certificate (optional, for mutual TLS)DB_SSL_KEY- Path to client key (optional, for mutual TLS)DB_SSL_REJECT_UNAUTHORIZED=true- Reject invalid certificates (recommended for production)
See certs/README.md and db/README.md for more details.
The SMTP_SECURE environment variable controls SSL/TLS encryption for SMTP connections:
Secure Connection (SSL/TLS):
SMTP_SECURE=true
SMTP_PORT=465- Full SSL/TLS encryption from the start
- Uses port 465 (standard for SMTPS)
- Recommended for production environments
- Application enforces port 465 when
SMTP_SECURE=true
STARTTLS Connection:
SMTP_SECURE=false
SMTP_PORT=587- Connection starts unencrypted, upgrades to TLS
- Uses port 587 (standard for SMTP with STARTTLS)
- Supported by most email providers
- Application enforces port 587 when
SMTP_SECURE=false
Port Enforcement: The application validates SMTP configuration on startup and will throw an error if:
SMTP_SECURE=truebutSMTP_PORTis not 465SMTP_SECURE=falsebutSMTP_PORTis not 587
This prevents misconfiguration and ensures proper encryption is used.
Gmail Configuration Example:
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password # Use App Password, not account password
MAIL_FROM=your-email@gmail.comSee mail.service.ts for implementation details.
All environment variables are validated on startup using Zod schemas in env.ts.
SMTP Configuration:
SMTP_HOST- SMTP server hostname (e.g.,smtp.gmail.com)SMTP_USER- SMTP authentication username/emailSMTP_PASS- SMTP authentication passwordMAIL_FROM- Default sender email address
Rate Limiting:
MAIL_SERVICE_RATE_LIMIT- Max email requests per window (integer)MAIL_SERVICE_RATE_WINDOW- Rate limit window in minutes (integer)RENDER_SERVICE_RATE_LIMIT- Max render requests per window (integer)RENDER_SERVICE_RATE_WINDOW- Rate limit window in minutes (integer)
Application Environment:
NODE_ENV- Environment mode (development,production,test)
Database Configuration:
DB_HOST- MySQL server hostnameDB_PORT- MySQL server port (typically 3306)DB_USER- Database usernameDB_PASS- Database passwordDB_NAME- Database name
Server:
PORT- Server listening port (default:3000)
SMTP:
SMTP_PORT- SMTP server port (default:587)SMTP_SECURE- Use SSL/TLS (default:false)
Database Connection Pool:
DB_CONNECTION_LIMIT- Max pool connections (default:10)DB_QUEUE_LIMIT- Max queued connection requests (default:0= unlimited)DB_WAIT_FOR_CONNECTIONS- Wait for available connection (default:true)DB_KEEP_ALIVE- Enable TCP keep-alive (default:true)DB_KEEP_ALIVE_INITIAL_DELAY- Keep-alive initial delay in ms (default:0)
Database SSL:
DB_SSL_ENABLED- Enable SSL for database (default:false)DB_SSL_CA- Path to CA certificate (optional)DB_SSL_CERT- Path to client certificate (optional)DB_SSL_KEY- Path to client key (optional)DB_SSL_REJECT_UNAUTHORIZED- Reject invalid certs (default:true)
HTTPS Server:
HTTPS_ENABLED- Enable HTTPS server (default:false)HTTPS_KEY_PATH- Path to HTTPS private key (default:./key.pem)HTTPS_CERT_PATH- Path to HTTPS certificate (default:./cert.pem)
Validation: Missing required variables will cause the application to fail during startup with a clear error message indicating which variable is missing.
See env.ts for detailed documentation and validation logic.
-
ESLint - TypeScript-aware linting with recommended rules
- Configuration:
eslint.config.js - Uses
@typescript-eslintfor TypeScript support - Integrates with Prettier for consistent formatting
- Max line length: 120 characters
- Configuration:
-
Prettier - Opinionated code formatting
- Configuration:
.prettierrc - Automatically formats on save (if editor is configured)
- Enforced through ESLint plugin
- Configuration:
Commands:
npm run lint # Check for linting issues
npm run lint:fix # Auto-fix linting and formatting issues- TypeScript - Strict type checking enabled
- Main config:
tsconfig.json - Test config:
tsconfig.test.json - Strict mode enabled for maximum type safety
- No implicit
anytypes allowed
- Main config:
This project uses Vitest for unit and integration testing with comprehensive coverage.
- Configuration:
vitest.config.ts - Test Files: Co-located with source files (
.spec.tsextension) - Mocks: Located in
__mocks__directories for module mocking - Coverage Provider: V8 for accurate coverage reporting
npm test # Run tests in watch mode (interactive)
npm run test:run # Run all tests once (CI mode)
npm run test:coverage # Generate coverage report
npm run test:ui # Open Vitest UI (visual test runner)
npm run test:clean # Remove coverage directoryCoverage reports are generated in the coverage/ directory with:
- Text report - Console output
- JSON report - Machine-readable format
- HTML report - Interactive browser view (open
coverage/index.html)
Coverage Exclusions:
node_modules/- Third-party dependenciesdist/- Compiled outputerrors/- Error class definitionsconfig/- Configuration files*.config.*- All configuration files*.d.ts- TypeScript definition files**/index.ts- Re-export files**/types/**- Type definition directories
Service Tests:
mail.service.spec.ts- Mail service and transportertemplate.service.spec.ts- Template compilation and caching
Controller Tests:
mail.controller.spec.ts- Email sending endpoint
Route Tests:
health.routes.spec.ts- Health check endpoint
Utility Tests:
format.util.spec.ts- String formatting utilitieserror.util.spec.ts- Error extraction logic
Config Tests:
env.spec.ts- Environment variable validation
- Nodemailer - Mocked transporter for email tests
- Filesystem - Mocked
fsmodule for template tests - Environment - Mocked config for different NODE_ENV scenarios
- Database - Connection pool mocked for controller tests
-
Create a feature branch:
git checkout -b feature/my-feature
-
Make your changes to the codebase
-
Run linting and fix issues:
npm run lint:fix
-
Run tests and verify coverage:
npm test npm run test:coverage -
Build the project:
npm run build
-
Test your changes locally:
npm run dev
-
Commit your changes:
git add . git commit -m "Add new feature: description"
-
Push to the branch:
git push origin feature/my-feature
-
Open a pull request on GitHub
- Hot Reload:
npm run devwatches TypeScript files and templates - Template Development: Templates reload automatically in development mode
- Log Monitoring: Check
logs/directory for detailed application logs - Database Testing: Use transactions to test database operations safely
- Environment Switching: Create
.env.developmentand.env.productionfor different configs
Install Offser globally to use as a command-line tool:
npm install -g offserThis makes the offser command available system-wide.
Start the server from anywhere:
offserThe CLI will:
- Look for
.envfile in the current directory - Start the server with the same configuration
- Use environment variables from the shell if
.envis not found
Requirements:
- Project must be built (
npm run build) before publishing or installing globally dist/index.jsmust exist and be executable.envfile should be in the current working directory
To publish to npm:
npm run prepare # Clean and build
npm publish # Publish to npm registryThe prepare script automatically runs before publishing to ensure the build is up-to-date.
Symptoms:
- Email sending fails with authentication errors
- Connection timeout errors
Solutions:
- Verify SMTP credentials in
.envfile - Check firewall settings (allow ports 587 and 465)
- For Gmail:
- Enable 2-factor authentication
- Generate and use an App Password
- Don't use your Google account password
- Verify
SMTP_SECUREandSMTP_PORTmatch (587 for false, 465 for true) - Test your credentials with a mail client (e.g., Thunderbird)
Symptoms:
- "Connection refused" errors
- Authentication failures
- SSL handshake errors
Solutions:
- Verify database credentials in
.envfile - Ensure MySQL server is running:
# Check MySQL status sudo systemctl status mysql # Linux brew services list # macOS with Homebrew
- Check firewall settings (allow port 3306)
- Verify database exists and user has proper permissions:
SHOW DATABASES; SHOW GRANTS FOR 'your_user'@'localhost';
- If using SSL:
- Ensure certificate paths in
.envare correct - Verify certificate files exist and are readable
- Check certificate expiration dates
- Test SSL connection with MySQL client
- Ensure certificate paths in
- Check MySQL error logs for detailed error messages
- Verify connection pool configuration (limits, timeouts)
Symptoms:
- Build fails with type errors
- IDE shows red squiggles
Solutions:
- Ensure
tsconfig.jsonis properly configured - Run
npm run buildto see all type errors - Check for missing type definitions:
npm install --save-dev @types/node @types/express
- Use
npm run lint:fixto fix formatting issues - Clear TypeScript cache:
rm -rf node_modules/.cache
- Verify TypeScript version matches project requirements
Symptoms:
- "EADDRINUSE" error on startup
- Server fails to bind to port
Solutions:
- Change
PORTin.envto an available port - Kill the process using the port:
# macOS/Linux lsof -ti:8080 | xargs kill -9 # Windows netstat -ano | findstr :8080 taskkill /PID <PID> /F
- Use a different port range for development (8000-9000)
Symptoms:
- Log files not created
- Missing log entries
- Permission errors
Solutions:
- Log files are created automatically in
logs/directory - Check directory permissions:
ls -la logs/ chmod 755 logs/ # If needed - Ensure the application process has write access
- Verify sufficient disk space
- Use
npm run backup:logsto archive logs before cleaning - Check logger configuration in
logger.util.ts
Symptoms:
- "Certificate not found" errors
- SSL handshake failures
- Browser security warnings
Solutions:
- Ensure certificate files exist at paths specified in
.env:ls -la certs/server/ ls -la certs/db/
- Check file permissions (must be readable by application):
chmod 644 certs/server/*.pem - For development, self-signed certificates are acceptable
- For production, use certificates from a trusted CA (Let's Encrypt, etc.)
- Verify certificate validity:
openssl x509 -in certs/server/cert.pem -text -noout
- Check certificate expiration dates
- See certs/README.md for detailed troubleshooting
Symptoms:
- Template not found errors
- Handlebars compilation errors
- Missing template variables
Solutions:
- Templates are cached in production mode (
NODE_ENV=production) - In development mode, templates reload on each request
- Ensure template files exist in
src/templates/directory:ls src/templates/
- Check template syntax for Handlebars errors
- Verify template data matches schema in
template.schema.ts - Clear template cache by restarting the server
- Use
npm run build:templatesto ensure templates are copied todist/
Symptoms:
- "Too many requests" errors
- 429 status codes
Solutions:
- Adjust rate limits in
.env:MAIL_SERVICE_RATE_LIMIT=100 MAIL_SERVICE_RATE_WINDOW=15 RENDER_SERVICE_RATE_LIMIT=50 RENDER_SERVICE_RATE_WINDOW=10
- Rate limits are per IP address
- Wait for the rate limit window to expire
- Use different IP addresses for testing (e.g., VPN, mobile network)
Contributions are welcome! Please follow these guidelines:
- Fork the repository and create a feature branch
- Follow the code style - Use ESLint and Prettier
- Write tests for new features
- Update documentation - Add JSDoc comments and update README
- Commit messages - Use clear, descriptive commit messages
- Pull requests - Provide a clear description of changes
Code Standards:
- Follow existing file structure and naming conventions
- Use TypeScript types for all function parameters and returns
- Add JSDoc comments for public APIs
- Write unit tests for utilities and services
- Write integration tests for controllers and routes
- Maintain test coverage above 80%
- Repository: marcomg-byte/offser
- Issues: GitHub Issues
- Pull Requests: GitHub PRs
- Express.js - Web framework
- TypeScript - Type-safe JavaScript
- Zod - Schema validation
- Nodemailer - Email sending
- Handlebars - Template engine
- Pino - Fast logger
- Vitest - Testing framework
- MySQL - Database system
This project uses automated GitHub Actions workflows for versioned releases and npm publishing:
- Release workflow: When a pull request is merged into
mainwith the labelpatch,minor, ormajor, the version is automatically bumped, a git tag is created, and a GitHub Release is published. The merged pull request also receives areleasedlabel. - Publish workflow: When a new version tag (e.g.,
v1.2.3) is pushed, the package is built and published to the npm registry using the trusted publisher connection.
- Open a pull request to
mainwith your changes. - Add one of the labels:
patch,minor, ormajorto the PR (according to semantic versioning). - Merge the PR.
- The workflows will:
- Bump the version in
package.jsonand create a git tag. - Publish a GitHub Release.
- Build and publish the new version to npm.
- Add the
releasedlabel to the PR.
- Bump the version in
- Current Version: 1.1.6
- Registry: https://www.npmjs.com/package/offser
- Install:
npm install -g offser
- Usage:
offser --help
This project uses GitHub Actions for automated CI, container validation, release management, and npm publishing. The workflows are defined in .github/workflows/:
- Trigger: On every pull request to the
mainbranch - Steps:
- Checks out the code
- Sets up Node.js using the version in
.nvmrc - Installs dependencies with
npm ci - Runs all tests (
npm run test:run) - Performs a dry run of
npm publishto verify publishability
- Trigger: On every pull request to the
mainbranch - Steps:
- Checks out the code
- Sets up Node.js using
.nvmrc - Installs dependencies (ignoring scripts)
- Bundles the container (
npm run bundle:container) - Builds the container image (
npm run build:container)
- Trigger: On pull request events (opened, synchronized, reopened, closed) to
main - Steps:
- Checks out the code with full history
- Determines the version bump type based on PR labels (
major,minor,patch) - (Further steps may include version bumping, changelog generation, tagging, and release publishing)
- Trigger:
- On push of a tag matching the pattern
v*.*.* - On pull request to
main(dry run)
- On push of a tag matching the pattern
- Steps:
- Checks out the code
- Sets up Node.js using
.nvmrc - Installs dependencies with
npm ci - For PRs: Performs a dry run of
npm publish - For tags: Publishes the package to npm with provenance using the
NODE_AUTH_TOKENsecret
For more details, see the workflow files in .github/workflows/.