Check-in [e678fcdc39]
Logged in as anonymous
Overview
Comment:clarify docs, change file check
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: e678fcdc3980ab972a3eda1e3b9dcdd8ebd08674ef8b6c89854816fc41a2f10c
User & Date: arcade on 2026-09-12 20:00:11.244
Other Links: manifest | tags
Context
2026-09-12
20:01
fix doc ident check-in: b3ca5f23ee user: arcade tags: trunk
20:00
clarify docs, change file check check-in: e678fcdc39 user: arcade tags: trunk
2026-09-11
19:42
clean Cargo, rewrite telegram support check-in: d711370ce3 user: arcade tags: trunk
Changes
35
36
37
38
39
40
41
42

43
44
45
46
47
48

49


50
51
52
53
54
55
56
57
58
59
60
#[command(about = format!("SMTP-to-Telegram gateway v{}, (C) 2024 - 2026", env!("CARGO_PKG_VERSION")), long_about = None)]
struct Args {
	/// Set configuration file location
	#[arg(short, long, default_value = "smtp2tg.toml")]
	config: String,
}

/// Main asynchronous entry point for the application.

///
/// Parses command-line arguments, loads configuration, and starts the SMTP
/// server.
///
/// # Errors
/// Returns an error if configuration is invalid, files are inaccessible, or

/// server fails to start.


pub async fn async_main () -> Result<()> {
	let args = Args::parse();
	let config_file = Path::new(&args.config);
	if !config_file.exists() {
		bail!("Configuration file not found: {config_file:?}\n\
			Hint: Ensure the file exists and the path is correct.");
	};
	{
		let meta = metadata(config_file).await.stack()?;
		if (!0o100600 & meta.permissions().mode()) > 0 {
			bail!("Configuration file permissions are insecure {config_file:?}\n\







|
>

|
|


|
>
|
>
>



|







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#[command(about = format!("SMTP-to-Telegram gateway v{}, (C) 2024 - 2026", env!("CARGO_PKG_VERSION")), long_about = None)]
struct Args {
	/// Set configuration file location
	#[arg(short, long, default_value = "smtp2tg.toml")]
	config: String,
}

/// Main asynchronous entry point for the application. Runs the gateway using
/// the configuration selected on the command line.
///
/// The configuration file must use owner-only permissions. Once configured,
/// the SMTP server runs until it stops or encounters an error.
///
/// # Errors
/// Returns an error if the configuration file is missing, inaccessible,
/// insecure, malformed, or contains invalid required settings.
///
/// # Panics
/// Panics if configuring or serving the SMTP server fails.
pub async fn async_main () -> Result<()> {
	let args = Args::parse();
	let config_file = Path::new(&args.config);
	if !config_file.try_exists().stack()? {
		bail!("Configuration file not found: {config_file:?}\n\
			Hint: Ensure the file exists and the path is correct.");
	};
	{
		let meta = metadata(config_file).await.stack()?;
		if (!0o100600 & meta.permissions().mode()) > 0 {
			bail!("Configuration file permissions are insecure {config_file:?}\n\
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
			hostname::get().context("Failed to get current hostname")?
			.to_str().context("Can't convert hostname to string, bad UTF-8?")?]).stack()?
		.add_source(config::File::from(config_file))
		.build()
		.with_context(|| format!(
			"Failed to parse configuration file: {config_file:?}\n\
			Check syntax against smtp2tg.toml.example.\n\
			Common issues: missing quotes, trailing commas, or invalid types."
		))?;

	let listen_on = settings.get_string("listen_on").stack()?;
	let server_name = settings.get_string("hostname").stack()?;
	let core = MailServer::new(settings)?;
	let mut server = mailin_embedded::Server::new(core);








|







76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
			hostname::get().context("Failed to get current hostname")?
			.to_str().context("Can't convert hostname to string, bad UTF-8?")?]).stack()?
		.add_source(config::File::from(config_file))
		.build()
		.with_context(|| format!(
			"Failed to parse configuration file: {config_file:?}\n\
			Check syntax against smtp2tg.toml.example.\n\
			Common issues: missing quotes, trailing commas in inline tables, or invalid types."
		))?;

	let listen_on = settings.get_string("listen_on").stack()?;
	let server_name = settings.get_string("hostname").stack()?;
	let core = MailServer::new(settings)?;
	let mut server = mailin_embedded::Server::new(core);

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
	/// validates all required configuration values.
	///
	/// # Arguments
	/// * `settings` - Parsed application configuration.
	///
	/// # Errors
	/// Returns an error if required configuration values are missing or invalid.
	/// server fails to start.
	pub fn new (settings: config::Config) -> Result<MailServer> {
		let api_key = settings.get_string("api_key")
			.context("[smtp2tg.toml] missing \"api_key\" parameter.\n")?;
		let mut recipients = HashMap::new();
		for (name, value) in settings.get_table("recipients")
			.context("[smtp2tg.toml] missing table \"recipients\".\n")?
		{







<







66
67
68
69
70
71
72

73
74
75
76
77
78
79
	/// validates all required configuration values.
	///
	/// # Arguments
	/// * `settings` - Parsed application configuration.
	///
	/// # Errors
	/// Returns an error if required configuration values are missing or invalid.

	pub fn new (settings: config::Config) -> Result<MailServer> {
		let api_key = settings.get_string("api_key")
			.context("[smtp2tg.toml] missing \"api_key\" parameter.\n")?;
		let mut recipients = HashMap::new();
		for (name, value) in settings.get_table("recipients")
			.context("[smtp2tg.toml] missing table \"recipients\".\n")?
		{
107
108
109
110
111
112
113
114

115
116
117
118
119
120
121
	///
	/// # Arguments
	/// * `to` - Target chat ID.
	/// * `media` - List of attachments, non-empty.
	/// * `msg` - Message text (supports HTML formatting).
	///
	/// # Returns
	/// * `Result<()>` - Success or error.

	pub async fn sendgroup (&self, to: &ChatPeerId, media: Vec<Attachment>, msg: &str) -> Result<()> {
		if media.len() > 1 {
			let mut attach = vec![];
			let mut pos = media.len();
			for file in media {
				let mut doc = InputMediaDocument::from(
					InputFile::from(







|
>







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
	///
	/// # Arguments
	/// * `to` - Target chat ID.
	/// * `media` - List of attachments, non-empty.
	/// * `msg` - Message text (supports HTML formatting).
	///
	/// # Returns
	/// * `Result<()>` - fails if `media` is empty, problems forming media
	/// group, request fails.
	pub async fn sendgroup (&self, to: &ChatPeerId, media: Vec<Attachment>, msg: &str) -> Result<()> {
		if media.len() > 1 {
			let mut attach = vec![];
			let mut pos = media.len();
			for file in media {
				let mut doc = InputMediaDocument::from(
					InputFile::from(