5
5
Dotdigital helpers.
6
6
"""
7
7
8
- import os
8
+ import json
9
+ import logging
9
10
import typing as t
10
11
from dataclasses import dataclass
11
12
12
13
import requests
14
+ from django .conf import settings
13
15
16
+ from .types import JsonDict
14
17
15
- # pylint: disable-next=unused-argument
16
- def add_contact (email : str ):
17
- """Add a new contact to Dotdigital."""
18
- # TODO: implement
18
+
19
+ @dataclass
20
+ class Preference :
21
+ """The marketing preferences for a Dotdigital contact."""
22
+
23
+ @dataclass
24
+ class Preference :
25
+ """
26
+ The preference values to set in the category. Only supply if
27
+ is_preference is false, and therefore referring to a preference
28
+ category.
29
+ """
30
+
31
+ id : int
32
+ is_preference : bool
33
+ is_opted_in : bool
34
+
35
+ id : int
36
+ is_preference : bool
37
+ preferences : t .Optional [t .List [Preference ]] = None
38
+ is_opted_in : t .Optional [bool ] = None
39
+
40
+
41
+ # pylint: disable-next=too-many-arguments
42
+ def add_contact (
43
+ email : str ,
44
+ opt_in_type : t .Optional [
45
+ t .Literal ["Unknown" , "Single" , "Double" , "VerifiedDouble" ]
46
+ ] = None ,
47
+ email_type : t .Optional [t .Literal ["PlainText, Html" ]] = None ,
48
+ data_fields : t .Optional [t .Dict [str , str ]] = None ,
49
+ consent_fields : t .Optional [t .List [t .Dict [str , str ]]] = None ,
50
+ preferences : t .Optional [t .List [Preference ]] = None ,
51
+ region : str = "r1" ,
52
+ auth : t .Optional [str ] = None ,
53
+ timeout : int = 30 ,
54
+ ):
55
+ # pylint: disable=line-too-long
56
+ """Add a new contact to Dotdigital.
57
+
58
+ https://developer.dotdigital.com/reference/create-contact-with-consent-and-preferences
59
+
60
+ Args:
61
+ email: The email address of the contact.
62
+ opt_in_type: The opt-in type of the contact.
63
+ email_type: The email type of the contact.
64
+ data_fields: Each contact data field is a key-value pair; the key is a string matching the data field name in Dotdigital.
65
+ consent_fields: The consent fields that apply to the contact.
66
+ preferences: The marketing preferences to be applied.
67
+ region: The Dotdigital region id your account belongs to e.g. r1, r2 or r3.
68
+ auth: The authorization header used to enable API access. If None, the value will be retrieved from the MAIL_AUTH environment variable.
69
+ timeout: Send timeout to avoid hanging.
70
+
71
+ Raises:
72
+ AssertionError: If failed to add contact.
73
+ """
74
+ # pylint: enable=line-too-long
75
+
76
+ if auth is None :
77
+ auth = settings .MAIL_AUTH
78
+
79
+ contact : JsonDict = {"email" : email .lower ()}
80
+ if opt_in_type is not None :
81
+ contact ["optInType" ] = opt_in_type
82
+ if email_type is not None :
83
+ contact ["emailType" ] = email_type
84
+ if data_fields is not None :
85
+ contact ["dataFields" ] = [
86
+ {"key" : key , "value" : value } for key , value in data_fields .items ()
87
+ ]
88
+
89
+ body : JsonDict = {"contact" : contact }
90
+ if consent_fields is not None :
91
+ body ["consentFields" ] = [
92
+ {
93
+ "fields" : [
94
+ {"key" : key , "value" : value }
95
+ for key , value in fields .items ()
96
+ ]
97
+ }
98
+ for fields in consent_fields
99
+ ]
100
+ if preferences is not None :
101
+ body ["preferences" ] = [
102
+ {
103
+ "id" : preference .id ,
104
+ "isPreference" : preference .is_preference ,
105
+ ** (
106
+ {}
107
+ if preference .is_opted_in is None
108
+ else {"isOptedIn" : preference .is_opted_in }
109
+ ),
110
+ ** (
111
+ {}
112
+ if preference .preferences is None
113
+ else {
114
+ "preferences" : [
115
+ {
116
+ "id" : _preference .id ,
117
+ "isPreference" : _preference .is_preference ,
118
+ "isOptedIn" : _preference .is_opted_in ,
119
+ }
120
+ for _preference in preference .preferences
121
+ ]
122
+ }
123
+ ),
124
+ }
125
+ for preference in preferences
126
+ ]
127
+
128
+ if not settings .MAIL_ENABLED :
129
+ logging .info (
130
+ "Added contact to DotDigital:\n %s" , json .dumps (body , indent = 2 )
131
+ )
132
+ return
133
+
134
+ response = requests .post (
135
+ # pylint: disable-next=line-too-long
136
+ url = f"https://{ region } -api.dotdigital.com/v2/contacts/with-consent-and-preferences" ,
137
+ json = body ,
138
+ headers = {
139
+ "accept" : "application/json" ,
140
+ "authorization" : auth ,
141
+ },
142
+ timeout = timeout ,
143
+ )
144
+
145
+ assert response .ok , (
146
+ "Failed to add contact."
147
+ f" Reason: { response .reason } ."
148
+ f" Text: { response .text } ."
149
+ )
19
150
20
151
21
152
# pylint: disable-next=unused-argument
22
- def remove_contact (email : str ):
23
- """Remove an existing contact from Dotdigital."""
24
- # TODO: implement
153
+ def remove_contact (
154
+ contact_identifier : str ,
155
+ region : str = "r1" ,
156
+ auth : t .Optional [str ] = None ,
157
+ timeout : int = 30 ,
158
+ ):
159
+ # pylint: disable=line-too-long
160
+ """Remove an existing contact from Dotdigital.
161
+
162
+ https://developer.dotdigital.com/reference/get-contact
163
+ https://developer.dotdigital.com/reference/delete-contact
164
+
165
+ Args:
166
+ contact_identifier: Either the contact id or email address of the contact.
167
+ region: The Dotdigital region id your account belongs to e.g. r1, r2 or r3.
168
+ auth: The authorization header used to enable API access. If None, the value will be retrieved from the MAIL_AUTH environment variable.
169
+ timeout: Send timeout to avoid hanging.
170
+
171
+ Raises:
172
+ AssertionError: If failed to get contact.
173
+ AssertionError: If failed to delete contact.
174
+ """
175
+ # pylint: enable=line-too-long
176
+
177
+ if not settings .MAIL_ENABLED :
178
+ logging .info ("Removed contact from DotDigital: %s" , contact_identifier )
179
+ return
180
+
181
+ if auth is None :
182
+ auth = settings .MAIL_AUTH
183
+
184
+ response = requests .get (
185
+ # pylint: disable-next=line-too-long
186
+ url = f"https://{ region } -api.dotdigital.com/v2/contacts/{ contact_identifier } " ,
187
+ headers = {
188
+ "accept" : "application/json" ,
189
+ "authorization" : auth ,
190
+ },
191
+ timeout = timeout ,
192
+ )
193
+
194
+ assert response .ok , (
195
+ "Failed to get contact."
196
+ f" Reason: { response .reason } ."
197
+ f" Text: { response .text } ."
198
+ )
199
+
200
+ contact_id : int = response .json ()["id" ]
201
+
202
+ response = requests .delete (
203
+ url = f"https://{ region } -api.dotdigital.com/v2/contacts/{ contact_id } " ,
204
+ headers = {
205
+ "accept" : "application/json" ,
206
+ "authorization" : auth ,
207
+ },
208
+ timeout = timeout ,
209
+ )
210
+
211
+ assert response .ok , (
212
+ "Failed to delete contact."
213
+ f" Reason: { response .reason } ."
214
+ f" Text: { response .text } ."
215
+ )
25
216
26
217
27
218
@dataclass
@@ -62,7 +253,7 @@ def send_mail(
62
253
metadata: The metadata for your email. It can be either a single value or a series of values in a JSON object.
63
254
attachments: A Base64 encoded string. All attachment types are supported. Maximum file size: 15 MB.
64
255
region: The Dotdigital region id your account belongs to e.g. r1, r2 or r3.
65
- auth: The authorization header used to enable API access. If None, the value will be retrieved from the DOTDIGITAL_AUTH environment variable.
256
+ auth: The authorization header used to enable API access. If None, the value will be retrieved from the MAIL_AUTH environment variable.
66
257
timeout: Send timeout to avoid hanging.
67
258
68
259
Raises:
@@ -71,7 +262,7 @@ def send_mail(
71
262
# pylint: enable=line-too-long
72
263
73
264
if auth is None :
74
- auth = os . environ [ "DOTDIGITAL_AUTH" ]
265
+ auth = settings . MAIL_AUTH
75
266
76
267
body = {
77
268
"campaignId" : campaign_id ,
@@ -103,6 +294,13 @@ def send_mail(
103
294
for attachment in attachments
104
295
]
105
296
297
+ if not settings .MAIL_ENABLED :
298
+ logging .info (
299
+ "Sent a triggered email with DotDigital:\n %s" ,
300
+ json .dumps (body , indent = 2 ),
301
+ )
302
+ return
303
+
106
304
response = requests .post (
107
305
url = f"https://{ region } -api.dotdigital.com/v2/email/triggered-campaign" ,
108
306
json = body ,
0 commit comments