1use chrono::{DateTime, Utc};
2use cidr_map::CidrSet;
3use serde::{Deserialize, Serialize};
4use spool::SpoolId;
5use std::collections::HashMap;
6use std::time::Duration;
7use url::Url;
8use utoipa::{IntoParams, ToResponse, ToSchema};
9use uuid::Uuid;
10
11pub mod egress_path;
12pub mod rebind;
13pub mod shaping;
14pub mod tsa;
15
16#[derive(Serialize, Deserialize, Debug, ToSchema)]
20#[serde(deny_unknown_fields)]
21pub struct BounceV1Request {
22 #[serde(default)]
24 pub campaign: Option<String>,
25
26 #[serde(default)]
28 pub tenant: Option<String>,
29
30 #[serde(default)]
32 #[schema(example = "example.com")]
33 pub domain: Option<String>,
34
35 #[serde(default)]
37 pub routing_domain: Option<String>,
38
39 #[schema(example = "Cleaning up a bad send")]
44 pub reason: String,
45
46 #[serde(
50 default,
51 with = "duration_serde",
52 skip_serializing_if = "Option::is_none"
53 )]
54 #[schema(example = "20m")]
55 pub duration: Option<Duration>,
56
57 #[serde(default)]
60 pub suppress_logging: bool,
61
62 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub expires: Option<DateTime<Utc>>,
66}
67
68impl BounceV1Request {
69 pub fn duration(&self) -> Duration {
70 match &self.expires {
71 Some(exp) => (*exp - Utc::now()).to_std().unwrap_or(Duration::ZERO),
72 None => self.duration.unwrap_or_else(default_duration),
73 }
74 }
75}
76
77fn default_duration() -> Duration {
78 Duration::from_secs(300)
79}
80
81#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
82pub struct BounceV1Response {
83 #[schema(example = "552016f1-08e7-4e90-9da3-fd5c25acd069")]
86 pub id: Uuid,
87 #[schema(deprecated, example=json!({
97 "gmail.com": 200,
98 "yahoo.com": 100
99 }))]
100 pub bounced: HashMap<String, usize>,
101 #[schema(deprecated, example = 300)]
107 pub total_bounced: usize,
108}
109
110#[derive(Serialize, Deserialize, Debug, ToSchema)]
111pub struct SetDiagnosticFilterRequest {
112 #[schema(example = "kumod=trace")]
114 pub filter: String,
115}
116
117#[derive(Serialize, Deserialize, Debug, ToSchema)]
118pub struct BounceV1ListEntry {
119 #[schema(example = "552016f1-08e7-4e90-9da3-fd5c25acd069")]
124 pub id: Uuid,
125
126 #[serde(default)]
128 pub campaign: Option<String>,
129 #[serde(default)]
131 pub tenant: Option<String>,
132 #[serde(default)]
134 pub domain: Option<String>,
135 #[serde(default)]
137 pub routing_domain: Option<String>,
138
139 pub reason: String,
141
142 #[serde(with = "duration_serde")]
145 pub duration: Duration,
146
147 #[schema(example=json!({
150 "gmail.com": 200,
151 "yahoo.com": 100
152 }))]
153 pub bounced: HashMap<String, usize>,
154 pub total_bounced: usize,
157}
158
159#[derive(Serialize, Deserialize, Debug, ToSchema)]
160pub struct BounceV1CancelRequest {
161 pub id: Uuid,
162}
163
164#[derive(Serialize, Deserialize, Debug, ToSchema)]
165pub struct SuspendV1Request {
166 #[serde(default)]
168 pub campaign: Option<String>,
169 #[serde(default)]
171 pub tenant: Option<String>,
172 #[serde(default)]
174 pub domain: Option<String>,
175
176 #[schema(example = "pause while working on resolving a block with the destination postmaster")]
178 pub reason: String,
179
180 #[serde(
182 default,
183 with = "duration_serde",
184 skip_serializing_if = "Option::is_none"
185 )]
186 pub duration: Option<Duration>,
187
188 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub expires: Option<DateTime<Utc>>,
192}
193
194impl SuspendV1Request {
195 pub fn duration(&self) -> Duration {
196 match &self.expires {
197 Some(exp) => (*exp - Utc::now()).to_std().unwrap_or(Duration::ZERO),
198 None => self.duration.unwrap_or_else(default_duration),
199 }
200 }
201}
202
203#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
204pub struct SuspendV1Response {
205 pub id: Uuid,
208}
209
210#[derive(Serialize, Deserialize, Debug, ToSchema)]
211pub struct SuspendV1CancelRequest {
212 pub id: Uuid,
214}
215
216#[derive(Serialize, Deserialize, Debug, ToSchema)]
217pub struct SuspendV1ListEntry {
218 pub id: Uuid,
221
222 #[serde(default)]
224 pub campaign: Option<String>,
225 #[serde(default)]
227 pub tenant: Option<String>,
228 #[serde(default)]
230 pub domain: Option<String>,
231
232 #[schema(example = "pause while working on resolving a deliverability issue")]
234 pub reason: String,
235
236 #[serde(with = "duration_serde")]
237 pub duration: Duration,
239}
240
241#[derive(Serialize, Deserialize, Debug, ToSchema)]
242pub struct SuspendReadyQueueV1Request {
243 pub name: String,
245 #[schema(example = "pause while working on resolving a block with the destination postmaster")]
247 pub reason: String,
248 #[serde(
250 default,
251 with = "duration_serde",
252 skip_serializing_if = "Option::is_none"
253 )]
254 pub duration: Option<Duration>,
255
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub expires: Option<DateTime<Utc>>,
258}
259
260impl SuspendReadyQueueV1Request {
261 pub fn duration(&self) -> Duration {
262 if let Some(expires) = &self.expires {
263 let duration = expires.signed_duration_since(Utc::now());
264 duration.to_std().unwrap_or(Duration::ZERO)
265 } else {
266 self.duration.unwrap_or_else(default_duration)
267 }
268 }
269}
270
271#[derive(Serialize, Deserialize, Debug, ToSchema)]
272pub struct SuspendReadyQueueV1ListEntry {
273 pub id: Uuid,
275 pub name: String,
277 #[schema(example = "pause while working on resolving a block with the destination postmaster")]
279 pub reason: String,
280
281 #[serde(with = "duration_serde")]
283 pub duration: Duration,
284
285 pub expires: DateTime<Utc>,
287}
288
289#[derive(Serialize, Deserialize, Debug, IntoParams)]
290pub struct InspectMessageV1Request {
291 pub id: SpoolId,
294 #[serde(default)]
297 pub want_body: bool,
298}
299
300impl InspectMessageV1Request {
301 pub fn apply_to_url(&self, url: &mut Url) {
302 let mut query = url.query_pairs_mut();
303 query.append_pair("id", &self.id.to_string());
304 if self.want_body {
305 query.append_pair("want_body", "true");
306 }
307 }
308}
309
310#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
311pub struct InspectMessageV1Response {
312 pub id: SpoolId,
314 pub message: MessageInformation,
316}
317
318#[derive(Serialize, Deserialize, Debug, IntoParams)]
319pub struct InspectQueueV1Request {
320 pub queue_name: String,
322 #[serde(default)]
325 pub want_body: bool,
326
327 #[serde(default)]
333 pub limit: Option<usize>,
334}
335
336impl InspectQueueV1Request {
337 pub fn apply_to_url(&self, url: &mut Url) {
338 let mut query = url.query_pairs_mut();
339 query.append_pair("queue_name", &self.queue_name.to_string());
340 if self.want_body {
341 query.append_pair("want_body", "true");
342 }
343 if let Some(limit) = self.limit {
344 query.append_pair("limit", &limit.to_string());
345 }
346 }
347}
348
349#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
350pub struct InspectQueueV1Response {
351 pub queue_name: String,
352 pub messages: Vec<InspectMessageV1Response>,
353 pub num_scheduled: usize,
354 pub queue_config: serde_json::Value,
355 pub delayed_metric: usize,
356 pub now: DateTime<Utc>,
357 pub last_changed: DateTime<Utc>,
358}
359
360#[derive(Serialize, Deserialize, Debug, ToSchema)]
361pub struct MessageInformation {
362 #[schema(example = "sender@sender.example.com")]
364 pub sender: String,
365 #[schema(example = "recipient@example.com")]
367 pub recipient: String,
368 #[schema(example=json!({
370 "received_from": "10.0.0.1:3488"
371 }))]
372 pub meta: serde_json::Value,
373 #[serde(default)]
376 pub data: Option<String>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub due: Option<DateTime<Utc>>,
379 #[serde(skip_serializing_if = "Option::is_none")]
380 pub num_attempts: Option<u16>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 pub scheduling: Option<serde_json::Value>,
383}
384
385#[derive(Serialize, Deserialize, Debug, ToSchema)]
386pub struct TraceSmtpV1Request {
387 #[serde(default)]
388 pub source_addr: Option<CidrSet>,
389
390 #[serde(default, skip_serializing_if = "is_false")]
391 pub terse: bool,
392}
393
394fn is_false(b: &bool) -> bool {
395 !b
396}
397
398#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
399pub struct TraceSmtpV1Event {
400 pub conn_meta: serde_json::Value,
401 pub payload: TraceSmtpV1Payload,
402 pub when: DateTime<Utc>,
403}
404
405#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq)]
406pub enum TraceSmtpV1Payload {
407 Connected,
408 Closed,
409 Read(String),
410 Write(String),
411 Diagnostic {
412 level: String,
413 message: String,
414 },
415 Callback {
416 name: String,
417 result: Option<serde_json::Value>,
418 error: Option<String>,
419 },
420 MessageDisposition {
421 relay: bool,
422 log_arf: bool,
423 log_oob: bool,
424 queue: String,
425 meta: serde_json::Value,
426 sender: String,
427 recipient: String,
428 id: SpoolId,
429 },
430 AbbreviatedRead {
432 snippet: String,
434 len: usize,
436 },
437}
438
439#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
440pub struct TraceSmtpClientV1Event {
441 pub conn_meta: serde_json::Value,
442 pub payload: TraceSmtpClientV1Payload,
443 pub when: DateTime<Utc>,
444}
445
446#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq)]
447pub enum TraceSmtpClientV1Payload {
448 BeginSession,
449 Connected,
450 Closed,
451 Read(String),
452 Write(String),
453 Diagnostic {
454 level: String,
455 message: String,
456 },
457 MessageObtained,
458 AbbreviatedWrite {
460 snippet: String,
462 len: usize,
464 },
465}
466
467#[derive(Serialize, Deserialize, Debug, ToSchema)]
468pub struct TraceSmtpClientV1Request {
469 #[serde(default)]
471 pub campaign: Vec<String>,
472
473 #[serde(default)]
475 pub tenant: Vec<String>,
476
477 #[serde(default)]
479 #[schema(example = "example.com")]
480 pub domain: Vec<String>,
481
482 #[serde(default)]
484 pub routing_domain: Vec<String>,
485
486 #[serde(default)]
488 pub egress_pool: Vec<String>,
489
490 #[serde(default)]
492 pub egress_source: Vec<String>,
493
494 #[serde(default)]
496 pub mail_from: Vec<String>,
497
498 #[serde(default)]
500 pub rcpt_to: Vec<String>,
501
502 #[serde(default)]
504 pub source_addr: Option<CidrSet>,
505
506 #[serde(default)]
508 pub mx_host: Vec<String>,
509
510 #[serde(default)]
512 pub ready_queue: Vec<String>,
513
514 #[serde(default)]
516 pub mx_addr: Option<CidrSet>,
517
518 #[serde(default, skip_serializing_if = "is_false")]
521 pub terse: bool,
522}
523
524#[derive(Serialize, Deserialize, Debug, ToSchema, IntoParams)]
525pub struct ReadyQueueStateRequest {
526 #[serde(default)]
528 pub queues: Vec<String>,
529}
530
531#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
532pub struct QueueState {
533 pub context: String,
534 pub since: DateTime<Utc>,
535}
536
537#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
538pub struct ReadyQueueStateResponse {
539 pub states_by_ready_queue: HashMap<String, HashMap<String, QueueState>>,
540}