A pseudo-IMAP server that bridges AWS SES Email Receiving and S3 storage with local IMAP clients, enabling applications like Freescout and Dmarcguard to retrieve email content without exposing a public email system.
IMAP-SES provides a secure, behind-the-firewall solution for email management:
- No Public Email Service: Relies entirely on AWS SES for inbound email handling and S3 for storage
- IMAP Protocol Support: Works with compatible IMAP clients
- Simplified Architecture: Receives emails via SNS/S3 notifications, and serves them over IMAP
- Containerized: Runs as a Docker container within your network
Based on:
- https://docs.aws.amazon.com/ses/latest/dg/receiving-email-setting-up.html
- https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-s3.html
- https://github.com/devinstewart/sns-cloudflare-validator/blob/main/README.md
- Add a DNS record for AWS SES Email Receiving
@ IN MX 10 inbound-smtp.<region>.amazonaws.com. - Create an S3 bucket for storing email copies
$ aws s3api create-bucket --bucket <bucket-name> --region <region>
- Deploy an HTTPS endpoint for proxying SNS requests to a private network
- Cloudflare Worker VPC, based on https://developers.cloudflare.com/workers-vpc/get-started/#4-configure-your-worker
$ git clone https://github.com/HankieCodes/imap-ses.git $ cd imap-ses/cloudflare $ cp wrangler.example.jsonc wrangler.jsonc # Edit wrangler.jsonc with your values: # - SNS_TOPIC_ARN # - VPC_SERVICE_ID $ pnpm install $ pnpm run deploy
- AWS Lambda, TBD (simple
fetch()content, subscribe as Lambda instead of HTTP)
- Cloudflare Worker VPC, based on https://developers.cloudflare.com/workers-vpc/get-started/#4-configure-your-worker
- Create an SNS topic and subscription for email notifications
$ aws sns create-topic --name imap-ses-email-received --region <region> $ aws sns subscribe --topic-arn imap-ses-email-received --protocol https --notification-endpoint <worker-url>
- Configure AWS SES Email Receiving rules:
- Sign into AWS Console
- Open
SES > Email Receiving - Create a new ruleset as:
- Recipient conditions:
example.com(your domain) - Actions:
Deliver to Amazon S3 bucket - Choose the S3 bucket you created earlier
- Choose the SNS topic you created earlier
- Recipient conditions:
- Save the ruleset
- Make the ruleset active
- Go to
SES > Email Receivinghome - Click
Set as activefor the new ruleset
- Go to
- Deploy IMAP-SES server
- Ensure the container is on a shared network with Cloudflared container.
- Docker Compose:
$ git clone https://github.com/HankieCodes/imap-ses.git $ cd imap-ses $ cp docker-compose.example.yml docker-compose.yml # Edit docker-compose.yml with your values: # - AWS_REGION # - AWS_ACCESS_KEY_ID # - AWS_SECRET_ACCESS_KEY $ docker-compose up -d
- Docker CLI:
$ docker run -d \ --name imap-ses \ -p 143:143 \ -p 2525:2525 \ --network <network-name> \ -v /local/mail/storage:/data \ ghcr.io/hankiecodes/imap-ses:latest
- Connect IMAP client (varies)
HOST: imap-ses # (or your docker container name) PORT: 143 USERNAME: postmaster.example.com # (or any email, replace @ with ".") PASSWORD: anything TLS/SSL: no
DATA_DIR: Root directory for email storage (default:/data)SNS_PORT: HTTP port for SNS notifications (default:2525)IMAP_PORT: TCP port for IMAP clients (default:143)
AWS SES (Email Receiving)
↓
S3 Bucket (Email Storage)
↓
SNS Topic (Event Notification)
↓
Cloudflare Worker VPC (Optional Proxy)
↓
IMAP-SES Server (Docker)
↓
IMAP Clients (Freescout, Dmarcguard)
A dockerized Node.js pseudo-IMAP server that:
- Listens on port 143 (IMAP) for client connections
- Listens on port 2525 (HTTP) for SNS notifications
- Maps each IMAP login username to a local directory (
/data/<username>/) - Accepts any password (no authentication required)
- Stores emails as
.emlfiles in per-user directories - Supports only the INBOX mailbox
- Implements core IMAP commands:
LOGIN,SELECT,LIST,FETCH,STORE,SEARCH
Key Features:
- No password validation (operates within trusted network)
- Uses S3 SDK to download emails on-demand
- Parses email headers to route to correct user directory
An optional security layer that:
- Validates SNS signatures before forwarding
- Proxies requests through Cloudflare Workers to your VPC-hosted IMAP server
- Prevents direct internet exposure of your IMAP service
Emails are stored as .eml files in /data/<username>/ directories:
/data/
└── postmaster.example.com/
├── MessageId_receipt-001.eml
├── MessageId_receipt-002.eml
└── MessageId_receipt-003.eml
└── help.skyway.run/
└── MessageId_unparseable.eml
- Username is the email address with
@replaced by. - Filenames include the SNS MessageId and original S3 key
- Files are deleted when marked as read (IMAP STORE with
\Seen)
CAPABILITYNOOPLOGOUTLOGINLISTSELECT INBOXSTATUSFETCH/UID FETCH(RFC822, RFC822.HEADER, RFC822.TEXT, ENVELOPE, FLAGS)STORE/UID STORE(delete via\Seenflag)SEARCH/UID SEARCH
- Only INBOX mailbox supported
- No authentication (assumes trusted network)
- No IMAP extensions (IDLE, COMPRESS, etc.)
- No support for multiple mailboxes
- No message flags beyond basic support
- No password validation
- Runs on unsecured IMAP port
- Assumes trusted network environment
- Use VPC/security group restrictions in AWS
- Consider Cloudflare Worker proxy for added security
This is a monorepo using pnpm workspaces.
# Install dependencies
pnpm install
# Build all packages
pnpm --recursive run build
# Run IMAP server locally
cd imap
pnpm run build
node dist/server.js- Verify SNS topic is subscribed to your IMAP-SES HTTP endpoint
- Check container logs:
docker logs <container> - Confirm S3 bucket and SNS topic are properly configured in SES receipt rules
- Verify IAM permissions for S3 access
- Check
IMAP_PORTandSNS_PORTare correctly configured - Verify Docker port mappings:
-p 143:143 -p 2525:2525 - Confirm firewall rules allow traffic to your server
Contributions welcome!
LGPL-3.0 — See LICENSE.txt