mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	feat(email): integrate email service using AWS SES (#1158)
This commit is contained in:
		
							
								
								
									
										92
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										92
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -598,9 +598,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-credential-types" | name = "aws-credential-types" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "f4232d3729eefc287adc0d5a8adc97b7d94eefffe6bbe94312cc86c7ab6b06ce" | checksum = "4cb57ac6088805821f78d282c0ba8aec809f11cbee10dda19a97b03ab040ccc2" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-async", |  "aws-smithy-async", | ||||||
|  "aws-smithy-types", |  "aws-smithy-types", | ||||||
| @ -612,9 +612,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-endpoint" | name = "aws-endpoint" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "87f04ab03b3f1cca91f7cccaa213056d732accb14e2e65debfacc1d28627d162" | checksum = "9c5f6f84a4f46f95a9bb71d9300b73cd67eb868bc43ae84f66ad34752299f4ac" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-http", |  "aws-smithy-http", | ||||||
|  "aws-smithy-types", |  "aws-smithy-types", | ||||||
| @ -626,9 +626,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-http" | name = "aws-http" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e5ad8c53f7560baaf635b6aa811f3213d39b50555d100f83e43801652d4e318e" | checksum = "a754683c322f7dc5167484266489fdebdcd04d26e53c162cad1f3f949f2c5671" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-credential-types", |  "aws-credential-types", | ||||||
|  "aws-smithy-http", |  "aws-smithy-http", | ||||||
| @ -701,6 +701,31 @@ dependencies = [ | |||||||
|  "url", |  "url", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "aws-sdk-sesv2" | ||||||
|  | version = "0.27.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "28ec96086c4bda28c512b10c5c951d031651be454a512e511cf5fe91b21b9cc9" | ||||||
|  | dependencies = [ | ||||||
|  |  "aws-credential-types", | ||||||
|  |  "aws-endpoint", | ||||||
|  |  "aws-http", | ||||||
|  |  "aws-sig-auth", | ||||||
|  |  "aws-smithy-async", | ||||||
|  |  "aws-smithy-client", | ||||||
|  |  "aws-smithy-http", | ||||||
|  |  "aws-smithy-http-tower", | ||||||
|  |  "aws-smithy-json", | ||||||
|  |  "aws-smithy-types", | ||||||
|  |  "aws-types", | ||||||
|  |  "bytes", | ||||||
|  |  "http", | ||||||
|  |  "regex", | ||||||
|  |  "tokio-stream", | ||||||
|  |  "tower", | ||||||
|  |  "tracing", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-sdk-sso" | name = "aws-sdk-sso" | ||||||
| version = "0.26.0" | version = "0.26.0" | ||||||
| @ -754,9 +779,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-sig-auth" | name = "aws-sig-auth" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "24d77d879ab210e958ba65a6d3842969a596738c024989cd3e490cf9f9b560ec" | checksum = "84dc92a63ede3c2cbe43529cb87ffa58763520c96c6a46ca1ced80417afba845" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-credential-types", |  "aws-credential-types", | ||||||
|  "aws-sigv4", |  "aws-sigv4", | ||||||
| @ -769,9 +794,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-sigv4" | name = "aws-sigv4" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "4ab4eebc8ec484fb9eab04b15a5d1e71f3dc13bee8fdd2d9ed78bcd6ecbd7192" | checksum = "392fefab9d6fcbd76d518eb3b1c040b84728ab50f58df0c3c53ada4bea9d327e" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-eventstream", |  "aws-smithy-eventstream", | ||||||
|  "aws-smithy-http", |  "aws-smithy-http", | ||||||
| @ -790,9 +815,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-async" | name = "aws-smithy-async" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "88573bcfbe1dcfd54d4912846df028b42d6255cbf9ce07be216b1bbfd11fc4b9" | checksum = "ae23b9fe7a07d0919000116c4c5c0578303fbce6fc8d32efca1f7759d4c20faf" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  "pin-project-lite", |  "pin-project-lite", | ||||||
| @ -823,9 +848,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-client" | name = "aws-smithy-client" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "b2f52352bae50d3337d5d6151b695d31a8c10ebea113eca5bead531f8301b067" | checksum = "5230d25d244a51339273b8870f0f77874cd4449fb4f8f629b21188ae10cfc0ba" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-async", |  "aws-smithy-async", | ||||||
|  "aws-smithy-http", |  "aws-smithy-http", | ||||||
| @ -847,9 +872,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-eventstream" | name = "aws-smithy-eventstream" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "168f08f8439c8b317b578a695e514c5cd7b869e73849a2d6b71ced4de6ce193d" | checksum = "22d2a2bcc16e5c4d949ffd2b851da852b9bbed4bb364ed4ae371b42137ca06d9" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-types", |  "aws-smithy-types", | ||||||
|  "bytes", |  "bytes", | ||||||
| @ -858,9 +883,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-http" | name = "aws-smithy-http" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "03bcc02d7ed9649d855c8ce4a735e9848d7b8f7568aad0504c158e3baa955df8" | checksum = "b60e2133beb9fe6ffe0b70deca57aaeff0a35ad24a9c6fab2fd3b4f45b99fdb5" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-eventstream", |  "aws-smithy-eventstream", | ||||||
|  "aws-smithy-types", |  "aws-smithy-types", | ||||||
| @ -881,9 +906,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-http-tower" | name = "aws-smithy-http-tower" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "da88b3a860f65505996c29192d800f1aeb9480440f56d63aad33a3c12045017a" | checksum = "3a4d94f556c86a0dd916a5d7c39747157ea8cb909ca469703e20fee33e448b67" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-http", |  "aws-smithy-http", | ||||||
|  "aws-smithy-types", |  "aws-smithy-types", | ||||||
| @ -897,9 +922,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-json" | name = "aws-smithy-json" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "9b0c1e87d75cac889dca2a7f5ba280da2cde8122448e7fec1d614194dfa00c70" | checksum = "5ce3d6e6ebb00b2cce379f079ad5ec508f9bcc3a9510d9b9c1840ed1d6f8af39" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-smithy-types", |  "aws-smithy-types", | ||||||
| ] | ] | ||||||
| @ -916,9 +941,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-smithy-types" | name = "aws-smithy-types" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "cd0afc731fd1417d791f9145a1e0c30e23ae0beaab9b4814017708ead2fc20f1" | checksum = "58db46fc1f4f26be01ebdb821751b4e2482cd43aa2b64a0348fb89762defaffa" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "base64-simd", |  "base64-simd", | ||||||
|  "itoa", |  "itoa", | ||||||
| @ -938,9 +963,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-types" | name = "aws-types" | ||||||
| version = "0.55.1" | version = "0.55.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "b9b082e329d9a304d39e193ad5c7ab363a0d6507aca6965e0673a746686fb0cc" | checksum = "de0869598bfe46ec44ffe17e063ed33336e59df90356ca8ff0e8da6f7c1d994b" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "aws-credential-types", |  "aws-credential-types", | ||||||
|  "aws-smithy-async", |  "aws-smithy-async", | ||||||
| @ -1795,11 +1820,16 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" | |||||||
| name = "external_services" | name = "external_services" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "async-trait", | ||||||
|  "aws-config", |  "aws-config", | ||||||
|  "aws-sdk-kms", |  "aws-sdk-kms", | ||||||
|  |  "aws-sdk-sesv2", | ||||||
|  |  "aws-smithy-client", | ||||||
|  "base64 0.21.0", |  "base64 0.21.0", | ||||||
|  "common_utils", |  "common_utils", | ||||||
|  |  "dyn-clone", | ||||||
|  "error-stack", |  "error-stack", | ||||||
|  |  "masking", | ||||||
|  "once_cell", |  "once_cell", | ||||||
|  "router_env", |  "router_env", | ||||||
|  "serde", |  "serde", | ||||||
| @ -2970,7 +3000,7 @@ dependencies = [ | |||||||
| [[package]] | [[package]] | ||||||
| name = "opentelemetry" | name = "opentelemetry" | ||||||
| version = "0.18.0" | version = "0.18.0" | ||||||
| source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "opentelemetry_api", |  "opentelemetry_api", | ||||||
|  "opentelemetry_sdk", |  "opentelemetry_sdk", | ||||||
| @ -2979,7 +3009,7 @@ dependencies = [ | |||||||
| [[package]] | [[package]] | ||||||
| name = "opentelemetry-otlp" | name = "opentelemetry-otlp" | ||||||
| version = "0.11.0" | version = "0.11.0" | ||||||
| source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "async-trait", |  "async-trait", | ||||||
|  "futures", |  "futures", | ||||||
| @ -2996,7 +3026,7 @@ dependencies = [ | |||||||
| [[package]] | [[package]] | ||||||
| name = "opentelemetry-proto" | name = "opentelemetry-proto" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "futures", |  "futures", | ||||||
|  "futures-util", |  "futures-util", | ||||||
| @ -3008,7 +3038,7 @@ dependencies = [ | |||||||
| [[package]] | [[package]] | ||||||
| name = "opentelemetry_api" | name = "opentelemetry_api" | ||||||
| version = "0.18.0" | version = "0.18.0" | ||||||
| source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "fnv", |  "fnv", | ||||||
|  "futures-channel", |  "futures-channel", | ||||||
| @ -3023,7 +3053,7 @@ dependencies = [ | |||||||
| [[package]] | [[package]] | ||||||
| name = "opentelemetry_sdk" | name = "opentelemetry_sdk" | ||||||
| version = "0.18.0" | version = "0.18.0" | ||||||
| source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "async-trait", |  "async-trait", | ||||||
|  "crossbeam-channel", |  "crossbeam-channel", | ||||||
|  | |||||||
| @ -242,6 +242,12 @@ paypal = { currency = "USD,INR", country = "US" } | |||||||
| key_id = "" # The AWS key ID used by the KMS SDK for decrypting data. | key_id = "" # The AWS key ID used by the KMS SDK for decrypting data. | ||||||
| region = "" # The AWS region used by the KMS SDK for decrypting data. | region = "" # The AWS region used by the KMS SDK for decrypting data. | ||||||
|  |  | ||||||
|  | # EmailClient configuration. Only applicable when the `email` feature flag is enabled. | ||||||
|  | [email] | ||||||
|  | from_email = "notify@example.com" # Sender email | ||||||
|  | aws_region = "" # AWS region used by AWS SES  | ||||||
|  | base_url = "" # Base url used when adding links that should redirect to self | ||||||
|  |  | ||||||
| [dummy_connector] | [dummy_connector] | ||||||
| payment_ttl = 172800             # Time to live for dummy connector payment in redis | payment_ttl = 172800             # Time to live for dummy connector payment in redis | ||||||
| payment_duration = 1000          # Fake delay duration for dummy connector payment | payment_duration = 1000          # Fake delay duration for dummy connector payment | ||||||
|  | |||||||
| @ -149,6 +149,11 @@ stream = "SCHEDULER_STREAM" | |||||||
| disabled = false | disabled = false | ||||||
| consumer_group = "SCHEDULER_GROUP" | consumer_group = "SCHEDULER_GROUP" | ||||||
|  |  | ||||||
|  | [email] | ||||||
|  | from_email = "notify@example.com" | ||||||
|  | aws_region = "" | ||||||
|  | base_url = "" | ||||||
|  |  | ||||||
| [bank_config.eps] | [bank_config.eps] | ||||||
| stripe = { banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" } | stripe = { banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" } | ||||||
| adyen = { banks = "bank_austria,bawag_psk_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_tirol_bank_ag,posojilnica_bank_e_gen,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag" } | adyen = { banks = "bank_austria,bawag_psk_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_tirol_bank_ag,posojilnica_bank_e_gen,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag" } | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ license = "Apache-2.0" | |||||||
|  |  | ||||||
| [features] | [features] | ||||||
| kms = ["dep:aws-config", "dep:aws-sdk-kms"] | kms = ["dep:aws-config", "dep:aws-sdk-kms"] | ||||||
|  | email = ["dep:aws-config"] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| aws-config = { version = "0.55.1", optional = true } | aws-config = { version = "0.55.1", optional = true } | ||||||
| @ -19,7 +20,12 @@ once_cell = "1.17.1" | |||||||
| serde = { version = "1.0.160", features = ["derive"] } | serde = { version = "1.0.160", features = ["derive"] } | ||||||
| thiserror = "1.0.40" | thiserror = "1.0.40" | ||||||
| tokio = "1.27.0" | tokio = "1.27.0" | ||||||
|  | dyn-clone = "1.0.11" | ||||||
|  | async-trait = "0.1.66" | ||||||
|  | aws-sdk-sesv2 = "0.27.0" | ||||||
|  | aws-smithy-client =  "0.55.0" | ||||||
|  |  | ||||||
| # First party crates | # First party crates | ||||||
| common_utils = { version = "0.1.0", path = "../common_utils" } | common_utils = { version = "0.1.0", path = "../common_utils" } | ||||||
| router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } | router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } | ||||||
|  | masking = { version = "0.1.0", path = "../masking" } | ||||||
|  | |||||||
							
								
								
									
										123
									
								
								crates/external_services/src/email.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								crates/external_services/src/email.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | |||||||
|  | //! Interactions with the AWS SES SDK | ||||||
|  |  | ||||||
|  | use aws_config::meta::region::RegionProviderChain; | ||||||
|  | use aws_sdk_sesv2::{ | ||||||
|  |     config::Region, | ||||||
|  |     operation::send_email::SendEmailError, | ||||||
|  |     types::{Body, Content, Destination, EmailContent, Message}, | ||||||
|  |     Client, | ||||||
|  | }; | ||||||
|  | use common_utils::{errors::CustomResult, pii}; | ||||||
|  | use error_stack::{IntoReport, ResultExt}; | ||||||
|  | use masking::PeekInterface; | ||||||
|  | use serde::Deserialize; | ||||||
|  |  | ||||||
|  | /// Custom Result type alias for Email operations. | ||||||
|  | pub type EmailResult<T> = CustomResult<T, EmailError>; | ||||||
|  |  | ||||||
|  | /// A trait that defines the methods that must be implemented to send email. | ||||||
|  | #[async_trait::async_trait] | ||||||
|  | pub trait EmailClient: Sync + Send + dyn_clone::DynClone { | ||||||
|  |     /// Sends an email to the specified recipient with the given subject and body. | ||||||
|  |     async fn send_email( | ||||||
|  |         &self, | ||||||
|  |         recipient: pii::Email, | ||||||
|  |         subject: String, | ||||||
|  |         body: String, | ||||||
|  |     ) -> EmailResult<()>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dyn_clone::clone_trait_object!(EmailClient); | ||||||
|  |  | ||||||
|  | /// Struct that contains the settings required to construct an EmailClient. | ||||||
|  | #[derive(Debug, Clone, Default, Deserialize)] | ||||||
|  | pub struct EmailSettings { | ||||||
|  |     /// Sender email. | ||||||
|  |     pub from_email: String, | ||||||
|  |  | ||||||
|  |     /// The AWS region to send SES requests to. | ||||||
|  |     pub aws_region: String, | ||||||
|  |  | ||||||
|  |     /// Base-url used when adding links that should redirect to self | ||||||
|  |     pub base_url: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Client for AWS SES operation | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub struct AwsSes { | ||||||
|  |     ses_client: Client, | ||||||
|  |     from_email: String, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl AwsSes { | ||||||
|  |     /// Constructs a new AwsSes client | ||||||
|  |     pub async fn new(conf: &EmailSettings) -> Self { | ||||||
|  |         let region_provider = RegionProviderChain::first_try(Region::new(conf.aws_region.clone())); | ||||||
|  |         let sdk_config = aws_config::from_env().region(region_provider).load().await; | ||||||
|  |  | ||||||
|  |         Self { | ||||||
|  |             ses_client: Client::new(&sdk_config), | ||||||
|  |             from_email: conf.from_email.clone(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait::async_trait] | ||||||
|  | impl EmailClient for AwsSes { | ||||||
|  |     async fn send_email( | ||||||
|  |         &self, | ||||||
|  |         recipient: pii::Email, | ||||||
|  |         subject: String, | ||||||
|  |         body: String, | ||||||
|  |     ) -> EmailResult<()> { | ||||||
|  |         self.ses_client | ||||||
|  |             .send_email() | ||||||
|  |             .from_email_address(self.from_email.to_owned()) | ||||||
|  |             .destination( | ||||||
|  |                 Destination::builder() | ||||||
|  |                     .to_addresses(recipient.peek()) | ||||||
|  |                     .build(), | ||||||
|  |             ) | ||||||
|  |             .content( | ||||||
|  |                 EmailContent::builder() | ||||||
|  |                     .simple( | ||||||
|  |                         Message::builder() | ||||||
|  |                             .subject(Content::builder().data(subject).build()) | ||||||
|  |                             .body( | ||||||
|  |                                 Body::builder() | ||||||
|  |                                     .text(Content::builder().data(body).charset("UTF-8").build()) | ||||||
|  |                                     .build(), | ||||||
|  |                             ) | ||||||
|  |                             .build(), | ||||||
|  |                     ) | ||||||
|  |                     .build(), | ||||||
|  |             ) | ||||||
|  |             .send() | ||||||
|  |             .await | ||||||
|  |             .map_err(AwsSesError::SendingFailure) | ||||||
|  |             .into_report() | ||||||
|  |             .change_context(EmailError::EmailSendingFailure)?; | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Errors that could occur from EmailClient. | ||||||
|  | #[derive(Debug, thiserror::Error)] | ||||||
|  | pub enum EmailError { | ||||||
|  |     /// An error occurred when building email client. | ||||||
|  |     #[error("Error building email client")] | ||||||
|  |     ClientBuildingFailure, | ||||||
|  |  | ||||||
|  |     /// An error occurred when sending email | ||||||
|  |     #[error("Error sending email to recipient")] | ||||||
|  |     EmailSendingFailure, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Errors that could occur during SES operations. | ||||||
|  | #[derive(Debug, thiserror::Error)] | ||||||
|  | pub enum AwsSesError { | ||||||
|  |     /// An error occurred in the SDK while sending email. | ||||||
|  |     #[error("Failed to Send Email {0:?}")] | ||||||
|  |     SendingFailure(aws_smithy_client::SdkError<SendEmailError>), | ||||||
|  | } | ||||||
| @ -3,6 +3,9 @@ | |||||||
| #![forbid(unsafe_code)] | #![forbid(unsafe_code)] | ||||||
| #![warn(missing_docs, missing_debug_implementations)] | #![warn(missing_docs, missing_debug_implementations)] | ||||||
|  |  | ||||||
|  | #[cfg(feature = "email")] | ||||||
|  | pub mod email; | ||||||
|  |  | ||||||
| #[cfg(feature = "kms")] | #[cfg(feature = "kms")] | ||||||
| pub mod kms; | pub mod kms; | ||||||
|  |  | ||||||
|  | |||||||
| @ -13,10 +13,11 @@ build = "src/build.rs" | |||||||
| default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector"] | default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector"] | ||||||
| s3 = ["dep:aws-sdk-s3","dep:aws-config"] | s3 = ["dep:aws-sdk-s3","dep:aws-config"] | ||||||
| kms = ["external_services/kms","dep:aws-config"] | kms = ["external_services/kms","dep:aws-config"] | ||||||
|  | email = ["external_services/email","dep:aws-config"] | ||||||
| basilisk = ["kms"] | basilisk = ["kms"] | ||||||
| stripe = ["dep:serde_qs"] | stripe = ["dep:serde_qs"] | ||||||
| sandbox = ["kms", "stripe", "basilisk", "s3"] | sandbox = ["kms", "stripe", "basilisk", "s3", "email"] | ||||||
| production = ["kms", "stripe", "basilisk", "s3"] | production = ["kms", "stripe", "basilisk", "s3", "email"] | ||||||
| olap = [] | olap = [] | ||||||
| oltp = [] | oltp = [] | ||||||
| kv_store = [] | kv_store = [] | ||||||
|  | |||||||
| @ -7,6 +7,8 @@ use std::{ | |||||||
| use api_models::enums; | use api_models::enums; | ||||||
| use common_utils::ext_traits::ConfigExt; | use common_utils::ext_traits::ConfigExt; | ||||||
| use config::{Environment, File}; | use config::{Environment, File}; | ||||||
|  | #[cfg(feature = "email")] | ||||||
|  | use external_services::email::EmailSettings; | ||||||
| #[cfg(feature = "kms")] | #[cfg(feature = "kms")] | ||||||
| use external_services::kms; | use external_services::kms; | ||||||
| use redis_interface::RedisSettings; | use redis_interface::RedisSettings; | ||||||
| @ -69,6 +71,8 @@ pub struct Settings { | |||||||
|     pub connector_customer: ConnectorCustomer, |     pub connector_customer: ConnectorCustomer, | ||||||
|     #[cfg(feature = "dummy_connector")] |     #[cfg(feature = "dummy_connector")] | ||||||
|     pub dummy_connector: DummyConnector, |     pub dummy_connector: DummyConnector, | ||||||
|  |     #[cfg(feature = "email")] | ||||||
|  |     pub email: EmailSettings, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize, Clone, Default)] | #[derive(Debug, Deserialize, Clone, Default)] | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| use actix_web::{web, Scope}; | use actix_web::{web, Scope}; | ||||||
|  | #[cfg(feature = "email")] | ||||||
|  | use external_services::email::{AwsSes, EmailClient}; | ||||||
| use tokio::sync::oneshot; | use tokio::sync::oneshot; | ||||||
|  |  | ||||||
| #[cfg(feature = "dummy_connector")] | #[cfg(feature = "dummy_connector")] | ||||||
| @ -22,12 +24,16 @@ pub struct AppState { | |||||||
|     pub flow_name: String, |     pub flow_name: String, | ||||||
|     pub store: Box<dyn StorageInterface>, |     pub store: Box<dyn StorageInterface>, | ||||||
|     pub conf: Settings, |     pub conf: Settings, | ||||||
|  |     #[cfg(feature = "email")] | ||||||
|  |     pub email_client: Box<dyn EmailClient>, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub trait AppStateInfo { | pub trait AppStateInfo { | ||||||
|     fn conf(&self) -> Settings; |     fn conf(&self) -> Settings; | ||||||
|     fn flow_name(&self) -> String; |     fn flow_name(&self) -> String; | ||||||
|     fn store(&self) -> Box<dyn StorageInterface>; |     fn store(&self) -> Box<dyn StorageInterface>; | ||||||
|  |     #[cfg(feature = "email")] | ||||||
|  |     fn email_client(&self) -> Box<dyn EmailClient>; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl AppStateInfo for AppState { | impl AppStateInfo for AppState { | ||||||
| @ -40,6 +46,10 @@ impl AppStateInfo for AppState { | |||||||
|     fn store(&self) -> Box<dyn StorageInterface> { |     fn store(&self) -> Box<dyn StorageInterface> { | ||||||
|         self.store.to_owned() |         self.store.to_owned() | ||||||
|     } |     } | ||||||
|  |     #[cfg(feature = "email")] | ||||||
|  |     fn email_client(&self) -> Box<dyn EmailClient> { | ||||||
|  |         self.email_client.to_owned() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl AppState { | impl AppState { | ||||||
| @ -56,10 +66,15 @@ impl AppState { | |||||||
|             StorageImpl::Mock => Box::new(MockDb::new(&conf).await), |             StorageImpl::Mock => Box::new(MockDb::new(&conf).await), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         #[cfg(feature = "email")] | ||||||
|  |         #[allow(clippy::expect_used)] | ||||||
|  |         let email_client = Box::new(AwsSes::new(&conf.email).await); | ||||||
|         Self { |         Self { | ||||||
|             flow_name: String::from("default"), |             flow_name: String::from("default"), | ||||||
|             store, |             store, | ||||||
|             conf, |             conf, | ||||||
|  |             #[cfg(feature = "email")] | ||||||
|  |             email_client, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Chethan Rao
					Chethan Rao