Bug: [FEATURE] Add support for SMTP email server



### Feature Description

Add support for sending emails with a custom SMTP server. (Currently only AWS SES is supported)

### Possible Implementation

Could be implemented using `lettre` crate.
Steps to implement:
- Create the email building and sending logic
- Impl the EmailClient trait
- Add a new arm to the match statement in `create_email_client()` function

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/Cargo.lock b/Cargo.lock
index 27b4158ba61..a2ede8c08c1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1888,9 +1888,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
 name = "cc"
-version = "1.1.18"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
+checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
 dependencies = [
  "jobserver",
  "libc",
@@ -1963,6 +1963,16 @@ dependencies = [
  "phf_codegen",
 ]
 
+[[package]]
+name = "chumsky"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
+dependencies = [
+ "hashbrown 0.14.5",
+ "stacker",
+]
+
 [[package]]
 name = "ciborium"
 version = "0.2.2"
@@ -2962,6 +2972,22 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "email-encoding"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f"
+dependencies = [
+ "base64 0.22.1",
+ "memchr",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.34"
@@ -3134,6 +3160,7 @@ dependencies = [
  "hyper-proxy",
  "hyper-util",
  "hyperswitch_interfaces",
+ "lettre",
  "masking",
  "once_cell",
  "prost 0.13.2",
@@ -3732,6 +3759,17 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "hostname"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "windows",
+]
+
 [[package]]
 name = "hsdev"
 version = "0.1.0"
@@ -4117,6 +4155,124 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec 1.13.2",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
 [[package]]
 name = "ident_case"
 version = "1.0.1"
@@ -4133,6 +4289,27 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec 1.13.2",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
 [[package]]
 name = "ignore"
 version = "0.4.22"
@@ -4456,6 +4633,31 @@ dependencies = [
  "spin 0.9.8",
 ]
 
+[[package]]
+name = "lettre"
+version = "0.11.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0161e452348e399deb685ba05e55ee116cae9410f4f51fe42d597361444521d9"
+dependencies = [
+ "base64 0.22.1",
+ "chumsky",
+ "email-encoding",
+ "email_address",
+ "fastrand 2.1.1",
+ "futures-util",
+ "hostname",
+ "httpdate",
+ "idna 1.0.3",
+ "mime",
+ "native-tls",
+ "nom",
+ "percent-encoding",
+ "quoted_printable",
+ "socket2",
+ "tokio 1.40.0",
+ "url",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.158"
@@ -4531,6 +4733,12 @@ version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
+[[package]]
+name = "litemap"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+
 [[package]]
 name = "local-channel"
 version = "0.1.5"
@@ -5832,7 +6040,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302"
 dependencies = [
  "bytes 1.7.1",
  "heck 0.5.0",
- "itertools 0.12.1",
+ "itertools 0.13.0",
  "log",
  "multimap",
  "once_cell",
@@ -5865,7 +6073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac"
 dependencies = [
  "anyhow",
- "itertools 0.12.1",
+ "itertools 0.13.0",
  "proc-macro2",
  "quote",
  "syn 2.0.77",
@@ -5880,6 +6088,15 @@ dependencies = [
  "prost 0.13.2",
 ]
 
+[[package]]
+name = "psm"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "ptr_meta"
 version = "0.1.4"
@@ -5949,6 +6166,12 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "quoted_printable"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
+
 [[package]]
 name = "r2d2"
 version = "0.8.10"
@@ -7669,6 +7892,19 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
+[[package]]
+name = "stacker"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b"
+dependencies = [
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "psm",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -8092,6 +8328,16 @@ dependencies = [
  "crunchy",
 ]
 
+[[package]]
+name = "tinystr"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
 [[package]]
 name = "tinytemplate"
 version = "1.2.1"
@@ -8939,7 +9185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
 dependencies = [
  "form_urlencoded",
- "idna",
+ "idna 0.5.0",
  "percent-encoding",
  "serde",
 ]
@@ -8956,6 +9202,18 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "110352d4e9076c67839003c7788d8604e24dcded13e0b375af3efaa8cf468517"
 
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
 [[package]]
 name = "utf8parse"
 version = "0.2.2"
@@ -9001,7 +9259,7 @@ version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da339118f018cc70ebf01fafc103360528aad53717e4bf311db929cb01cb9345"
 dependencies = [
- "idna",
+ "idna 0.5.0",
  "once_cell",
  "regex",
  "serde",
@@ -9290,6 +9548,16 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.6",
+]
+
 [[package]]
 name = "windows-core"
 version = "0.52.0"
@@ -9529,6 +9797,18 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
 [[package]]
 name = "ws2_32-sys"
 version = "0.2.1"
@@ -9580,6 +9860,30 @@ dependencies = [
  "linked-hash-map",
 ]
 
+[[package]]
+name = "yoke"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+ "synstructure 0.13.1",
+]
+
 [[package]]
 name = "zerocopy"
 version = "0.7.35"
@@ -9601,12 +9905,55 @@ dependencies = [
  "syn 2.0.77",
 ]
 
+[[package]]
+name = "zerofrom"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+ "synstructure 0.13.1",
+]
+
 [[package]]
 name = "zeroize"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
 
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
 [[package]]
 name = "zstd"
 version = "0.13.2"
diff --git a/config/development.toml b/config/development.toml
index 6a6fda6755e..97739c3f5cd 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -311,7 +311,7 @@ wildcard_origin = true
 sender_email = "example@example.com"
 aws_region = ""
 allowed_unverified_days = 1
-active_email_client = "SES"
+active_email_client = "NO_EMAIL_CLIENT"
 recon_recipient_email = "recon@example.com"
 prod_intent_recipient_email = "business@example.com"
 
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index 7adeee8a376..d71be958486 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -676,7 +676,7 @@ connector_list = "cybersource"
 sender_email = "example@example.com"                 # Sender email
 aws_region = ""                                      # AWS region used by AWS SES
 allowed_unverified_days = 1                          # Number of days the api calls ( with jwt token ) can be made without verifying the email
-active_email_client = "SES"                          # The currently active email client
+active_email_client = "NO_EMAIL_CLIENT"              # The currently active email client
 recon_recipient_email = "recon@example.com"          # Recipient email for recon request email
 prod_intent_recipient_email = "business@example.com" # Recipient email for prod intent email
 
diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml
index 12b9dd3b0f3..e617dbe8351 100644
--- a/crates/external_services/Cargo.toml
+++ b/crates/external_services/Cargo.toml
@@ -29,6 +29,7 @@ error-stack = "0.4.1"
 hex = "0.4.3"
 hyper = "0.14.28"
 hyper-proxy = "0.9.1"
+lettre = "0.11.10"
 once_cell = "1.19.0"
 serde = { version = "1.0.197", features = ["derive"] }
 thiserror = "1.0.58"
diff --git a/crates/external_services/src/email.rs b/crates/external_services/src/email.rs
index 5751de95c12..2e05a26e1f1 100644
--- a/crates/external_services/src/email.rs
+++ b/crates/external_services/src/email.rs
@@ -7,6 +7,12 @@ use serde::Deserialize;
 /// Implementation of aws ses client
 pub mod ses;
 
+/// Implementation of SMTP server client
+pub mod smtp;
+
+/// Implementation of Email client when email support is disabled
+pub mod no_email;
+
 /// Custom Result type alias for Email operations.
 pub type EmailResult<T> = CustomResult<T, EmailError>;
 
@@ -114,14 +120,27 @@ dyn_clone::clone_trait_object!(EmailClient<RichText = Body>);
 
 /// List of available email clients to choose from
 #[derive(Debug, Clone, Default, Deserialize)]
-pub enum AvailableEmailClients {
+#[serde(tag = "active_email_client")]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum EmailClientConfigs {
     #[default]
+    /// Default Email client to use when no client is specified
+    NoEmailClient,
     /// AWS ses email client
-    SES,
+    Ses {
+        /// AWS SES client configuration
+        aws_ses: ses::SESConfig,
+    },
+    /// Other Simple SMTP server
+    Smtp {
+        /// SMTP server configuration
+        smtp: smtp::SmtpServerConfig,
+    },
 }
 
 /// Struct that contains the settings required to construct an EmailClient.
 #[derive(Debug, Clone, Default, Deserialize)]
+#[serde(default)]
 pub struct EmailSettings {
     /// The AWS region to send SES requests to.
     pub aws_region: String,
@@ -132,11 +151,9 @@ pub struct EmailSettings {
     /// Sender email
     pub sender_email: String,
 
-    /// Configs related to AWS Simple Email Service
-    pub aws_ses: Option<ses::SESConfig>,
-
-    /// The active email client to use
-    pub active_email_client: AvailableEmailClients,
+    #[serde(flatten)]
+    /// The client specific configurations
+    pub client_config: EmailClientConfigs,
 
     /// Recipient email for recon emails
     pub recon_recipient_email: pii::Email,
@@ -145,6 +162,17 @@ pub struct EmailSettings {
     pub prod_intent_recipient_email: pii::Email,
 }
 
+impl EmailSettings {
+    /// Validation for the Email client specific configurations
+    pub fn validate(&self) -> Result<(), &'static str> {
+        match &self.client_config {
+            EmailClientConfigs::Ses { ref aws_ses } => aws_ses.validate(),
+            EmailClientConfigs::Smtp { ref smtp } => smtp.validate(),
+            EmailClientConfigs::NoEmailClient => Ok(()),
+        }
+    }
+}
+
 /// Errors that could occur from EmailClient.
 #[derive(Debug, thiserror::Error)]
 pub enum EmailError {
diff --git a/crates/external_services/src/email/no_email.rs b/crates/external_services/src/email/no_email.rs
new file mode 100644
index 00000000000..6ec5d69e1ab
--- /dev/null
+++ b/crates/external_services/src/email/no_email.rs
@@ -0,0 +1,37 @@
+use common_utils::{errors::CustomResult, pii};
+use router_env::logger;
+
+use crate::email::{EmailClient, EmailError, EmailResult, IntermediateString};
+
+/// Client when email support is disabled
+#[derive(Debug, Clone, Default, serde::Deserialize)]
+pub struct NoEmailClient {}
+
+impl NoEmailClient {
+    /// Constructs a new client when email is disabled
+    pub async fn create() -> Self {
+        Self {}
+    }
+}
+
+#[async_trait::async_trait]
+impl EmailClient for NoEmailClient {
+    type RichText = String;
+    fn convert_to_rich_text(
+        &self,
+        intermediate_string: IntermediateString,
+    ) -> CustomResult<Self::RichText, EmailError> {
+        Ok(intermediate_string.into_inner())
+    }
+
+    async fn send_email(
+        &self,
+        _recipient: pii::Email,
+        _subject: String,
+        _body: Self::RichText,
+        _proxy_url: Option<&String>,
+    ) -> EmailResult<()> {
+        logger::info!("Email not sent as email support is disabled, please enable any of the supported email clients to send emails");
+        Ok(())
+    }
+}
diff --git a/crates/external_services/src/email/ses.rs b/crates/external_services/src/email/ses.rs
index 73599b344cd..f9dcc8f26ad 100644
--- a/crates/external_services/src/email/ses.rs
+++ b/crates/external_services/src/email/ses.rs
@@ -7,7 +7,7 @@ use aws_sdk_sesv2::{
     Client,
 };
 use aws_sdk_sts::config::Credentials;
-use common_utils::{errors::CustomResult, ext_traits::OptionExt, pii};
+use common_utils::{errors::CustomResult, pii};
 use error_stack::{report, ResultExt};
 use hyper::Uri;
 use masking::PeekInterface;
@@ -19,6 +19,7 @@ use crate::email::{EmailClient, EmailError, EmailResult, EmailSettings, Intermed
 #[derive(Debug, Clone)]
 pub struct AwsSes {
     sender: String,
+    ses_config: SESConfig,
     settings: EmailSettings,
 }
 
@@ -32,6 +33,21 @@ pub struct SESConfig {
     pub sts_role_session_name: String,
 }
 
+impl SESConfig {
+    /// Validation for the SES client specific configs
+    pub fn validate(&self) -> Result<(), &'static str> {
+        use common_utils::{ext_traits::ConfigExt, fp_utils::when};
+
+        when(self.email_role_arn.is_default_or_empty(), || {
+            Err("email.aws_ses.email_role_arn must not be empty")
+        })?;
+
+        when(self.sts_role_session_name.is_default_or_empty(), || {
+            Err("email.aws_ses.sts_role_session_name must not be empty")
+        })
+    }
+}
+
 /// Errors that could occur during SES operations.
 #[derive(Debug, thiserror::Error)]
 pub enum AwsSesError {
@@ -67,15 +83,20 @@ pub enum AwsSesError {
 
 impl AwsSes {
     /// Constructs a new AwsSes client
-    pub async fn create(conf: &EmailSettings, proxy_url: Option<impl AsRef<str>>) -> Self {
+    pub async fn create(
+        conf: &EmailSettings,
+        ses_config: &SESConfig,
+        proxy_url: Option<impl AsRef<str>>,
+    ) -> Self {
         // Build the client initially which will help us know if the email configuration is correct
-        Self::create_client(conf, proxy_url)
+        Self::create_client(conf, ses_config, proxy_url)
             .await
             .map_err(|error| logger::error!(?error, "Failed to initialize SES Client"))
             .ok();
 
         Self {
             sender: conf.sender_email.clone(),
+            ses_config: ses_config.clone(),
             settings: conf.clone(),
         }
     }
@@ -83,19 +104,13 @@ impl AwsSes {
     /// A helper function to create ses client
     pub async fn create_client(
         conf: &EmailSettings,
+        ses_config: &SESConfig,
         proxy_url: Option<impl AsRef<str>>,
     ) -> CustomResult<Client, AwsSesError> {
         let sts_config = Self::get_shared_config(conf.aws_region.to_owned(), proxy_url.as_ref())?
             .load()
             .await;
 
-        let ses_config = conf
-            .aws_ses
-            .as_ref()
-            .get_required_value("aws ses configuration")
-            .attach_printable("The selected email client is aws ses, but configuration is missing")
-            .change_context(AwsSesError::MissingConfigurationVariable("aws_ses"))?;
-
         let role = aws_sdk_sts::Client::new(&sts_config)
             .assume_role()
             .role_arn(&ses_config.email_role_arn)
@@ -219,7 +234,7 @@ impl EmailClient for AwsSes {
     ) -> EmailResult<()> {
         // Not using the same email client which was created at startup as the role session would expire
         // Create a client every time when the email is being sent
-        let email_client = Self::create_client(&self.settings, proxy_url)
+        let email_client = Self::create_client(&self.settings, &self.ses_config, proxy_url)
             .await
             .change_context(EmailError::ClientBuildingFailure)?;
 
diff --git a/crates/external_services/src/email/smtp.rs b/crates/external_services/src/email/smtp.rs
new file mode 100644
index 00000000000..33ab89f4571
--- /dev/null
+++ b/crates/external_services/src/email/smtp.rs
@@ -0,0 +1,189 @@
+use std::time::Duration;
+
+use common_utils::{errors::CustomResult, pii};
+use error_stack::ResultExt;
+use lettre::{
+    address::AddressError,
+    error,
+    message::{header::ContentType, Mailbox},
+    transport::smtp::{self, authentication::Credentials},
+    Message, SmtpTransport, Transport,
+};
+use masking::{PeekInterface, Secret};
+
+use crate::email::{EmailClient, EmailError, EmailResult, EmailSettings, IntermediateString};
+
+/// Client for SMTP server operation
+#[derive(Debug, Clone, Default, serde::Deserialize)]
+pub struct SmtpServer {
+    /// sender email id
+    pub sender: String,
+    /// SMTP server specific configs
+    pub smtp_config: SmtpServerConfig,
+}
+
+impl SmtpServer {
+    /// A helper function to create SMTP server client
+    pub fn create_client(&self) -> Result<SmtpTransport, SmtpError> {
+        let host = self.smtp_config.host.clone();
+        let port = self.smtp_config.port;
+        let timeout = Some(Duration::from_secs(self.smtp_config.timeout));
+        let credentials = self
+            .smtp_config
+            .username
+            .clone()
+            .zip(self.smtp_config.password.clone())
+            .map(|(username, password)| {
+                Credentials::new(username.peek().to_owned(), password.peek().to_owned())
+            });
+        match &self.smtp_config.connection {
+            SmtpConnection::StartTls => match credentials {
+                Some(credentials) => Ok(SmtpTransport::starttls_relay(&host)
+                    .map_err(SmtpError::ConnectionFailure)?
+                    .port(port)
+                    .timeout(timeout)
+                    .credentials(credentials)
+                    .build()),
+                None => Ok(SmtpTransport::starttls_relay(&host)
+                    .map_err(SmtpError::ConnectionFailure)?
+                    .port(port)
+                    .timeout(timeout)
+                    .build()),
+            },
+            SmtpConnection::Plaintext => match credentials {
+                Some(credentials) => Ok(SmtpTransport::builder_dangerous(&host)
+                    .port(port)
+                    .timeout(timeout)
+                    .credentials(credentials)
+                    .build()),
+                None => Ok(SmtpTransport::builder_dangerous(&host)
+                    .port(port)
+                    .timeout(timeout)
+                    .build()),
+            },
+        }
+    }
+    /// Constructs a new SMTP client
+    pub async fn create(conf: &EmailSettings, smtp_config: SmtpServerConfig) -> Self {
+        Self {
+            sender: conf.sender_email.clone(),
+            smtp_config: smtp_config.clone(),
+        }
+    }
+    /// helper function to convert email id into Mailbox
+    fn to_mail_box(email: String) -> EmailResult<Mailbox> {
+        Ok(Mailbox::new(
+            None,
+            email
+                .parse()
+                .map_err(SmtpError::EmailParsingFailed)
+                .change_context(EmailError::EmailSendingFailure)?,
+        ))
+    }
+}
+/// Struct that contains the SMTP server specific configs required
+#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub struct SmtpServerConfig {
+    /// hostname of the SMTP server eg: smtp.gmail.com
+    pub host: String,
+    /// portname of the SMTP server eg: 25
+    pub port: u16,
+    /// timeout for the SMTP server connection in seconds eg: 10
+    pub timeout: u64,
+    /// Username name of the SMTP server
+    pub username: Option<Secret<String>>,
+    /// Password of the SMTP server
+    pub password: Option<Secret<String>>,
+    /// Connection type of the SMTP server
+    #[serde(default)]
+    pub connection: SmtpConnection,
+}
+
+/// Enum that contains the connection types of the SMTP server
+#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum SmtpConnection {
+    #[default]
+    /// Plaintext connection which MUST then successfully upgrade to TLS via STARTTLS
+    StartTls,
+    /// Plaintext connection (very insecure)
+    Plaintext,
+}
+
+impl SmtpServerConfig {
+    /// Validation for the SMTP server client specific configs
+    pub fn validate(&self) -> Result<(), &'static str> {
+        use common_utils::{ext_traits::ConfigExt, fp_utils::when};
+        when(self.host.is_default_or_empty(), || {
+            Err("email.smtp.host must not be empty")
+        })?;
+        self.username.clone().zip(self.password.clone()).map_or(
+            Ok(()),
+            |(username, password)| {
+                when(username.peek().is_default_or_empty(), || {
+                    Err("email.smtp.username must not be empty")
+                })?;
+                when(password.peek().is_default_or_empty(), || {
+                    Err("email.smtp.password must not be empty")
+                })
+            },
+        )?;
+        Ok(())
+    }
+}
+
+#[async_trait::async_trait]
+impl EmailClient for SmtpServer {
+    type RichText = String;
+    fn convert_to_rich_text(
+        &self,
+        intermediate_string: IntermediateString,
+    ) -> CustomResult<Self::RichText, EmailError> {
+        Ok(intermediate_string.into_inner())
+    }
+
+    async fn send_email(
+        &self,
+        recipient: pii::Email,
+        subject: String,
+        body: Self::RichText,
+        _proxy_url: Option<&String>,
+    ) -> EmailResult<()> {
+        // Create a client every time when the email is being sent
+        let email_client =
+            Self::create_client(self).change_context(EmailError::EmailSendingFailure)?;
+
+        let email = Message::builder()
+            .to(Self::to_mail_box(recipient.peek().to_string())?)
+            .from(Self::to_mail_box(self.sender.clone())?)
+            .subject(subject)
+            .header(ContentType::TEXT_HTML)
+            .body(body)
+            .map_err(SmtpError::MessageBuildingFailed)
+            .change_context(EmailError::EmailSendingFailure)?;
+
+        email_client
+            .send(&email)
+            .map_err(SmtpError::SendingFailure)
+            .change_context(EmailError::EmailSendingFailure)?;
+        Ok(())
+    }
+}
+
+/// Errors that could occur during SES operations.
+#[derive(Debug, thiserror::Error)]
+pub enum SmtpError {
+    /// An error occurred in the SMTP while sending email.
+    #[error("Failed to Send Email {0:?}")]
+    SendingFailure(smtp::Error),
+    /// An error occurred in the SMTP while building the message content.
+    #[error("Failed to create connection {0:?}")]
+    ConnectionFailure(smtp::Error),
+    /// An error occurred in the SMTP while building the message content.
+    #[error("Failed to Build Email content {0:?}")]
+    MessageBuildingFailed(error::Error),
+    /// An error occurred in the SMTP while building the message content.
+    #[error("Failed to parse given email {0:?}")]
+    EmailParsingFailed(AddressError),
+}
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index f675aad11a7..76b58f5b67b 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -881,6 +881,10 @@ impl Settings<SecuredSecret> {
             .transpose()?;
 
         self.key_manager.get_inner().validate()?;
+        #[cfg(feature = "email")]
+        self.email
+            .validate()
+            .map_err(|err| ApplicationError::InvalidConfigurationValueError(err.into()))?;
 
         Ok(())
     }
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 1d7e9727cf3..baa2ba4ae15 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -8,7 +8,9 @@ use common_enums::TransactionType;
 #[cfg(feature = "partial-auth")]
 use common_utils::crypto::Blake3;
 #[cfg(feature = "email")]
-use external_services::email::{ses::AwsSes, EmailService};
+use external_services::email::{
+    no_email::NoEmailClient, ses::AwsSes, smtp::SmtpServer, EmailClientConfigs, EmailService,
+};
 use external_services::{file_storage::FileStorageInterface, grpc_client::GrpcClients};
 use hyperswitch_interfaces::{
     encryption_interface::EncryptionManagementInterface,
@@ -97,7 +99,7 @@ pub struct SessionState {
     pub api_client: Box<dyn crate::services::ApiClient>,
     pub event_handler: EventsHandler,
     #[cfg(feature = "email")]
-    pub email_client: Arc<dyn EmailService>,
+    pub email_client: Arc<Box<dyn EmailService>>,
     #[cfg(feature = "olap")]
     pub pool: AnalyticsProvider,
     pub file_storage_client: Arc<dyn FileStorageInterface>,
@@ -195,7 +197,7 @@ pub struct AppState {
     pub conf: Arc<settings::Settings<RawSecret>>,
     pub event_handler: EventsHandler,
     #[cfg(feature = "email")]
-    pub email_client: Arc<dyn EmailService>,
+    pub email_client: Arc<Box<dyn EmailService>>,
     pub api_client: Box<dyn crate::services::ApiClient>,
     #[cfg(feature = "olap")]
     pub pools: HashMap<String, AnalyticsProvider>,
@@ -215,7 +217,7 @@ pub trait AppStateInfo {
     fn conf(&self) -> settings::Settings<RawSecret>;
     fn event_handler(&self) -> EventsHandler;
     #[cfg(feature = "email")]
-    fn email_client(&self) -> Arc<dyn EmailService>;
+    fn email_client(&self) -> Arc<Box<dyn EmailService>>;
     fn add_request_id(&mut self, request_id: RequestId);
     fn add_flow_name(&mut self, flow_name: String);
     fn get_request_id(&self) -> Option<String>;
@@ -232,7 +234,7 @@ impl AppStateInfo for AppState {
         self.conf.as_ref().to_owned()
     }
     #[cfg(feature = "email")]
-    fn email_client(&self) -> Arc<dyn EmailService> {
+    fn email_client(&self) -> Arc<Box<dyn EmailService>> {
         self.email_client.to_owned()
     }
     fn event_handler(&self) -> EventsHandler {
@@ -258,11 +260,22 @@ impl AsRef<Self> for AppState {
 }
 
 #[cfg(feature = "email")]
-pub async fn create_email_client(settings: &settings::Settings<RawSecret>) -> impl EmailService {
-    match settings.email.active_email_client {
-        external_services::email::AvailableEmailClients::SES => {
-            AwsSes::create(&settings.email, settings.proxy.https_url.to_owned()).await
+pub async fn create_email_client(
+    settings: &settings::Settings<RawSecret>,
+) -> Box<dyn EmailService> {
+    match &settings.email.client_config {
+        EmailClientConfigs::Ses { aws_ses } => Box::new(
+            AwsSes::create(
+                &settings.email,
+                aws_ses,
+                settings.proxy.https_url.to_owned(),
+            )
+            .await,
+        ),
+        EmailClientConfigs::Smtp { smtp } => {
+            Box::new(SmtpServer::create(&settings.email, smtp.clone()).await)
         }
+        EmailClientConfigs::NoEmailClient => Box::new(NoEmailClient::create().await),
     }
 }
 
diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml
index 934b072f14a..288e72164cf 100644
--- a/loadtest/config/development.toml
+++ b/loadtest/config/development.toml
@@ -384,7 +384,7 @@ public = { base_url = "http://localhost:8080", schema = "public", redis_key_pref
 sender_email = "example@example.com"
 aws_region = ""
 allowed_unverified_days = 1
-active_email_client = "SES"
+active_email_client = "NO_EMAIL_CLIENT"
 recon_recipient_email = "recon@example.com"
 prod_intent_recipient_email = "business@example.com"
 


## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [X] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Add SMTP support to allow mails through self-hosted/custom SMTP server
#5156 

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Hyperswitch currently supports only AWS SES to send emails, other open users who want to use some custom SMTP server will have no way to configure it. With this implementation users can change below config to use their own SMTP server.

```
[email]
active_email_client = "SMTP"

[email.smtp]
host = "127.0.0.1"
port = 1025
timeout = 10
connection = "plaintext" #currently plaintext and starttls are supported
```

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Setup mailhog to test emails locally
```docker run -p 1025:1025 -p 8025:8025 mailhog/mailhog```

1. Check if is possible to disable email with the env config
```
[email]
active_email_client = "NO_EMAIL_CLIENT"
```
Start the server with `cargo r` and there should be no error

2. Check the SMTP server without authentication and No TLS with the above configs
Start the server with `cargo r`, and there should be no error
Start the dashboard and change the below config
```
[user]
base_url = "http://localhost:9000"
```

- Check if signup email is sent correctly 
- Check if reset password email is sent correctly: Profile -> Reset Password 
- Check if Forgot password email is sent correctly
- Check if magic link is working
- Check if invite user email is sent correctly
<img width="785" alt="Screenshot 2024-11-20 at 01 24 02" src="https://github.com/user-attachments/assets/feb21b27-07c4-4135-8ff2-bd114a704fcf">
<img width="1342" alt="Screenshot 2024-11-20 at 01 24 28" src="https://github.com/user-attachments/assets/7c141481-7a81-41cb-9253-b6fc2a6a07b7">


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [ ] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
