1use chrono::{DateTime, Utc};
2use cidr_map::CidrSet;
3use serde::{Deserialize, Serialize};
4use serde_with::formats::PreferOne;
5use serde_with::{serde_as, OneOrMany};
6use spool::SpoolId;
7use std::collections::HashMap;
8use std::time::Duration;
9use url::Url;
10use utoipa::{IntoParams, ToResponse, ToSchema};
11use uuid::Uuid;
12
13pub mod egress_path;
14pub mod rebind;
15pub mod shaping;
16pub mod tsa;
17pub mod xfer;
18
19#[derive(Serialize, Deserialize, Debug, ToSchema)]
23#[serde(deny_unknown_fields)]
24pub struct BounceV1Request {
25    #[serde(default)]
27    pub campaign: Option<String>,
28
29    #[serde(default)]
31    pub tenant: Option<String>,
32
33    #[serde(default)]
35    #[schema(example = "example.com")]
36    pub domain: Option<String>,
37
38    #[serde(default)]
40    pub routing_domain: Option<String>,
41
42    #[schema(example = "Cleaning up a bad send")]
47    pub reason: String,
48
49    #[serde(
53        default,
54        with = "duration_serde",
55        skip_serializing_if = "Option::is_none"
56    )]
57    #[schema(example = "20m")]
58    pub duration: Option<Duration>,
59
60    #[serde(default)]
63    pub suppress_logging: bool,
64
65    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub expires: Option<DateTime<Utc>>,
69
70    #[serde(default, skip_serializing_if = "Vec::is_empty")]
74    pub queue_names: Vec<String>,
75}
76
77impl BounceV1Request {
78    pub fn duration(&self) -> Duration {
79        match &self.expires {
80            Some(exp) => (*exp - Utc::now()).to_std().unwrap_or(Duration::ZERO),
81            None => self.duration.unwrap_or_else(default_duration),
82        }
83    }
84}
85
86fn default_duration() -> Duration {
87    Duration::from_secs(300)
88}
89
90#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
91pub struct BounceV1Response {
92    #[schema(example = "552016f1-08e7-4e90-9da3-fd5c25acd069")]
95    pub id: Uuid,
96    #[schema(deprecated, example=json!({
106        "gmail.com": 200,
107        "yahoo.com": 100
108    }))]
109    pub bounced: HashMap<String, usize>,
110    #[schema(deprecated, example = 300)]
116    pub total_bounced: usize,
117}
118
119#[derive(Serialize, Deserialize, Debug, ToSchema)]
120pub struct SetDiagnosticFilterRequest {
121    #[schema(example = "kumod=trace")]
123    pub filter: String,
124}
125
126#[derive(Serialize, Deserialize, Debug, ToSchema)]
127pub struct BounceV1ListEntry {
128    #[schema(example = "552016f1-08e7-4e90-9da3-fd5c25acd069")]
133    pub id: Uuid,
134
135    #[serde(default)]
137    pub campaign: Option<String>,
138    #[serde(default)]
140    pub tenant: Option<String>,
141    #[serde(default)]
143    pub domain: Option<String>,
144    #[serde(default)]
146    pub routing_domain: Option<String>,
147
148    pub reason: String,
150
151    #[serde(with = "duration_serde")]
154    pub duration: Duration,
155
156    #[schema(example=json!({
159        "gmail.com": 200,
160        "yahoo.com": 100
161    }))]
162    pub bounced: HashMap<String, usize>,
163    pub total_bounced: usize,
166}
167
168#[derive(Serialize, Deserialize, Debug, ToSchema)]
169pub struct BounceV1CancelRequest {
170    pub id: Uuid,
171}
172
173#[derive(Serialize, Deserialize, Debug, ToSchema)]
174pub struct SuspendV1Request {
175    #[serde(default)]
177    pub campaign: Option<String>,
178    #[serde(default)]
180    pub tenant: Option<String>,
181    #[serde(default)]
183    pub domain: Option<String>,
184
185    #[schema(example = "pause while working on resolving a block with the destination postmaster")]
187    pub reason: String,
188
189    #[serde(
191        default,
192        with = "duration_serde",
193        skip_serializing_if = "Option::is_none"
194    )]
195    pub duration: Option<Duration>,
196
197    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub expires: Option<DateTime<Utc>>,
201
202    #[serde(default, skip_serializing_if = "Vec::is_empty")]
206    pub queue_names: Vec<String>,
207}
208
209impl SuspendV1Request {
210    pub fn duration(&self) -> Duration {
211        match &self.expires {
212            Some(exp) => (*exp - Utc::now()).to_std().unwrap_or(Duration::ZERO),
213            None => self.duration.unwrap_or_else(default_duration),
214        }
215    }
216}
217
218#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
219pub struct SuspendV1Response {
220    pub id: Uuid,
223}
224
225#[derive(Serialize, Deserialize, Debug, ToSchema)]
226pub struct SuspendV1CancelRequest {
227    pub id: Uuid,
229}
230
231#[derive(Serialize, Deserialize, Debug, ToSchema)]
232pub struct SuspendV1ListEntry {
233    pub id: Uuid,
236
237    #[serde(default)]
239    pub campaign: Option<String>,
240    #[serde(default)]
242    pub tenant: Option<String>,
243    #[serde(default)]
245    pub domain: Option<String>,
246
247    #[schema(example = "pause while working on resolving a deliverability issue")]
249    pub reason: String,
250
251    #[serde(with = "duration_serde")]
252    pub duration: Duration,
254}
255
256#[derive(Serialize, Deserialize, Debug, ToSchema)]
257pub struct SuspendReadyQueueV1Request {
258    pub name: String,
260    #[schema(example = "pause while working on resolving a block with the destination postmaster")]
262    pub reason: String,
263    #[serde(
265        default,
266        with = "duration_serde",
267        skip_serializing_if = "Option::is_none"
268    )]
269    pub duration: Option<Duration>,
270
271    #[serde(default, skip_serializing_if = "Option::is_none")]
272    pub expires: Option<DateTime<Utc>>,
273}
274
275impl SuspendReadyQueueV1Request {
276    pub fn duration(&self) -> Duration {
277        if let Some(expires) = &self.expires {
278            let duration = expires.signed_duration_since(Utc::now());
279            duration.to_std().unwrap_or(Duration::ZERO)
280        } else {
281            self.duration.unwrap_or_else(default_duration)
282        }
283    }
284}
285
286#[derive(Serialize, Deserialize, Debug, ToSchema)]
287pub struct SuspendReadyQueueV1ListEntry {
288    pub id: Uuid,
290    pub name: String,
292    #[schema(example = "pause while working on resolving a block with the destination postmaster")]
294    pub reason: String,
295
296    #[serde(with = "duration_serde")]
298    pub duration: Duration,
299
300    pub expires: DateTime<Utc>,
302}
303
304#[derive(Serialize, Deserialize, Debug, IntoParams)]
305pub struct InspectMessageV1Request {
306    pub id: SpoolId,
309    #[serde(default)]
312    pub want_body: bool,
313}
314
315impl InspectMessageV1Request {
316    pub fn apply_to_url(&self, url: &mut Url) {
317        let mut query = url.query_pairs_mut();
318        query.append_pair("id", &self.id.to_string());
319        if self.want_body {
320            query.append_pair("want_body", "true");
321        }
322    }
323}
324
325#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
326pub struct InspectMessageV1Response {
327    pub id: SpoolId,
329    pub message: MessageInformation,
331}
332
333#[derive(Serialize, Deserialize, Debug, IntoParams)]
334pub struct InspectQueueV1Request {
335    pub queue_name: String,
337    #[serde(default)]
340    pub want_body: bool,
341
342    #[serde(default)]
348    pub limit: Option<usize>,
349}
350
351impl InspectQueueV1Request {
352    pub fn apply_to_url(&self, url: &mut Url) {
353        let mut query = url.query_pairs_mut();
354        query.append_pair("queue_name", &self.queue_name.to_string());
355        if self.want_body {
356            query.append_pair("want_body", "true");
357        }
358        if let Some(limit) = self.limit {
359            query.append_pair("limit", &limit.to_string());
360        }
361    }
362}
363
364#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
365pub struct InspectQueueV1Response {
366    pub queue_name: String,
367    pub messages: Vec<InspectMessageV1Response>,
368    pub num_scheduled: usize,
369    pub queue_config: serde_json::Value,
370    pub delayed_metric: usize,
371    pub now: DateTime<Utc>,
372    pub last_changed: DateTime<Utc>,
373}
374
375#[serde_as]
376#[derive(Serialize, Deserialize, Debug, ToSchema)]
377pub struct MessageInformation {
378    #[schema(example = "sender@sender.example.com")]
380    pub sender: String,
381    #[schema(example = "recipient@example.com")]
383    #[serde_as(as = "OneOrMany<_, PreferOne>")] pub recipient: Vec<String>,
385    #[schema(example=json!({
387        "received_from": "10.0.0.1:3488"
388    }))]
389    pub meta: serde_json::Value,
390    #[serde(default)]
393    pub data: Option<String>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub due: Option<DateTime<Utc>>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub num_attempts: Option<u16>,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub scheduling: Option<serde_json::Value>,
400}
401
402#[derive(Serialize, Deserialize, Debug, ToSchema)]
403pub struct TraceSmtpV1Request {
404    #[serde(default)]
405    #[schema(value_type=Option<Vec<String>>)]
406    pub source_addr: Option<CidrSet>,
407
408    #[serde(default, skip_serializing_if = "is_false")]
409    pub terse: bool,
410}
411
412fn is_false(b: &bool) -> bool {
413    !b
414}
415
416#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
417pub struct TraceSmtpV1Event {
418    pub conn_meta: serde_json::Value,
419    pub payload: TraceSmtpV1Payload,
420    pub when: DateTime<Utc>,
421}
422
423#[serde_as]
424#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq)]
425pub enum TraceSmtpV1Payload {
426    Connected,
427    Closed,
428    Read(String),
429    Write(String),
430    Diagnostic {
431        level: String,
432        message: String,
433    },
434    Callback {
435        name: String,
436        result: Option<serde_json::Value>,
437        error: Option<String>,
438    },
439    MessageDisposition {
440        relay: bool,
441        log_arf: serde_json::Value,
442        log_oob: serde_json::Value,
443        queue: String,
444        meta: serde_json::Value,
445        sender: String,
446        #[serde_as(as = "OneOrMany<_, PreferOne>")] recipient: Vec<String>,
448        id: SpoolId,
449        #[serde(default)]
450        was_arf_or_oob: Option<bool>,
451        #[serde(default)]
452        will_enqueue: Option<bool>,
453    },
454    AbbreviatedRead {
456        snippet: String,
458        len: usize,
460    },
461}
462
463#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
464pub struct TraceSmtpClientV1Event {
465    pub conn_meta: serde_json::Value,
466    pub payload: TraceSmtpClientV1Payload,
467    pub when: DateTime<Utc>,
468}
469
470#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq)]
471pub enum TraceSmtpClientV1Payload {
472    BeginSession,
473    Connected,
474    Closed,
475    Read(String),
476    Write(String),
477    Diagnostic {
478        level: String,
479        message: String,
480    },
481    MessageObtained,
482    AbbreviatedWrite {
484        snippet: String,
486        len: usize,
488    },
489}
490
491#[derive(Serialize, Deserialize, Debug, ToSchema)]
492pub struct TraceSmtpClientV1Request {
493    #[serde(default)]
495    pub campaign: Vec<String>,
496
497    #[serde(default)]
499    pub tenant: Vec<String>,
500
501    #[serde(default)]
503    #[schema(example = "example.com")]
504    pub domain: Vec<String>,
505
506    #[serde(default)]
508    pub routing_domain: Vec<String>,
509
510    #[serde(default)]
512    pub egress_pool: Vec<String>,
513
514    #[serde(default)]
516    pub egress_source: Vec<String>,
517
518    #[serde(default)]
520    pub mail_from: Vec<String>,
521
522    #[serde(default)]
524    pub rcpt_to: Vec<String>,
525
526    #[serde(default)]
528    #[schema(value_type=Option<Vec<String>>)]
529    pub source_addr: Option<CidrSet>,
530
531    #[serde(default)]
533    pub mx_host: Vec<String>,
534
535    #[serde(default)]
537    pub ready_queue: Vec<String>,
538
539    #[serde(default)]
541    #[schema(value_type=Option<Vec<String>>)]
542    pub mx_addr: Option<CidrSet>,
543
544    #[serde(default, skip_serializing_if = "is_false")]
547    pub terse: bool,
548}
549
550#[derive(Serialize, Deserialize, Debug, ToSchema, IntoParams)]
551pub struct ReadyQueueStateRequest {
552    #[serde(default)]
554    pub queues: Vec<String>,
555}
556
557#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
558pub struct QueueState {
559    pub context: String,
560    pub since: DateTime<Utc>,
561}
562
563#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
564pub struct ReadyQueueStateResponse {
565    pub states_by_ready_queue: HashMap<String, HashMap<String, QueueState>>,
566}