1+ from typing import Optional , Dict , Any , Union , List
2+ from pathlib import Path
3+
4+ from mailbridge .dto .email_message_dto import EmailMessageDto
5+ from mailbridge .dto .email_response_dto import EmailResponseDTO
6+ from mailbridge .dto .bulk_email_dto import BulkEmailDTO
7+ from mailbridge .dto .bulk_email_response_dto import BulkEmailResponseDTO
8+ from mailbridge .exceptions import ProviderNotFoundError
9+ from mailbridge .providers .base_email_provider import BaseEmailProvider
10+ from mailbridge .providers .brevo_provider import BrevoProvider
11+ from mailbridge .providers .mailgun_provider import MailgunProvider
12+ from mailbridge .providers .postmark_provider import PostmarkProvider
13+ from mailbridge .providers .sendgrid_provider import SendGridProvider
14+ from mailbridge .providers .ses_provider import SESProvider
15+ from mailbridge .providers .smtp_provider import SMTPProvider
16+
17+ class MailBridge :
18+
19+ PROVIDERS = {
20+ 'smtp' : SMTPProvider ,
21+ 'sendgrid' : SendGridProvider ,
22+ 'mailgun' : MailgunProvider ,
23+ 'ses' : SESProvider ,
24+ 'postmark' : PostmarkProvider ,
25+ 'brevo' : BrevoProvider ,
26+ }
27+
28+ def __init__ (self , provider : str , ** config ):
29+ self .provider_name = provider .lower ()
30+ if self .provider_name not in self .PROVIDERS :
31+ available = ', ' .join (self .PROVIDERS .keys ())
32+ raise ProviderNotFoundError (
33+ f"Provider '{ provider } ' not found. Available providers: { available } "
34+ )
35+
36+ provider_class = self .PROVIDERS [self .provider_name ]
37+ self .provider : BaseEmailProvider = provider_class (** config )
38+
39+ def send (
40+ self ,
41+ to : Union [str , List [str ]],
42+ subject : str ,
43+ body : str ,
44+ from_email : Optional [str ] = None ,
45+ cc : Optional [Union [str , List [str ]]] = None ,
46+ bcc : Optional [Union [str , List [str ]]] = None ,
47+ reply_to : Optional [str ] = None ,
48+ attachments : Optional [List [Union [Path , tuple ]]] = None ,
49+ html : bool = True ,
50+ headers : Optional [Dict [str , str ]] = None ,
51+ template_id : Optional [str ] = None ,
52+ template_data : Optional [Dict [str , Any ]] = None ,
53+ tags : Optional [List [str ]] = None
54+ ) -> EmailResponseDTO :
55+ """
56+ Send an email.
57+
58+ Args:
59+ to: Recipient email address(es)
60+ subject: Email subject
61+ body: Email body (HTML or plain text)
62+ from_email: Sender email address (optional if set in config)
63+ cc: CC recipients (optional)
64+ bcc: BCC recipients (optional)
65+ reply_to: Reply-To address (optional)
66+ attachments: List of file paths or tuples (filename, content, mimetype)
67+ html: Whether body is HTML (default: True)
68+ headers: Custom headers (optional)
69+ template_id: Template id (optional)
70+ template_data: Template varibales (optional)
71+ tags: Tags (optional)
72+
73+ Returns:
74+ Dict with response data (success, message_id, etc.)
75+
76+ Raises:
77+ EmailSendError: If sending fails
78+
79+ Examples:
80+ >>> mailer.send(
81+ ... to='user@example.com',
82+ ... subject='Welcome',
83+ ... body='<h1>Hello!</h1>'
84+ ... )
85+
86+ >>> mailer.send(
87+ ... to=['user1@example.com', 'user2@example.com'],
88+ ... subject='Newsletter',
89+ ... body='Content here',
90+ ... attachments=[Path('report.pdf')]
91+ ... )
92+ """
93+ message = EmailMessageDto (
94+ to = to ,
95+ subject = subject ,
96+ body = body ,
97+ from_email = from_email ,
98+ cc = cc ,
99+ bcc = bcc ,
100+ reply_to = reply_to ,
101+ attachments = attachments ,
102+ html = html ,
103+ headers = headers ,
104+ template_id = template_id ,
105+ template_data = template_data ,
106+ tags = tags
107+ )
108+
109+ return self .provider .send (message )
110+
111+ def send_bulk (
112+ self ,
113+ messages : Union [List [EmailMessageDto ], BulkEmailDTO ],
114+ default_from : str = None ,
115+ tags : List [str ] = None
116+ ) -> BulkEmailResponseDTO :
117+ """
118+ Send multiple emails at once.
119+
120+ Args:
121+ messages: List of EmailMessageDto or BulkEmailDTO
122+ default_from: Default sender email (optional)
123+ tags: Common tags for all messages (optional)
124+
125+ Returns:
126+ BulkEmailResponseDTO with results
127+
128+ Example:
129+ messages = [
130+ EmailMessageDto(to='user1@example.com', subject='Hi', body='Hello 1'),
131+ EmailMessageDto(to='user2@example.com', subject='Hi', body='Hello 2'),
132+ ]
133+
134+ result = mailer.send_bulk(messages)
135+ print(f"Sent: {result.successful}/{result.total}")
136+ """
137+ # If already a BulkEmailDTO, use it directly
138+ if isinstance (messages , BulkEmailDTO ):
139+ return self .provider .send_bulk (messages )
140+
141+ # Otherwise, create BulkEmailDTO from list
142+ bulk = BulkEmailDTO (
143+ messages = messages ,
144+ default_from = default_from ,
145+ tags = tags
146+ )
147+
148+ return self .provider .send_bulk (bulk )
149+
150+ def supports_templates (self ) -> bool :
151+ """
152+ Check if current provider supports template emails.
153+
154+ Returns:
155+ True if templates are supported, False otherwise
156+ """
157+ return self .provider .supports_templates ()
158+
159+ def supports_bulk_sending (self ) -> bool :
160+ """
161+ Check if current provider has native bulk sending API.
162+
163+ Returns:
164+ True if native bulk API exists, False otherwise
165+ """
166+ return self .provider .supports_bulk_sending ()
167+
168+ def __enter__ (self ):
169+ """Context manager entry."""
170+ return self
171+
172+ def __exit__ (self , exc_type , exc_val , exc_tb ):
173+ """Context manager exit."""
174+ self .provider .close ()
175+
176+ def close (self ):
177+ """Close provider connection."""
178+ self .provider .close ()
179+
180+ @classmethod
181+ def register_provider (cls , name : str , provider_class : type ):
182+ """
183+ Register a custom provider.
184+
185+ Args:
186+ name: Provider name
187+ provider_class: Provider class (must inherit from BaseEmailProvider)
188+
189+ Example:
190+ >>> class MyProvider(BaseEmailProvider):
191+ ... def send(self, message):
192+ ... # Implementation
193+ ... pass
194+ >>> MailBridge.register_provider('myprovider', MyProvider)
195+ """
196+ if not issubclass (provider_class , BaseEmailProvider ):
197+ raise TypeError (f"{ provider_class } must inherit from EmailProvider" )
198+ cls .PROVIDERS [name .lower ()] = provider_class
199+
200+ @classmethod
201+ def available_providers (cls ) -> List [str ]:
202+ """
203+ Get list of available providers.
204+
205+ Returns:
206+ List of provider names
207+ """
208+ return list (cls .PROVIDERS .keys ())
0 commit comments