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}