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
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.
/// Main asynchronous entry point for the application. Runs the gateway using
/// the configuration selected on the command line.
///
/// Parses command-line arguments, loads configuration, and starts the SMTP
/// server.
/// 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 configuration is invalid, files are inaccessible, or
/// server fails to start.
/// 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.exists() {
	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
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, or invalid types."
			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
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.
	/// 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")?
		{
107
108
109
110
111
112
113
114


115
116
117
118
119
120
121
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<()>` - Success or error.
	/// * `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(