Skip to content

Commit ec9fbce

Browse files
committed
feat: CC and BCC accept string and array
1 parent 8f2bf90 commit ec9fbce

File tree

3 files changed

+244
-12
lines changed

3 files changed

+244
-12
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,8 +657,14 @@ ms = MailerSendClient()
657657
email = (EmailBuilder()
658658
.from_email("sender@domain.com", "Your Name")
659659
.to_many([{"email": "recipient@domain.com", "name": "Recipient"}])
660-
.cc("cc@domain.com", "CC Recipient")
661-
.bcc("bcc@domain.com", "BCC Recipient")
660+
.cc([
661+
{"email": "cc1@example.com", "name": "CC User 1"},
662+
{"email": "cc2@example.com", "name": "CC User 2"}
663+
])
664+
.bcc([
665+
{"email": "bcc1@example.com", "name": "BCC User 1"},
666+
{"email": "bcc2@example.com"}
667+
])
662668
.subject("Hello with CC/BCC!")
663669
.html("<h1>Hello World!</h1>")
664670
.build())

mailersend/builders/email.py

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ class EmailBuilder:
4545
... .to("user1@example.com", "John Doe")
4646
... .to("user2@example.com", "Jane Smith")
4747
... .cc("manager@example.com")
48+
... .bcc([
49+
... {"email": "analytics@example.com", "name": "Analytics Team"},
50+
... {"email": "backup@example.com"}
51+
... ])
4852
... .subject("Monthly Newsletter")
4953
... .html_file("templates/newsletter.html")
5054
... .attach_file("documents/report.pdf")
@@ -126,32 +130,112 @@ def to_many(self, recipients: List[Dict[str, str]]) -> "EmailBuilder":
126130
)
127131
return self
128132

129-
def cc(self, email: str, name: Optional[str] = None) -> "EmailBuilder":
133+
def cc(self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None) -> "EmailBuilder":
130134
"""
131-
Add a recipient to the CC field.
135+
Add recipient(s) to the CC field.
132136
133137
Args:
134-
email: CC recipient email address
135-
name: Optional recipient name
138+
email: CC recipient email address (string) or list of recipient objects
139+
name: Optional recipient name (only used when email is a string)
136140
137141
Returns:
138142
EmailBuilder instance for chaining
143+
144+
Examples:
145+
Single recipient:
146+
>>> builder.cc("cc@example.com", "CC User")
147+
148+
Multiple recipients:
149+
>>> builder.cc([
150+
... {"email": "cc1@example.com", "name": "CC User 1"},
151+
... {"email": "cc2@example.com", "name": "CC User 2"}
152+
... ])
139153
"""
140-
self._cc.append(EmailContact(email=email, name=name))
154+
if isinstance(email, str):
155+
self._cc.append(EmailContact(email=email, name=name))
156+
elif isinstance(email, list):
157+
for recipient in email:
158+
self._cc.append(
159+
EmailContact(email=recipient["email"], name=recipient.get("name"))
160+
)
161+
else:
162+
raise ValidationError("Email must be a string or list of recipient objects")
141163
return self
142164

