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)]
31#[serde(deny_unknown_fields)]
32pub struct BounceV1Request {
33 #[serde(default)]
35 #[schema(example = "campaign_name")]
36 pub campaign: Option<String>,
37
38 #[serde(default)]
40 #[schema(example = "tenant_name")]
41 pub tenant: Option<String>,
42
43 #[serde(default)]
45 #[schema(example = "example.com")]
46 pub domain: Option<String>,
47
48 #[serde(default)]
51 #[schema(example = "routing_domain.com")]
52 pub routing_domain: Option<String>,
53
54 #[schema(example = "Cleaning up a bad send")]
59 pub reason: String,
60
61 #[serde(
65 default,
66 with = "duration_serde",
67 skip_serializing_if = "Option::is_none"
68 )]
69 #[schema(example = "20m")]
70 pub duration: Option<Duration>,
71
72 #[serde(default)]
75 #[schema(default = false)]
76 pub suppress_logging: bool,
77
78 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub expires: Option<DateTime<Utc>>,
82
83 #[serde(default, skip_serializing_if = "Vec::is_empty")]
87 #[schema(example=json!(["campaign_name:tenant_name@example.com"]))]
88 pub queue_names: Vec<String>,
89}
90
91impl BounceV1Request {
92 pub fn duration(&self) -> Duration {
93 match &self.expires {
94 Some(exp) => (*exp - Utc::now()).to_std().unwrap_or(Duration::ZERO),
95 None => self.duration.unwrap_or_else(default_duration),
96 }
97 }
98}
99
100fn default_duration() -> Duration {
101 Duration::from_secs(300)
102}
103
104#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
105pub struct BounceV1Response {
106 #[schema(example = "552016f1-08e7-4e90-9da3-fd5c25acd069")]
109 pub id: Uuid,
110 #[schema(deprecated, example=json!({
120 "gmail.com": 200,
121 "yahoo.com": 100
122 }))]
123 pub bounced: HashMap<String, usize>,
124 #[schema(deprecated, example = 300)]
130 pub total_bounced: usize,
131}
132
133#[derive(Serialize, Deserialize, Debug, ToSchema)]
134pub struct SetDiagnosticFilterRequest {
135 #[schema(example = "kumod=trace")]
137 pub filter: String,
138}
139
140#[derive(Serialize, Deserialize, Debug, ToSchema)]
141pub struct BounceV1ListEntry {
142 #[schema(example = "552016f1-08e7-4e90-9da3-fd5c25acd069")]
147 pub id: Uuid,
148
149 #[serde(default)]
151 #[schema(example = "campaign_name")]
152 pub campaign: Option<String>,
153 #[serde(default)]
155 #[schema(example = "tenant_name")]
156 pub tenant: Option<String>,
157 #[serde(default)]
159 #[schema(example = "example.com")]
160 pub domain: Option<String>,
161 #[serde(default)]
163 #[schema(example = "routing_domain.com")]
164 pub routing_domain: Option<String>,
165
166 #[schema(example = "cleaning up a bad send")]
168 pub reason: String,
169
170 #[serde(with = "duration_serde")]
173 pub duration: Duration,
174
175 #[schema(example=json!({
178 "gmail.com": 200,
179 "yahoo.com": 100
180 }))]
181 pub bounced: HashMap<String, usize>,
182 pub total_bounced: usize,
185}
186
187#[derive(Serialize, Deserialize, Debug, ToSchema)]
188pub struct BounceV1CancelRequest {
189 pub id: Uuid,
190}
191
192#[derive(Serialize, Deserialize, Debug, ToSchema)]
193pub struct SuspendV1Request {
194 #[serde(default)]
196 #[schema(example = "campaign_name")]
197 pub campaign: Option<String>,
198 #[serde(default)]
200 #[schema(example = "tenant_name")]
201 pub tenant: Option<String>,
202 #[serde(default)]
204 #[schema(example = "example.com")]
205 pub domain: Option<String>,
206
207 #[schema(example = "pause while working on resolving a block with the destination postmaster")]
209 pub reason: String,
210
211 #[serde(
213 default,
214 with = "duration_serde",
215 skip_serializing_if = "Option::is_none"
216 )]
217 pub duration: Option<Duration>,
218
219 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub expires: Option<DateTime<Utc>>,
223
224 #[serde(default, skip_serializing_if = "Vec::is_empty")]
228 #[schema(example=json!(["campaign_name:tenant_name@example.com"]))]
229 pub queue_names: Vec<String>,
230}
231
232impl SuspendV1Request {
233 pub fn duration(&self) -> Duration {
234 match &self.expires {
235 Some(exp) => (*exp - Utc::now()).to_std().unwrap_or(Duration::ZERO),
236 None => self.duration.unwrap_or_else(default_duration),
237 }
238 }
239}
240
241#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
242pub struct SuspendV1Response {
243 pub id: Uuid,
246}
247
248#[derive(Serialize, Deserialize, Debug, ToSchema)]
249pub struct SuspendV1CancelRequest {
250 pub id: Uuid,
252}
253
254#[derive(Serialize, Deserialize, Debug, ToSchema)]
255pub struct SuspendV1ListEntry {
256 pub id: Uuid,
259
260 #[serde(default)]
262 #[schema(example = "campaign_name")]
263 pub campaign: Option<String>,
264 #[serde(default)]
266 #[schema(example = "tenant_name")]
267 pub tenant: Option<String>,
268 #[serde(default)]
270 #[schema(example = "example.com")]
271 pub domain: Option<String>,
272
273 #[schema(example = "pause while working on resolving a deliverability issue")]
275 pub reason: String,
276
277 #[serde(with = "duration_serde")]
278 pub duration: Duration,
280}
281
282#[derive(Serialize, Deserialize, Debug, ToSchema)]
283pub struct SuspendReadyQueueV1Request {
284 #[schema(
286 example = "source_name->(alt1|alt2|alt3|alt4)?.gmail-smtp-in.l.google.com@smtp_client"
287 )]
288 pub name: String,
289 #[schema(example = "pause while working on resolving a block with the destination postmaster")]
291 pub reason: String,
292 #[serde(
294 default,
295 with = "duration_serde",
296 skip_serializing_if = "Option::is_none"
297 )]
298 pub duration: Option<Duration>,
299
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub expires: Option<DateTime<Utc>>,
302}
303
304impl SuspendReadyQueueV1Request {
305 pub fn duration(&self) -> Duration {
306 if let Some(expires) = &self.expires {
307 let duration = expires.signed_duration_since(Utc::now());
308 duration.to_std().unwrap_or(Duration::ZERO)
309 } else {
310 self.duration.unwrap_or_else(default_duration)
311 }
312 }
313}
314
315#[derive(Serialize, Deserialize, Debug, ToSchema)]
316pub struct SuspendReadyQueueV1ListEntry {
317 pub id: Uuid,
319 #[schema(
321 example = "source_name->(alt1|alt2|alt3|alt4)?.gmail-smtp-in.l.google.com@smtp_client"
322 )]
323 pub name: String,
324 #[schema(example = "pause while working on resolving a block with the destination postmaster")]
326 pub reason: String,
327
328 #[serde(with = "duration_serde")]
330 pub duration: Duration,
331
332 pub expires: DateTime<Utc>,
334}
335
336#[derive(Serialize, Deserialize, Debug, IntoParams, ToSchema)]
337pub struct InspectMessageV1Request {
338 pub id: SpoolId,
341 #[serde(default)]
344 pub want_body: bool,
345}
346
347impl InspectMessageV1Request {
348 pub fn apply_to_url(&self, url: &mut Url) {
349 let mut query = url.query_pairs_mut();
350 query.append_pair("id", &self.id.to_string());
351 if self.want_body {
352 query.append_pair("want_body", "true");
353 }
354 }
355}
356
357#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
358pub struct InspectMessageV1Response {
359 pub id: SpoolId,
361 pub message: MessageInformation,
363}
364
365#[derive(Serialize, Deserialize, Debug, IntoParams, ToSchema)]
366pub struct InspectQueueV1Request {
367 #[schema(example = "campaign_name:tenant_name@example.com")]
369 pub queue_name: String,
370 #[serde(default)]
373 pub want_body: bool,
374
375 #[serde(default)]
381 pub limit: Option<usize>,
382}
383
384impl InspectQueueV1Request {
385 pub fn apply_to_url(&self, url: &mut Url) {
386 let mut query = url.query_pairs_mut();
387 query.append_pair("queue_name", &self.queue_name.to_string());
388 if self.want_body {
389 query.append_pair("want_body", "true");
390 }
391 if let Some(limit) = self.limit {
392 query.append_pair("limit", &limit.to_string());
393 }
394 }
395}
396
397#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
398pub struct InspectQueueV1Response {
399 #[schema(example = "campaign_name:tenant_name@example.com")]
400 pub queue_name: String,
401 pub messages: Vec<InspectMessageV1Response>,
402 pub num_scheduled: usize,
403 #[schema(value_type=Object)]
404 pub queue_config: serde_json::Value,
405 pub delayed_metric: usize,
406 pub now: DateTime<Utc>,
407 pub last_changed: DateTime<Utc>,
408}
409
410#[serde_as]
411#[derive(Serialize, Deserialize, Debug, ToSchema)]
412pub struct MessageInformation {
413 #[schema(example = "sender@sender.example.com")]
415 pub sender: String,
416 #[schema(example = "recipient@example.com", format = "email")]
420 #[serde_as(as = "OneOrMany<_, PreferOne>")] pub recipient: Vec<String>,
422 #[schema(value_type=Object, example=json!({
424 "received_from": "10.0.0.1:3488"
425 }))]
426 pub meta: serde_json::Value,
427 #[serde(default)]
430 #[schema(example = "From: user@example.com\nSubject: Hello\n\nHello there")]
431 pub data: Option<String>,
432 #[serde(skip_serializing_if = "Option::is_none")]
433 pub due: Option<DateTime<Utc>>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 pub num_attempts: Option<u16>,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 #[schema(value_type=Object)]
438 pub scheduling: Option<serde_json::Value>,
439}
440
441#[derive(Serialize, Deserialize, Debug, ToSchema)]
442pub struct TraceSmtpV1Request {
443 #[serde(default)]
444 #[schema(value_type=Option<Vec<String>>)]
445 pub source_addr: Option<CidrSet>,
446
447 #[serde(default, skip_serializing_if = "is_false")]
448 pub terse: bool,
449}
450
451fn is_false(b: &bool) -> bool {
452 !b
453}
454
455#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
456pub struct TraceSmtpV1Event {
457 pub conn_meta: serde_json::Value,
458 pub payload: TraceSmtpV1Payload,
459 pub when: DateTime<Utc>,
460}
461
462#[serde_as]
463#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq)]
464pub enum TraceSmtpV1Payload {
465 Connected,
466 Closed,
467 Read(String),
468 Write(String),
469 Diagnostic {
470 level: String,
471 message: String,
472 },
473 Callback {
474 name: String,
475 result: Option<serde_json::Value>,
476 error: Option<String>,
477 },
478 MessageDisposition {
479 relay: bool,
480 log_arf: serde_json::Value,
481 log_oob: serde_json::Value,
482 queue: String,
483 meta: serde_json::Value,
484 #[schema(format = "email")]
485 sender: String,
486 #[serde_as(as = "OneOrMany<_, PreferOne>")] #[schema(format = "email")]
488 recipient: Vec<String>,
489 id: SpoolId,
490 #[serde(default)]
491 was_arf_or_oob: Option<bool>,
492 #[serde(default)]
493 will_enqueue: Option<bool>,
494 },
495 AbbreviatedRead {
497 snippet: String,
499 len: usize,
501 },
502}
503
504#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
505pub struct TraceSmtpClientV1Event {
506 pub conn_meta: serde_json::Value,
507 pub payload: TraceSmtpClientV1Payload,
508 pub when: DateTime<Utc>,
509}
510
511#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq)]
512pub enum TraceSmtpClientV1Payload {
513 BeginSession,
514 Connected,
515 Closed,
516 Read(String),
517 Write(String),
518 Diagnostic {
519 level: String,
520 message: String,
521 },
522 MessageObtained,
523 AbbreviatedWrite {
525 snippet: String,
527 len: usize,
529 },
530}
531
532#[derive(Serialize, Deserialize, Debug, ToSchema)]
533pub struct TraceSmtpClientV1Request {
534 #[serde(default)]
536 #[schema(example = "campaign_name")]
537 pub campaign: Vec<String>,
538
539 #[serde(default)]
541 #[schema(example = "tenant_name")]
542 pub tenant: Vec<String>,
543
544 #[serde(default)]
546 #[schema(example = "example.com")]
547 pub domain: Vec<String>,
548
549 #[serde(default)]
551 #[schema(example = "routing_domain.com")]
552 pub routing_domain: Vec<String>,
553
554 #[serde(default)]
556 #[schema(example = "pool_name")]
557 pub egress_pool: Vec<String>,
558
559 #[serde(default)]
561 #[schema(example = "source_name")]
562 pub egress_source: Vec<String>,
563
564 #[serde(default)]
566 #[schema(format = "email")]
567 pub mail_from: Vec<String>,
568
569 #[serde(default)]
571 #[schema(format = "email")]
572 pub rcpt_to: Vec<String>,
573
574 #[serde(default)]
576 #[schema(value_type=Option<Vec<String>>, example="10.0.0.1/16")]
577 pub source_addr: Option<CidrSet>,
578
579 #[serde(default)]
581 #[schema(format = "mx1.example.com")]
582 pub mx_host: Vec<String>,
583
584 #[serde(default)]
586 #[schema(
587 example = "source_name->(alt1|alt2|alt3|alt4)?.gmail-smtp-in.l.google.com@smtp_client"
588 )]
589 pub ready_queue: Vec<String>,
590
591 #[serde(default)]
593 #[schema(value_type=Option<Vec<String>>, example="10.0.0.1/16")]
594 pub mx_addr: Option<CidrSet>,
595
596 #[serde(default, skip_serializing_if = "is_false")]
599 pub terse: bool,
600}
601
602#[derive(Serialize, Deserialize, Debug, ToSchema, IntoParams)]
603pub struct ReadyQueueStateRequest {
604 #[serde(default)]
606 #[schema(example=json!(["campaign_name:tenant_name@example.com"]))]
607 pub queues: Vec<String>,
608}
609
610#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
611pub struct QueueState {
612 #[schema(example = "TooManyLeases for queue")]
613 pub context: String,
614 pub since: DateTime<Utc>,
615}
616
617#[derive(Serialize, Deserialize, Debug, ToResponse, ToSchema)]
618pub struct ReadyQueueStateResponse {
619 pub states_by_ready_queue: HashMap<String, HashMap<String, QueueState>>,
620}