V1.0: Class 1 fax — real-world 4-page send to external number confirmed
Core features: - Class 1 T.30 protocol: full send/receive implementation - HDLC: DLE-stuffing, FCS strip, USR5637 bit-reversal handling - T.4 MH encoder/decoder (1728px A4 standard) - Document pipeline: PDF (Ghostscript), PNG, TIFF input - Width clamping: US Letter 1734px → 1728px fax standard - Cover page: CJK rasterization (TW/CN/JP/EN), TIFF + HTML output - OCR verification: Tesseract 5 with eng+chi_tra, CJK space-tolerant - API server (axum): health, send, jobs, cover, retry, cancel - Background worker: auto-poll queue, speed fallback, retry policy - Modem detection, pool management Real-world test results (2026-07-23): - V90 → 25153038: 4 pages, V.17 12000 bps, 2:33 ✅ - USR5637 → 25153038: 4 pages, V.17 12000 bps, 2:26 ✅ - Both faxes confirmed received on remote machine Tested: loopback (100% pixel match), multi-page, all input formats, cover pages, OCR verify, API endpoints, worker processing. 13 unit tests pass, 0 new clippy warnings.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use lettre::{
|
||||
message::Message,
|
||||
transport::smtp::authentication::Credentials,
|
||||
SmtpTransport, Transport,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EmailConfig {
|
||||
pub smtp_host: String,
|
||||
pub smtp_port: u16,
|
||||
pub smtp_username: String,
|
||||
pub smtp_password: String,
|
||||
pub from_address: String,
|
||||
pub from_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub use_tls: bool,
|
||||
}
|
||||
|
||||
pub struct EmailSender {
|
||||
config: EmailConfig,
|
||||
}
|
||||
|
||||
impl EmailSender {
|
||||
pub fn new(config: EmailConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub fn send_email(
|
||||
&self,
|
||||
to: &str,
|
||||
subject: &str,
|
||||
body: &str,
|
||||
) -> crate::error::Result<()> {
|
||||
let email = Message::builder()
|
||||
.from(
|
||||
format!(
|
||||
"{} <{}>",
|
||||
self.config.from_name.as_deref().unwrap_or("Telfax"),
|
||||
self.config.from_address
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.to(to.parse().unwrap())
|
||||
.subject(subject)
|
||||
.body(body.to_string())
|
||||
.unwrap();
|
||||
|
||||
let creds = Credentials::new(
|
||||
self.config.smtp_username.clone(),
|
||||
self.config.smtp_password.clone(),
|
||||
);
|
||||
|
||||
let mailer = SmtpTransport::relay(&self.config.smtp_host)
|
||||
.unwrap()
|
||||
.credentials(creds)
|
||||
.port(self.config.smtp_port)
|
||||
.build();
|
||||
|
||||
mailer.send(&email)
|
||||
.map_err(|e| crate::error::FaxError::Other(format!("Failed to send email: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_success_notification(
|
||||
&self,
|
||||
to: &str,
|
||||
fax_number: &str,
|
||||
job_id: &str,
|
||||
) -> crate::error::Result<()> {
|
||||
let subject = format!("Fax sent successfully to {}", fax_number);
|
||||
let body = format!(
|
||||
"Your fax to {} has been sent successfully.\n\nJob ID: {}\n\nThis is an automated message from Telfax Fax Server.",
|
||||
fax_number, job_id
|
||||
);
|
||||
|
||||
self.send_email(to, &subject, &body)
|
||||
}
|
||||
|
||||
pub fn send_failure_notification(
|
||||
&self,
|
||||
to: &str,
|
||||
fax_number: &str,
|
||||
job_id: &str,
|
||||
error: &str,
|
||||
) -> crate::error::Result<()> {
|
||||
let subject = format!("Fax failed to {}", fax_number);
|
||||
let body = format!(
|
||||
"Your fax to {} has failed.\n\nJob ID: {}\nError: {}\n\nPlease check the fax number and try again.\n\nThis is an automated message from Telfax Fax Server.",
|
||||
fax_number, job_id, error
|
||||
);
|
||||
|
||||
self.send_email(to, &subject, &body)
|
||||
}
|
||||
|
||||
pub fn send_received_notification(
|
||||
&self,
|
||||
to: &str,
|
||||
from_number: &str,
|
||||
) -> crate::error::Result<()> {
|
||||
let subject = format!("Fax received from {}", from_number);
|
||||
let body = format!(
|
||||
"You have received a fax from {}.\n\nPlease check the fax server for the attachment.\n\nThis is an automated message from Telfax Fax Server.",
|
||||
from_number
|
||||
);
|
||||
|
||||
self.send_email(to, &subject, &body)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user