Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

s3mailreader is a Node.js web application that provides a simple mail client interface for reading MIME email files stored in AWS S3 buckets. It's designed to work with AWS SES (Simple Email Service) that stores incoming emails as MIME files in S3.

## Architecture

- **Single-file application**: The main server logic is in `bin/s3mailreader.js` (218 lines)
- **Frontend**: Simple AngularJS (v1.7.8) single-page application in `bin/src/index.html`
- **Backend**: Express.js server with three main API endpoints:
- `/list` - Lists email files in S3 bucket
- `/mail/:encfilekey` - Retrieves and parses a specific email (base64 encoded key)
- `/mail/:encfilekey/:checksum` - Downloads email attachments
- **Email parsing**: Uses `mailparser` library to parse MIME files from S3
- **AWS Integration**: Direct S3 API calls using aws-sdk v2

## Key Dependencies

- **express**: Web server framework
- **aws-sdk**: AWS S3 integration (v2 - older version)
- **mailparser**: MIME email parsing
- **yargs**: Command-line argument parsing
- **angular**: Frontend framework (v1.7.8)
- **bootstrap**: UI styling (v4.3.1)

## Running the Application

```bash
# Install globally
npm i -g s3mailreader

# Basic usage
s3mailreader -b bucketname

# Full parameter usage
s3mailreader -b bucketname -d directory -r awsregion -k credentialprofile -a accessid -s secretkey -p PORT
```

The application serves on port 8003 by default and requires AWS credentials configured either via:
- `~/.aws/config` and `~/.aws/credentials` files
- Command-line parameters for access keys and region

## Development Notes

- **No build process**: Application runs directly from source
- **No test framework**: No testing infrastructure present
- **Legacy dependencies**: Uses older versions (Angular 1.x, aws-sdk v2)
- **Security considerations**: File keys are base64 encoded for URL safety
- **Error handling**: Basic try/catch with 500 status responses
- **Static files**: Served from node_modules directory

## File Structure

```
bin/
├── s3mailreader.js # Main server application
└── src/
└── index.html # Frontend SPA with embedded AngularJS
```
69 changes: 39 additions & 30 deletions bin/s3mailreader.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const path = require('path');
// Packages
const express = require('express');
const _ = require('lodash');
const AWS = require('aws-sdk');
const { S3Client, ListObjectsCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const simpleParser = require('mailparser').simpleParser;
const argv = require('yargs')
.option('b', {
Expand Down Expand Up @@ -64,34 +64,24 @@ const argv = require('yargs')
type: 'int'
}).argv;

// check if region is passed as argument and is valid, otherwise check if is available a region in the ~/.aws/config
if(typeof argv.region !== "undefined"){
if (["us-east-2","us-east-1","us-west-1","us-west-2","ap-south-1","ap-northeast-3","ap-northeast-2","ap-southeast-1","ap-southeast-2","ap-northeast-1","ca-central-1","cn-north-1","cn-northwest-1","eu-central-1","eu-west-1","eu-west-2","eu-west-3","eu-north-1","sa-east-1"].includes(argv.region)){
AWS.config.update({region: argv.region});
} else {
console.error('Error: invalid AWS region');
process.exit(1);
}
} else if (typeof AWS.config.region === "undefined") {
console.error('Error: missing AWS region in your configuration, check your .aws/config file or pass the region as argument');
process.exit(1);
// Build S3 client configuration
const s3Config = {};

// Set region if provided
if (argv.region) {
s3Config.region = argv.region;
}

// check if accessKeyId/secretAccessKey are passed as arguments, otherwise check if is has been loaded a ~/.aws/credentials file with valid profile
if (typeof argv.accessid !== "undefined" && typeof argv.secretkey !== "undefined"){
AWS.config.update({accessKeyId: argv.accessid, secretAccessKey: argv.secretkey});
} else {
const awsCred = new AWS.SharedIniFileCredentials({profile: argv.credentials});
if (awsCred.accessKeyId === undefined) {
console.error('Error: invalid AWS credentials, check your .aws/credentials file or pass the accessKeyId/secretAccessKey as arguments');
process.exit(1);
} else {
AWS.config.credentials = awsCred;
}
// Set credentials if provided via CLI arguments
if (argv.accessid && argv.secretkey) {
s3Config.credentials = {
accessKeyId: argv.accessid,
secretAccessKey: argv.secretkey
};
}

// initialize components
const s3 = new AWS.S3({apiVersion: '2006-03-01'});
// Initialize S3 client (will automatically use credential chain if no explicit credentials)
const s3 = new S3Client(s3Config);
const port = argv.port || 3000;
const app = express();
const router = express.Router();
Expand All @@ -108,7 +98,8 @@ router.get('/list', async (req, res) => {

try {
// get the list of the files inside the folder
const data = await s3.listObjects(request).promise();
const command = new ListObjectsCommand(request);
const data = await s3.send(command);

// return the obj array of files
res.send(data.Contents);
Expand All @@ -134,8 +125,17 @@ router.get('/mail/:encfilekey', async (req, res) => {
try {

// get and parse the email from s3
const data = await s3.getObject(request).promise();
var email = await simpleParser(data.Body);
const command = new GetObjectCommand(request);
const data = await s3.send(command);

// Convert the Node.js Readable stream to Buffer for mailparser compatibility
const chunks = [];
for await (const chunk of data.Body) {
chunks.push(chunk);
}
const bodyBuffer = Buffer.concat(chunks);

var email = await simpleParser(bodyBuffer);

// if there are attachments, remove the attachment content (avoid to send to the browser the content of email's attachments)
if ( typeof email.attachments !== 'undefined' ){
Expand Down Expand Up @@ -169,8 +169,17 @@ router.get('/mail/:encfilekey/:checksum', async (req, res) => {
try {

// get and parse the email from s3
const data = await s3.getObject(request).promise();
const email = await simpleParser(data.Body);
const command = new GetObjectCommand(request);
const data = await s3.send(command);

// Convert the Node.js Readable stream to Buffer for mailparser compatibility
const chunks = [];
for await (const chunk of data.Body) {
chunks.push(chunk);
}
const bodyBuffer = Buffer.concat(chunks);

const email = await simpleParser(bodyBuffer);
let file;

// foreach attachments, check which one has been requested and save it on a variable
Expand Down
Loading