initial import
This commit is contained in:
11
.env.example
Normal file
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
# POP3 Source Server Configuration
|
||||
POP3_HOST=pop.example.com
|
||||
POP3_PORT=995
|
||||
POP3_USERNAME=your_pop3_username
|
||||
POP3_PASSWORD=your_pop3_password
|
||||
|
||||
# IMAP Destination Server Configuration
|
||||
IMAP_HOST=imap.example.com
|
||||
IMAP_PORT=993
|
||||
IMAP_USERNAME=your_imap_username
|
||||
IMAP_PASSWORD=your_imap_password
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.env
|
||||
1051
Cargo.lock
generated
Normal file
1051
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "rs_pop_imap_importer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
dotenvy = "0.15.7"
|
||||
imap = "2.4.1"
|
||||
imap-proto = "0.16.6"
|
||||
native-tls = "0.2.14"
|
||||
rust-pop3-client = "0.2.2"
|
||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
# Build stage
|
||||
FROM rust:1.91 AS builder
|
||||
|
||||
WORKDIR /usr/src/pop_imap_importer
|
||||
|
||||
# Copy manifests first to leverage Docker layer caching
|
||||
COPY src/ Cargo.* ./
|
||||
|
||||
# Build the actual application
|
||||
RUN cargo build --release
|
||||
|
||||
RUN cargo install --path .
|
||||
|
||||
# Runtime stage
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install CA certificates and OpenSSL runtime libraries
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates libssl3 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /usr/local/cargo/bin/pop_imap_importer /usr/local/bin/pop_imap_importer
|
||||
|
||||
CMD ["pop_imap_importer"]
|
||||
|
||||
41
Makefile
Normal file
41
Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
# Makefile for building and running the POP3 to IMAP Importer Docker image
|
||||
|
||||
# Variables
|
||||
IMAGE_NAME = pop-imap-importer
|
||||
DOCKER_PLATFORM = linux/amd64
|
||||
|
||||
# Default target
|
||||
.PHONY: help
|
||||
help: ## Show this help message
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build the Docker image for linux/amd64 platform
|
||||
docker build --platform=$(DOCKER_PLATFORM) -t $(IMAGE_NAME) .
|
||||
|
||||
.PHONY: run
|
||||
run: ## Run the Docker container
|
||||
docker run --platform=$(DOCKER_PLATFORM) --rm -it $(IMAGE_NAME)
|
||||
|
||||
.PHONY: run-with-env
|
||||
run-with-env: ## Run the Docker container with a custom .env file
|
||||
docker run --platform=$(DOCKER_PLATFORM) --rm -it -v $(PWD)/.env:/app/.env $(IMAGE_NAME)
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Remove the Docker image
|
||||
docker rmi $(IMAGE_NAME)
|
||||
|
||||
.PHONY: push
|
||||
push: ## Push the Docker image to a registry (requires REPOSITORY variable)
|
||||
ifndef REPOSITORY
|
||||
$(error REPOSITORY is not set. Please set it with: make push REPOSITORY=your-repo/image-name)
|
||||
endif
|
||||
docker tag $(IMAGE_NAME) $(REPOSITORY):latest
|
||||
docker push $(REPOSITORY):latest
|
||||
|
||||
.PHONY: shell
|
||||
shell: ## Run a shell in the Docker container
|
||||
docker run --platform=$(DOCKER_PLATFORM) --rm -it $(IMAGE_NAME) sh
|
||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# POP to IMAP Importer
|
||||
|
||||
A Rust application that downloads emails from a POP3 server and imports them into an IMAP server's INBOX.
|
||||
|
||||
## Features
|
||||
|
||||
- Downloads all emails from a POP3 server
|
||||
- Imports emails to the INBOX of an IMAP server
|
||||
- Secure TLS connections
|
||||
- Environment-based configuration
|
||||
|
||||
## Setup
|
||||
|
||||
1. Copy `.env.example` to `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Edit `.env` with your server credentials:
|
||||
|
||||
```env
|
||||
# POP3 Source Server Configuration
|
||||
POP3_HOST=pop.example.com
|
||||
POP3_PORT=995
|
||||
POP3_USERNAME=your_pop3_username
|
||||
POP3_PASSWORD=your_pop3_password
|
||||
|
||||
# IMAP Destination Server Configuration
|
||||
IMAP_HOST=imap.example.com
|
||||
IMAP_PORT=993
|
||||
IMAP_USERNAME=your_imap_username
|
||||
IMAP_PASSWORD=your_imap_password
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Run the application:
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
3
src/config/mod.rs
Normal file
3
src/config/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod settings;
|
||||
|
||||
pub use settings::Settings;
|
||||
51
src/config/settings.rs
Normal file
51
src/config/settings.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use dotenvy::dotenv;
|
||||
use dotenvy::from_filename;
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pop3Config {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImapConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
pub pop3: Pop3Config,
|
||||
pub imap: ImapConfig,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_env_file(filename: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
if filename == ".env" {
|
||||
dotenv().ok();
|
||||
} else {
|
||||
from_filename(filename).ok();
|
||||
}
|
||||
|
||||
let pop3 = Pop3Config {
|
||||
host: env::var("POP3_HOST")?,
|
||||
port: env::var("POP3_PORT")?.parse()?,
|
||||
username: env::var("POP3_USERNAME")?,
|
||||
password: env::var("POP3_PASSWORD")?,
|
||||
};
|
||||
|
||||
let imap = ImapConfig {
|
||||
host: env::var("IMAP_HOST")?,
|
||||
port: env::var("IMAP_PORT")?.parse()?,
|
||||
username: env::var("IMAP_USERNAME")?,
|
||||
password: env::var("IMAP_PASSWORD")?,
|
||||
};
|
||||
|
||||
Ok(Settings { pop3, imap })
|
||||
}
|
||||
}
|
||||
43
src/imap_client/mod.rs
Normal file
43
src/imap_client/mod.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::config::settings::ImapConfig;
|
||||
use native_tls::TlsConnector;
|
||||
|
||||
pub struct ImapClient {
|
||||
session: Option<imap::Session<native_tls::TlsStream<std::net::TcpStream>>>,
|
||||
}
|
||||
|
||||
impl ImapClient {
|
||||
pub fn new(_config: &ImapConfig) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(ImapClient {
|
||||
session: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn login(&mut self, config: &ImapConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tls = TlsConnector::builder().build()?;
|
||||
let client = imap::connect((config.host.as_str(), config.port), &config.host, &tls)?;
|
||||
let session = client.login(&config.username, &config.password).map_err(|e| e.0)?;
|
||||
self.session = Some(session);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn select_inbox(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(ref mut session) = self.session {
|
||||
session.select("INBOX")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn append_message(&mut self, message: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(ref mut session) = self.session {
|
||||
session.append("INBOX", message.as_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn logout(self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(mut session) = self.session {
|
||||
session.logout()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
67
src/main.rs
Normal file
67
src/main.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
mod config;
|
||||
mod pop3_client;
|
||||
mod imap_client;
|
||||
|
||||
use clap::Parser;
|
||||
use config::Settings;
|
||||
use pop3_client::Pop3Client;
|
||||
use imap_client::ImapClient;
|
||||
|
||||
/// POP3 to IMAP Email Importer
|
||||
///
|
||||
/// This utility migrates emails from a POP3 server to an IMAP server.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the .env file containing server configurations
|
||||
#[clap(short, long, default_value = ".env")]
|
||||
env_file: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
println!("Starting POP3 to IMAP email importer...");
|
||||
|
||||
// Load configuration from specified .env file
|
||||
let settings = Settings::from_env_file(&args.env_file)?;
|
||||
|
||||
// Connect to POP3 server
|
||||
println!("Connecting to POP3 server at {}:{}...", settings.pop3.host, settings.pop3.port);
|
||||
let mut pop3_client = Pop3Client::new(&settings.pop3)?;
|
||||
pop3_client.login(&settings.pop3)?;
|
||||
println!("Successfully connected to POP3 server");
|
||||
|
||||
// Connect to IMAP server
|
||||
println!("Connecting to IMAP server at {}:{}...", settings.imap.host, settings.imap.port);
|
||||
let mut imap_client = ImapClient::new(&settings.imap)?;
|
||||
imap_client.login(&settings.imap)?;
|
||||
imap_client.select_inbox()?;
|
||||
println!("Successfully connected to IMAP server");
|
||||
|
||||
// List messages in POP3 inbox
|
||||
let messages = pop3_client.list_messages()?;
|
||||
println!("Found {} messages in POP3 inbox", messages.len());
|
||||
|
||||
// Process each message
|
||||
for (msg_id, _) in messages {
|
||||
println!("Processing message ID: {}", msg_id);
|
||||
|
||||
// Retrieve message content
|
||||
let message_content = pop3_client.retrieve_message(msg_id)?;
|
||||
|
||||
// Append message to IMAP inbox
|
||||
imap_client.append_message(&message_content)?;
|
||||
println!("Message {} imported successfully", msg_id);
|
||||
|
||||
// Optionally delete message from POP3 server after successful import
|
||||
// pop3_client.delete_message(msg_id)?;
|
||||
}
|
||||
|
||||
// Clean up connections
|
||||
pop3_client.quit()?;
|
||||
imap_client.logout()?;
|
||||
|
||||
println!("Email import completed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
43
src/pop3_client/mod.rs
Normal file
43
src/pop3_client/mod.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::config::settings::Pop3Config;
|
||||
use rust_pop3_client::Pop3Connection;
|
||||
|
||||
pub struct Pop3Client {
|
||||
connection: Pop3Connection,
|
||||
}
|
||||
|
||||
impl Pop3Client {
|
||||
pub fn new(config: &Pop3Config) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let connection = Pop3Connection::new(&config.host, config.port)?;
|
||||
Ok(Pop3Client { connection })
|
||||
}
|
||||
|
||||
pub fn login(&mut self, config: &Pop3Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.connection.login(&config.username, &config.password)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_messages(&mut self) -> Result<Vec<(u32, u32)>, Box<dyn std::error::Error>> {
|
||||
let infos = self.connection.list()?;
|
||||
let list = infos.into_iter().map(|info| (info.message_id, info.message_size)).collect();
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub fn retrieve_message(&mut self, msg_id: u32) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut buffer = Vec::new();
|
||||
self.connection.retrieve(msg_id, &mut buffer)?;
|
||||
let message = String::from_utf8(buffer)?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_message(&mut self, msg_id: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||
self.connection.delete(msg_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The rust-pop3-client doesn't seem to have an explicit quit method
|
||||
// The connection should be closed when the object is dropped
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user