Skip to content

Commit d35e23d

Browse files
authored
Merge pull request #10 from level12/9-accept-additional-mail-options
Add ability to insert additional mail options for mailgun
2 parents 5552cd2 + ee355fd commit d35e23d

File tree

6 files changed

+116
-10
lines changed

6 files changed

+116
-10
lines changed

README.rst

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Usage
1616
$ pip install keg-mail
1717
1818
19-
Initialize Keg-Mail in you application
19+
Initialize Keg-Mail in your application
2020

2121
.. code::
2222
@@ -65,8 +65,8 @@ Send the email
6565
6666
bp.route('/')
6767
def index():
68-
mail.send(
69-
'you@something.com'
68+
mail.send_email(
69+
'you@something.com',
7070
keg_mail.Email(
7171
subject="Hello {name}!",
7272
content=emails.hello_world_content,
@@ -86,3 +86,21 @@ Test the email
8686
assert len(outbox) == 1
8787
assert outbox[0].subject == "Hello You!"
8888
assert outbox[0].body == "Hello You!"
89+
90+
91+
Mailgun-specific options
92+
------------------------
93+
94+
Mailgun supports various options such as tagging, user-defined variables, etc.
95+
These can be added via a ``mailgun_opts`` dictionary that can be passed to the
96+
app's mail engine's ``send`` method directly:
97+
98+
.. code::
99+
100+
flask.current_app.mail.send(
101+
msg,
102+
mailgun_opts={
103+
'v:user_name': 'John Doe',
104+
'v:user_id': 100,
105+
},
106+
)

keg_mail/content.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def escape_html(content):
2727
return EmailContent(html=_escape_html(content), text=content)
2828

2929

30-
def send_email(recipient, email):
30+
def send_email(recipient, email, **kwargs):
3131
"""Sends an `Email` to a recipient.
3232
3333
:param recipient: is an email address.
@@ -49,7 +49,7 @@ def send_email(recipient, email):
4949
)
5050

5151
try:
52-
mail_engine.send(message)
52+
mail_engine.send(message, **kwargs)
5353
except (socket.gaierror, socket.error): # pragma: no cover
5454
raise SendEmailError('SMTP Connection error')
5555
except smtplib.SMTPAuthenticationError: # pragma: no cover

keg_mail/mailgun.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def _ensure_unique_recipients(self, to: List[str], cc: List[str], bcc: List[str]
117117

118118
return list(to), list(cc), list(bcc)
119119

120-
def send(self, message: flask_mail.Message):
120+
def send(self, message: flask_mail.Message, mailgun_opts: Dict = None):
121121
to, cc, bcc = self._ensure_unique_recipients(
122122
ensure_list(message.recipients),
123123
ensure_list(message.cc),
@@ -136,6 +136,8 @@ def send(self, message: flask_mail.Message):
136136
'o:testmode': 'yes' if self.testing else 'no'
137137
}
138138

139+
request_body.update(mailgun_opts or {})
140+
139141
files = None
140142
if message.attachments:
141143
files = [

keg_mail/plugin.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919

2020
class _MailMixin(flask_mail._MailMixin):
21-
def mailgun_send(self, message):
22-
return self.mailgun_client.send(message)
21+
def mailgun_send(self, message, mailgun_opts=None):
22+
return self.mailgun_client.send(message, mailgun_opts)
2323

2424
def mailgun_sync_webhooks(self):
2525
return self.mailgun_client.sync_webhooks(self.mailgun_webhooks)
@@ -55,9 +55,10 @@ def mailgun_update_message_status(self, event_data):
5555
return
5656
self.mailgun_client.update_message_status(event_data)
5757

58-
def send(self, message):
58+
def send(self, message, **kwargs):
5959
if self.mailgun_client:
60-
return self.mailgun_send(message)
60+
mailgun_opts = kwargs.pop('mailgun_opts', None)
61+
return self.mailgun_send(message, mailgun_opts)
6162

6263
if message.extra_headers is None:
6364
message.extra_headers = {}

keg_mail/tests/test_mailgun.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,31 @@ def test_send(self, requests_mock):
5353
assert body['html'] == ['<blink>very important message</blink>']
5454
assert 'attachment' not in body
5555

56+
def test_send_with_mailgun_opts(self, requests_mock):
57+
requests_mock.post('https://api.mailgun.net/v3/example.com/messages',
58+
json={'response': 'ok'})
59+
60+
client = MailgunClient(domain='example.com', api_key='foo', testing=False)
61+
message = flask_mail.Message(
62+
subject='RE: xyz',
63+
recipients=['bob@example.com', 'joe@example.com'],
64+
body='very important message',
65+
html='<blink>very important message</blink>',
66+
)
67+
mailgun_opts = {'o:tag': ['one_tag', 'another_tag']}
68+
resp = client.send(message, mailgun_opts=mailgun_opts)
69+
70+
assert resp == {'response': 'ok'}
71+
72+
assert requests_mock.call_count == 1
73+
74+
req = requests_mock.request_history[0]
75+
body = urllib.parse.parse_qs(req.text)
76+
77+
assert body['to'] == ['bob@example.com', 'joe@example.com']
78+
assert body['o:tag'] == ['one_tag', 'another_tag']
79+
assert 'attachment' not in body
80+
5681
def test_send_attachments(self, requests_mock):
5782
requests_mock.post('https://api.mailgun.net/v3/example.com/messages',
5883
json={'response': 'ok'})

keg_mail/tests/test_plugin.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from unittest import mock
2+
3+
import flask_mail
4+
5+
from keg_mail.mailgun import MailgunClient
6+
from keg_mail.plugin import _MailMixin
7+
8+
9+
def mock_patch(*args, **kwargs):
10+
kwargs.setdefault('autospec', False)
11+
kwargs.setdefault('spec_set', False)
12+
return mock.patch(*args, **kwargs)
13+
14+
15+
class TestKegMailMixin:
16+
def setup_method(self):
17+
client = MailgunClient(domain="keg.example.com", api_key="foo", testing=True)
18+
self.plugin = _MailMixin()
19+
self.plugin.mailgun_client = client
20+
self.message = flask_mail.Message(
21+
subject="binford tools",
22+
recipients=["bob@example.com", "joe@example.com"],
23+
body="more power",
24+
html="<blink>more power</blink>",
25+
sender="no-reply@binford.com",
26+
cc=["tim@example.com"],
27+
bcc=["al@example.com"],
28+
)
29+
30+
@mock_patch("keg_mail.mailgun.MailgunClient.send")
31+
def test_mailgun_send_no_options(self, m_send):
32+
self.plugin.mailgun_send(self.message)
33+
assert m_send.call_args[0][0] is self.message
34+
assert not m_send.call_args[0][1]
35+
36+
@mock_patch("keg_mail.mailgun.MailgunClient.send")
37+
def test_mailgun_send_with_options(self, m_send):
38+
mailgun_opt = {'opt1': 'bar'}
39+
self.plugin.mailgun_send(self.message, mailgun_opts=mailgun_opt)
40+
assert m_send.call_args[0][0] is self.message
41+
assert m_send.call_args[0][1] == mailgun_opt
42+
43+
@mock_patch("keg_mail.plugin._MailMixin.mailgun_send")
44+
def test_plugin_send_mailgun_client_no_options(self, m_send):
45+
self.plugin.send(self.message)
46+
assert m_send.call_args[0][0] is self.message
47+
assert not m_send.call_args[0][1]
48+
49+
@mock_patch("keg_mail.plugin._MailMixin.mailgun_send")
50+
def test_plugin_send_mailgun_client_with_options(self, m_send):
51+
mailgun_opt = {'opt1': 'bar'}
52+
self.plugin.send(self.message, mailgun_opts=mailgun_opt)
53+
assert self.message is m_send.call_args[0][0]
54+
assert m_send.call_args[0][1] == mailgun_opt
55+
56+
@mock_patch("keg_mail.plugin._MailMixin.mailgun_send")
57+
def test_plugin_send_no_mailgun_client(self, m_send):
58+
self.plugin.mailgun_client = None
59+
self.plugin.send(self.message)
60+
assert not m_send.called

0 commit comments

Comments
 (0)