143-
def bcc(self, email: str, name: Optional[str] = None) -> "EmailBuilder":
165+
def bcc(self, email: Union[str, List[Dict[str, str]]], name: Optional[str] = None) -> "EmailBuilder":
144166
"""
145-
Add a recipient to the BCC field.
167+
Add recipient(s) to the BCC field.
146168
147169
Args:
148-
email: BCC recipient email address
149-
name: Optional recipient name
170+
email: BCC recipient email address (string) or list of recipient objects
171+
name: Optional recipient name (only used when email is a string)
172+
173+
Returns:
174+
EmailBuilder instance for chaining
175+
176+
Examples:
177+
Single recipient:
178+
>>> builder.bcc("bcc@example.com", "BCC User")
179+
180+
Multiple recipients:
181+
>>> builder.bcc([
182+
... {"email": "bcc1@example.com", "name": "BCC User 1"},
183+
... {"email": "bcc2@example.com", "name": "BCC User 2"}
184+
... ])
185+
"""
186+
if isinstance(email, str):
187+
self._bcc.append(EmailContact(email=email, name=name))
188+
elif isinstance(email, list):
189+
for recipient in email:
190+
self._bcc.append(
191+
EmailContact(email=recipient["email"], name=recipient.get("name"))
192+
)
193+
else:
194+
raise ValidationError("Email must be a string or list of recipient objects")
195+
return self
196+
197+
def cc_many(self, recipients: List[Dict[str, str]]) -> "EmailBuilder":
198+
"""
199+
Add multiple recipients to the CC field.
200+
201+
Args:
202+
recipients: List of dicts with 'email' and optional 'name' keys
203+
204+
Returns:
205+
EmailBuilder instance for chaining
206+
207+
Example:
208+
>>> builder.cc_many([
209+
... {"email": "cc1@example.com", "name": "CC User 1"},
210+
... {"email": "cc2@example.com", "name": "CC User 2"}
211+
... ])
212+
"""
213+
for recipient in recipients:
214+
self._cc.append(
215+
EmailContact(email=recipient["email"], name=recipient.get("name"))
216+
)
217+
return self
218+
219+
def bcc_many(self, recipients: List[Dict[str, str]]) -> "EmailBuilder":
220+
"""
221+
Add multiple recipients to the BCC field.
222+
223+
Args:
224+
recipients: List of dicts with 'email' and optional 'name' keys
150225
151226
Returns:
152227
EmailBuilder instance for chaining
228+
229+
Example:
230+
>>> builder.bcc_many([
231+
... {"email": "bcc1@example.com", "name": "BCC User 1"},
232+
... {"email": "bcc2@example.com", "name": "BCC User 2"}
233+
... ])
153234
"""
154-
self._bcc.append(EmailContact(email=email, name=name))
235+
for recipient in recipients:
236+
self._bcc.append(
237+
EmailContact(email=recipient["email"], name=recipient.get("name"))
238+
)
155239
return self
156240

157241
def reply_to(self, email: str, name: Optional[str] = None) -> "EmailBuilder":

tests/unit/test_email_builder.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,148 @@ def test_to_many_recipients(self):
8787
assert email.to[2].email == "user3@example.com"
8888
assert email.to[2].name is None
8989

90+
def test_cc_array_functionality(self):
91+
"""Test CC with array input"""
92+
cc_recipients = [
93+
{"email": "cc1@example.com", "name": "CC User 1"},
94+
{"email": "cc2@example.com", "name": "CC User 2"},
95+
{"email": "cc3@example.com"}, # No name
96+
]
97+
98+
email = (
99+
EmailBuilder()
100+
.from_email("sender@example.com")
101+
.to("recipient@example.com")
102+
.cc(cc_recipients)
103+
.subject("CC Array Test")
104+
.text("Test message")
105+
.build()
106+
)
107+
108+
assert len(email.cc) == 3
109+
assert email.cc[0].email == "cc1@example.com"
110+
assert email.cc[0].name == "CC User 1"
111+
assert email.cc[1].email == "cc2@example.com"
112+
assert email.cc[1].name == "CC User 2"
113+
assert email.cc[2].email == "cc3@example.com"
114+
assert email.cc[2].name is None
115+
116+
def test_bcc_array_functionality(self):
117+
"""Test BCC with array input"""
118+
bcc_recipients = [
119+
{"email": "bcc1@example.com", "name": "BCC User 1"},
120+
{"email": "bcc2@example.com", "name": "BCC User 2"},
121+
{"email": "bcc3@example.com"}, # No name
122+
]
123+
124+
email = (
125+
EmailBuilder()
126+
.from_email("sender@example.com")
127+
.to("recipient@example.com")
128+
.bcc(bcc_recipients)
129+
.subject("BCC Array Test")
130+
.text("Test message")
131+
.build()
132+
)
133+
134+
assert len(email.bcc) == 3
135+
assert email.bcc[0].email == "bcc1@example.com"
136+
assert email.bcc[0].name == "BCC User 1"
137+
assert email.bcc[1].email == "bcc2@example.com"
138+
assert email.bcc[1].name == "BCC User 2"
139+
assert email.bcc[2].email == "bcc3@example.com"
140+
assert email.bcc[2].name is None
141+
142+
def test_cc_many_method(self):
143+
"""Test cc_many method"""
144+
cc_recipients = [
145+
{"email": "cc1@example.com", "name": "CC User 1"},
146+
{"email": "cc2@example.com", "name": "CC User 2"},
147+
]
148+
149+
email = (
150+
EmailBuilder()
151+
.from_email("sender@example.com")
152+
.to("recipient@example.com")
153+
.cc_many(cc_recipients)
154+
.subject("CC Many Test")
155+
.text("Test message")
156+
.build()
157+
)
158+
159+
assert len(email.cc) == 2
160+
assert email.cc[0].email == "cc1@example.com"
161+
assert email.cc[0].name == "CC User 1"
162+
assert email.cc[1].email == "cc2@example.com"
163+
assert email.cc[1].name == "CC User 2"
164+
165+
def test_bcc_many_method(self):
166+
"""Test bcc_many method"""
167+
bcc_recipients = [
168+
{"email": "bcc1@example.com", "name": "BCC User 1"},
169+
{"email": "bcc2@example.com", "name": "BCC User 2"},
170+
]
171+
172+
email = (
173+
EmailBuilder()
174+
.from_email("sender@example.com")
175+
.to("recipient@example.com")
176+
.bcc_many(bcc_recipients)
177+
.subject("BCC Many Test")
178+
.text("Test message")
179+
.build()
180+
)
181+
182+
assert len(email.bcc) == 2
183+
assert email.bcc[0].email == "bcc1@example.com"
184+
assert email.bcc[0].name == "BCC User 1"
185+
assert email.bcc[1].email == "bcc2@example.com"
186+
assert email.bcc[1].name == "BCC User 2"
187+
188+
def test_cc_bcc_mixed_usage(self):
189+
"""Test mixing single and array CC/BCC methods"""
190+
email = (
191+
EmailBuilder()
192+
.from_email("sender@example.com")
193+
.to("recipient@example.com")
194+
.cc("single-cc@example.com", "Single CC")
195+
.cc([{"email": "array-cc1@example.com", "name": "Array CC 1"}])
196+
.bcc("single-bcc@example.com")
197+
.bcc_many([
198+
{"email": "many-bcc1@example.com", "name": "Many BCC 1"},
199+
{"email": "many-bcc2@example.com"}
200+
])
201+
.subject("Mixed Usage Test")
202+
.text("Test message")
203+
.build()
204+
)
205+
206+
assert len(email.cc) == 2
207+
assert email.cc[0].email == "single-cc@example.com"
208+
assert email.cc[0].name == "Single CC"
209+
assert email.cc[1].email == "array-cc1@example.com"
210+
assert email.cc[1].name == "Array CC 1"
211+
212+
assert len(email.bcc) == 3
213+
assert email.bcc[0].email == "single-bcc@example.com"
214+
assert email.bcc[0].name is None
215+
assert email.bcc[1].email == "many-bcc1@example.com"
216+
assert email.bcc[1].name == "Many BCC 1"
217+
assert email.bcc[2].email == "many-bcc2@example.com"
218+
assert email.bcc[2].name is None
219+
220+
def test_cc_bcc_invalid_input_validation(self):
221+
"""Test error handling for invalid CC/BCC input types"""
222+
builder = EmailBuilder()
223+
224+
# Test invalid type for cc method
225+
with pytest.raises(ValidationError, match="Email must be a string or list of recipient objects"):
226+
builder.cc(123) # Invalid type
227+
228+
# Test invalid type for bcc method
229+
with pytest.raises(ValidationError, match="Email must be a string or list of recipient objects"):
230+
builder.bcc({"email": "test@example.com"}) # Dict instead of string or list
231+
90232
def test_content_methods(self):
91233
"""Test different content setting methods"""
92234
email = (

0 commit comments

Comments
 (0)