python-missive 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. pymissive/__init__.py +3 -0
  2. pymissive/__main__.py +9 -0
  3. pymissive/archives/__init__.py +142 -0
  4. pymissive/archives/__main__.py +9 -0
  5. pymissive/archives/address.py +272 -0
  6. pymissive/archives/address_backends/__init__.py +29 -0
  7. pymissive/archives/address_backends/base.py +610 -0
  8. pymissive/archives/address_backends/geoapify.py +221 -0
  9. pymissive/archives/address_backends/geocode_earth.py +210 -0
  10. pymissive/archives/address_backends/google_maps.py +371 -0
  11. pymissive/archives/address_backends/here.py +348 -0
  12. pymissive/archives/address_backends/locationiq.py +271 -0
  13. pymissive/archives/address_backends/mapbox.py +314 -0
  14. pymissive/archives/address_backends/maps_co.py +257 -0
  15. pymissive/archives/address_backends/nominatim.py +348 -0
  16. pymissive/archives/address_backends/opencage.py +292 -0
  17. pymissive/archives/address_backends/pelias_mixin.py +181 -0
  18. pymissive/archives/address_backends/photon.py +322 -0
  19. pymissive/archives/cli.py +42 -0
  20. pymissive/archives/helpers.py +45 -0
  21. pymissive/archives/missive.py +64 -0
  22. pymissive/archives/providers/__init__.py +167 -0
  23. pymissive/archives/providers/apn.py +171 -0
  24. pymissive/archives/providers/ar24.py +204 -0
  25. pymissive/archives/providers/base/__init__.py +203 -0
  26. pymissive/archives/providers/base/_attachments.py +166 -0
  27. pymissive/archives/providers/base/branded.py +341 -0
  28. pymissive/archives/providers/base/common.py +781 -0
  29. pymissive/archives/providers/base/email.py +422 -0
  30. pymissive/archives/providers/base/email_message.py +85 -0
  31. pymissive/archives/providers/base/monitoring.py +150 -0
  32. pymissive/archives/providers/base/notification.py +187 -0
  33. pymissive/archives/providers/base/postal.py +742 -0
  34. pymissive/archives/providers/base/postal_defaults.py +26 -0
  35. pymissive/archives/providers/base/sms.py +213 -0
  36. pymissive/archives/providers/base/voice_call.py +82 -0
  37. pymissive/archives/providers/brevo.py +363 -0
  38. pymissive/archives/providers/certeurope.py +249 -0
  39. pymissive/archives/providers/django_email.py +182 -0
  40. pymissive/archives/providers/fcm.py +91 -0
  41. pymissive/archives/providers/laposte.py +392 -0
  42. pymissive/archives/providers/maileva.py +511 -0
  43. pymissive/archives/providers/mailgun.py +118 -0
  44. pymissive/archives/providers/messenger.py +74 -0
  45. pymissive/archives/providers/notification.py +112 -0
  46. pymissive/archives/providers/sendgrid.py +160 -0
  47. pymissive/archives/providers/ses.py +185 -0
  48. pymissive/archives/providers/signal.py +68 -0
  49. pymissive/archives/providers/slack.py +80 -0
  50. pymissive/archives/providers/smtp.py +190 -0
  51. pymissive/archives/providers/teams.py +91 -0
  52. pymissive/archives/providers/telegram.py +69 -0
  53. pymissive/archives/providers/twilio.py +310 -0
  54. pymissive/archives/providers/vonage.py +208 -0
  55. pymissive/archives/sender.py +339 -0
  56. pymissive/archives/status.py +22 -0
  57. pymissive/cli.py +42 -0
  58. pymissive/config.py +397 -0
  59. pymissive/helpers.py +0 -0
  60. pymissive/providers/apn.py +8 -0
  61. pymissive/providers/ar24.py +8 -0
  62. pymissive/providers/base/__init__.py +64 -0
  63. pymissive/providers/base/acknowledgement.py +6 -0
  64. pymissive/providers/base/attachments.py +10 -0
  65. pymissive/providers/base/branded.py +16 -0
  66. pymissive/providers/base/email.py +2 -0
  67. pymissive/providers/base/notification.py +2 -0
  68. pymissive/providers/base/postal.py +2 -0
  69. pymissive/providers/base/sms.py +2 -0
  70. pymissive/providers/base/voice_call.py +2 -0
  71. pymissive/providers/brevo.py +420 -0
  72. pymissive/providers/certeurope.py +8 -0
  73. pymissive/providers/django_email.py +8 -0
  74. pymissive/providers/fcm.py +8 -0
  75. pymissive/providers/laposte.py +8 -0
  76. pymissive/providers/maileva.py +8 -0
  77. pymissive/providers/mailgun.py +8 -0
  78. pymissive/providers/messenger.py +8 -0
  79. pymissive/providers/notification.py +8 -0
  80. pymissive/providers/partner.py +650 -0
  81. pymissive/providers/scaleway.py +498 -0
  82. pymissive/providers/sendgrid.py +8 -0
  83. pymissive/providers/ses.py +8 -0
  84. pymissive/providers/signal.py +8 -0
  85. pymissive/providers/slack.py +8 -0
  86. pymissive/providers/smtp.py +8 -0
  87. pymissive/providers/teams.py +8 -0
  88. pymissive/providers/telegram.py +8 -0
  89. pymissive/providers/twilio.py +8 -0
  90. pymissive/providers/vonage.py +8 -0
  91. python_missive-0.2.0.dist-info/METADATA +152 -0
  92. python_missive-0.2.0.dist-info/RECORD +95 -0
  93. python_missive-0.2.0.dist-info/WHEEL +5 -0
  94. python_missive-0.2.0.dist-info/entry_points.txt +2 -0
  95. python_missive-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,420 @@
1
+ """Brevo (ex Sendinblue) email provider - API and SMTP modes."""
2
+
3
+ import contextlib
4
+ import json
5
+ from typing import Any
6
+ from .base import MissiveProviderBase
7
+
8
+
9
+ class BrevoAPIProvider(MissiveProviderBase):
10
+ """Abstract base class for Brevo providers."""
11
+
12
+ # -----------------------
13
+ # Metadata / configuration
14
+ # -----------------------
15
+ name = "brevo"
16
+ display_name = "Brevo"
17
+ required_packages = ["brevo-python"]
18
+ config_keys = ["EMAIL_API_KEY", "SMS_API_KEY", "WHATSAPP_API_KEY"]
19
+ description = "Complete CRM platform (Email, SMS, Marketing automation)"
20
+ documentation_url = "https://developers.brevo.com"
21
+ site_url = "https://www.brevo.com"
22
+ brands = ["WhatsApp"]
23
+ fields_associations = {
24
+ "id": "id",
25
+ "url": "url",
26
+ "type": "type",
27
+ "description": "description",
28
+ "created_at": "createdAt",
29
+ "updated_at": "updatedAt",
30
+ }
31
+ events_association = {
32
+ "request": "request",
33
+ "sent": "sent",
34
+ "hardBounce": "hard_bounce",
35
+ "softBounce": "soft_bounce",
36
+ "blocked": "blocked",
37
+ "spam": "spam",
38
+ "delivered": "delivered",
39
+ "click": "click",
40
+ "invalid": "invalid",
41
+ "deferred": "deferred",
42
+ "opened": "opened",
43
+ }
44
+
45
+ #########################################################
46
+ # Initialization / API clients
47
+ #########################################################
48
+
49
+ def __init__(self, **kwargs: str | None) -> None:
50
+ super().__init__(**kwargs)
51
+ if not hasattr(self, "attachments"):
52
+ self.attachments = []
53
+ self._email_api_key = self._get_config_or_env("EMAIL_API_KEY")
54
+ self._sms_api_key = self._get_config_or_env("SMS_API_KEY")
55
+ self._whatsapp_api_key = self._get_config_or_env("WHATSAPP_API_KEY")
56
+ self._webhooks_api = None
57
+ self._transactional_email_api = None
58
+ self._transactional_sms_api = None
59
+ self._transactional_whatsapp_api = None
60
+
61
+ def get_api_client(self, api_key: str):
62
+ """Return the Brevo API client."""
63
+ from brevo_python import ApiClient, Configuration
64
+ configuration = Configuration()
65
+ configuration.api_key["api-key"] = api_key
66
+ configuration.api_key['partner-key'] = api_key
67
+ return ApiClient(configuration)
68
+
69
+ def _get_transactional_email_api(self):
70
+ """Return the Brevo transactional API instance."""
71
+ if self._transactional_email_api is None:
72
+ from brevo_python import TransactionalEmailsApi
73
+ api_client = self.get_api_client(self._email_api_key)
74
+ self._transactional_email_api = TransactionalEmailsApi(api_client)
75
+ return self._transactional_email_api
76
+
77
+ #########################################################
78
+ # Webhooks
79
+ #########################################################
80
+
81
+ def _get_webhooks_api(self):
82
+ """Return the Brevo webhooks API instance."""
83
+ if self._webhooks_api is None:
84
+ from brevo_python import WebhooksApi
85
+ api_client = self.get_api_client(self._email_api_key)
86
+ self._webhooks_api = WebhooksApi(api_client)
87
+ return self._webhooks_api
88
+
89
+ def get_webhooks(self):
90
+ """Return the Brevo webhooks."""
91
+ wbhs = self._get_webhooks_api()
92
+ return wbhs.get_webhooks().webhooks
93
+
94
+
95
+ #########################################################
96
+ # Helpers
97
+ #########################################################
98
+
99
+ def get_external_id_email(self, payload: dict[str, Any]) -> str | None:
100
+ """Extract the external ID from the Brevo webhook."""
101
+ return payload.get("_message_id")
102
+
103
+ def get_normalize_type(self, data: dict[str, Any]) -> str:
104
+ """Return the normalized type of webhook/email/SMS."""
105
+ if data.get("type") == "transactional":
106
+ return "email"
107
+ elif data.get("type") == "marketing":
108
+ return "email_marketing"
109
+ elif data.get("type") == "sms":
110
+ return "sms"
111
+ return "unknown"
112
+
113
+ #########################################################
114
+ # Attachments
115
+ #########################################################
116
+
117
+ def _add_attachments_email(self, email, attachments):
118
+ if not attachments:
119
+ return
120
+ from brevo_python import SendSmtpEmailAttachment
121
+
122
+ email.attachment = [
123
+ SendSmtpEmailAttachment(
124
+ name=a["name"],
125
+ content=self._to_base64(a["content"]),
126
+ )
127
+ for a in attachments
128
+ ]
129
+
130
+ #########################################################
131
+ # Email sending
132
+ #########################################################
133
+
134
+ def delete_blocked_emails(self, kwargs: dict[str, Any]) -> bool:
135
+ with contextlib.suppress(Exception):
136
+ api_instance = self._get_transactional_email_api()
137
+ for recipient in kwargs.get("recipients", []):
138
+ api_instance.smtp_blocked_contacts_email_delete(recipient["email"])
139
+ for recipient in kwargs.get("cc", []):
140
+ api_instance.smtp_blocked_contacts_email_delete(recipient["email"])
141
+ for recipient in kwargs.get("bcc", []):
142
+ api_instance.smtp_blocked_contacts_email_delete(recipient["email"])
143
+
144
+ def _prepare_email(self, **kwargs):
145
+ """Prepare the SendSmtpEmail object for sending."""
146
+ from brevo_python import SendSmtpEmail
147
+ email = SendSmtpEmail(subject=kwargs["subject"])
148
+ self._add_sender(email, kwargs)
149
+ self._add_content(email, kwargs)
150
+ self._add_reply_to(email, kwargs)
151
+ self._add_recipients(email, kwargs["recipients"])
152
+ self._add_bcc_or_cc(email, kwargs.get("bcc", []), "bcc")
153
+ self._add_bcc_or_cc(email, kwargs.get("cc", []), "cc")
154
+ self._add_attachments_email(email, kwargs.get("attachments", []))
155
+ return email
156
+
157
+ def _add_recipients(self, email, recipients):
158
+ from brevo_python import SendSmtpEmailTo
159
+ email.to = [
160
+ SendSmtpEmailTo(email=recipient["email"], name=recipient.get("name", ""))
161
+ for recipient in recipients
162
+ ]
163
+
164
+ def _add_sender(self, email, kwargs):
165
+ from brevo_python import SendSmtpEmailSender
166
+ sender = kwargs.get("sender")
167
+ email.sender = SendSmtpEmailSender(
168
+ email=sender["email"],
169
+ name=sender.get("name", "")
170
+ )
171
+
172
+ def _add_content(self, email, kwargs):
173
+ if kwargs.get("body"):
174
+ email.html_content = kwargs["body"]
175
+ if kwargs.get("body_text"):
176
+ email.text_content = kwargs["body_text"]
177
+
178
+ def _add_reply_to(self, email, kwargs):
179
+ reply_to = kwargs.get("reply_to")
180
+ if reply_to:
181
+ from brevo_python import SendSmtpEmailReplyTo
182
+ email.reply_to = SendSmtpEmailReplyTo(
183
+ email=reply_to["email"],
184
+ name=reply_to.get("name", "")
185
+ )
186
+
187
+ def _add_bcc_or_cc(self, email, recipients, key):
188
+ if not recipients or key not in ["cc", "bcc"]:
189
+ return
190
+ if key == "cc":
191
+ from brevo_python import SendSmtpEmailCc
192
+ recipient_class = SendSmtpEmailCc
193
+ elif key == "bcc":
194
+ from brevo_python import SendSmtpEmailBcc
195
+ recipient_class = SendSmtpEmailBcc
196
+ setattr(email, key, [
197
+ recipient_class(email=r["email"], name=r.get("name", ""))
198
+ for r in recipients
199
+ ])
200
+
201
+ def send_email(self, **kwargs) -> dict[str, Any]:
202
+ """Send email via Brevo API."""
203
+ self.delete_blocked_emails(kwargs)
204
+ email = self._prepare_email(**kwargs)
205
+ api_instance = self._get_transactional_email_api()
206
+ response = api_instance.send_transac_email(email)
207
+ return {field: str(getattr(response, field)) for field in response.__dict__}
208
+
209
+ def set_webhook_email(self, webhook_data: dict[str, Any]) -> bool:
210
+ """Configure a webhook to receive Brevo events."""
211
+ from brevo_python import CreateWebhook
212
+ wbhs = self._get_webhooks_api()
213
+ create_webhook = CreateWebhook(
214
+ url=webhook_data.get("url"),
215
+ description="Missive webhook email",
216
+ events=list(self.events_association.keys()),
217
+ channel="email",
218
+ type="transactional",
219
+ )
220
+ wbh = wbhs.create_webhook(create_webhook)
221
+ return self.get_normalize_webhook_id({"id": wbh.id})
222
+
223
+ def delete_webhook_email(self, webhook_data: dict[str, Any]) -> bool:
224
+ """Delete a webhook from Brevo."""
225
+ wbhs = self._get_webhooks_api()
226
+ return wbhs.delete_webhook(webhook_data.get("id"))
227
+
228
+ def update_webhook_email(self, webhook_data: dict[str, Any]) -> bool:
229
+ """Return the Brevo webhooks."""
230
+ from brevo_python import UpdateWebhook
231
+ wbhs = self._get_webhooks_api()
232
+ update = UpdateWebhook(url=webhook_data.get("url"))
233
+ webhook_id = webhook_data.get("id")
234
+ wbhs.update_webhook(webhook_id, update)
235
+ return self.get_normalize_webhook_id({"id": webhook_id})
236
+
237
+ def get_webhook_email(self, webhook_id: str):
238
+ """Return the Brevo webhook."""
239
+ wbhs = self._get_webhooks_api()
240
+ return next((wbh for wbh in wbhs if str(wbh.id) == str(webhook_id)), None)
241
+
242
+ def get_webhooks_email(self):
243
+ """Return only transactional email webhooks."""
244
+ webhooks = self.get_webhooks()
245
+ return [webhook for webhook in webhooks if webhook["type"] == "transactional"]
246
+
247
+ def handle_webhook_email(self, payload: dict[str, Any]) -> dict[str, Any]:
248
+ """Handle a Brevo webhook and normalize event type."""
249
+ payload = payload.decode("utf-8")
250
+ payload = json.loads(payload)
251
+ event = payload.get("event")
252
+ message_id = payload.get("message-id") or payload.get("messageId")
253
+ return {
254
+ "recipient": payload.get("email"),
255
+ "external_id": str(message_id),
256
+ "event": self.events_association.get(event, "unknown"),
257
+ "description": payload.get("reason"),
258
+ "occurred_at": payload.get("date"),
259
+ "trace": payload,
260
+ }
261
+
262
+
263
+ #########################################################
264
+ # SMS
265
+ #########################################################
266
+
267
+ def _get_transactional_sms_api(self):
268
+ """Return the Brevo transactional SMS API instance."""
269
+ if self._transactional_sms_api is None:
270
+ from brevo_python import TransactionalSMSApi
271
+ api_client = self.get_api_client(self._sms_api_key)
272
+ self._transactional_sms_api = TransactionalSMSApi(api_client)
273
+ return self._transactional_sms_api
274
+
275
+ def _prepare_sms(self, **kwargs):
276
+ """Prepare the SendSmsEmail object for sending."""
277
+ from brevo_python import SendTransacSms
278
+ sms = SendTransacSms(
279
+ sender=kwargs["sender"].get("name"),
280
+ recipient=str(kwargs["recipients"][0].get("phone")),
281
+ content=kwargs.get("body_text"),
282
+ )
283
+ return sms
284
+
285
+ def send_sms(self, **kwargs) -> dict[str, Any]:
286
+ """Send SMS via Brevo API."""
287
+ sms = self._prepare_sms(**kwargs)
288
+ api_instance = self._get_transactional_sms_api()
289
+ response = api_instance.send_transac_sms(sms)
290
+ return {field: str(getattr(response, field)) for field in response.__dict__}
291
+
292
+ def set_webhook_sms(self, webhook_data: dict[str, Any]) -> bool:
293
+ """Configure a webhook to receive Brevo events."""
294
+ from brevo_python import CreateWebhook
295
+ wbhs = self._get_webhooks_api()
296
+ create_webhook = CreateWebhook(
297
+ url=webhook_data.get("url"),
298
+ description="Missive webhook SMS",
299
+ events=list(self.events_association.keys()),
300
+ channel="sms",
301
+ type="transactional",
302
+ )
303
+ return wbhs.create_webhook(create_webhook)
304
+
305
+ def delete_webhook_sms(self, webhook_data: dict[str, Any]) -> bool:
306
+ """Delete a webhook from Brevo."""
307
+ wbhs = self._get_webhooks_api()
308
+ return wbhs.delete_webhook(webhook_data.get("id"))
309
+
310
+ def update_webhook_sms(self, webhook_data: dict[str, Any]) -> bool:
311
+ """Return the Brevo webhooks."""
312
+ from brevo_python import UpdateWebhook
313
+ wbhs = self._get_webhooks_api()
314
+
315
+ def get_webhook_sms(self, webhook_id: str):
316
+ """Return the Brevo webhook."""
317
+ wbhs = self._get_webhooks_api()
318
+ return next((wbh for wbh in wbhs if str(wbh.id) == str(webhook_id)), None)
319
+
320
+ def get_webhooks_sms(self):
321
+ """Return only transactional SMS webhooks."""
322
+ webhooks = self.get_webhooks()
323
+ return [webhook for webhook in webhooks if webhook["type"] == "transactional"]
324
+
325
+ def handle_webhook_sms(self, payload: dict[str, Any]) -> dict[str, Any]:
326
+ """Handle a Brevo webhook and normalize event type."""
327
+ payload = payload.decode("utf-8")
328
+ payload = json.loads(payload)
329
+ event = payload.get("event")
330
+ message_id = payload.get("message-id") or payload.get("messageId")
331
+ return {
332
+ "recipient": payload.get("phone"),
333
+ "external_id": str(message_id),
334
+ "event": self.events_association.get(event, "unknown"),
335
+ "description": payload.get("reason"),
336
+ "occurred_at": payload.get("date"),
337
+ "trace": payload,
338
+ }
339
+
340
+ #########################################################
341
+ # Whatsapp
342
+ #########################################################
343
+
344
+ def _get_transactional_whatsapp_api(self):
345
+ """Return the Brevo transactional WhatsApp API instance."""
346
+ if self._transactional_whatsapp_api is None:
347
+ from brevo_python import TransactionalWhatsAppApi
348
+ api_client = self.get_api_client(self._whatsapp_api_key)
349
+ self._transactional_whatsapp_api = TransactionalWhatsAppApi(api_client)
350
+ return self._transactional_whatsapp_api
351
+
352
+ def send_branded(self, **kwargs) -> dict[str, Any]:
353
+ """Send a branded message via Brevo API."""
354
+ return self.send_whatsapp(**kwargs)
355
+
356
+ def send_whatsapp(self, **kwargs) -> dict[str, Any]:
357
+ """Send WhatsApp via Brevo API."""
358
+ from brevo_python import SendWhatsappMessage
359
+ recipients = [str(recipient["phone"]) for recipient in kwargs.get("recipients", [])]
360
+ sender = "+33614397083"
361
+ message = SendWhatsappMessage(
362
+ contact_numbers=recipients,
363
+ sender_number=sender,
364
+ text=kwargs.get("body_text"),
365
+ )
366
+ api_instance = self._get_transactional_whatsapp_api()
367
+ response = api_instance.send_whatsapp_message(message)
368
+ return {field: str(getattr(response, field)) for field in response.__dict__}
369
+
370
+ def set_webhook_whatsapp(self, webhook_data: dict[str, Any]) -> bool:
371
+ """Configure a webhook to receive Brevo events."""
372
+ from brevo_python import CreateWebhook
373
+ wbhs = self._get_webhooks_api()
374
+ create_webhook = CreateWebhook(
375
+ url=webhook_data.get("url"),
376
+ description="Missive webhook WhatsApp",
377
+ events=list(self.events_association.keys()),
378
+ channel="whatsapp",
379
+ type="transactional",
380
+ )
381
+ return wbhs.create_webhook(create_webhook)
382
+
383
+ def delete_webhook_whatsapp(self, webhook_data: dict[str, Any]) -> bool:
384
+ """Delete a webhook from Brevo."""
385
+ wbhs = self._get_webhooks_api()
386
+ return wbhs.delete_webhook(webhook_data.get("id"))
387
+
388
+ def update_webhook_whatsapp(self, webhook_data: dict[str, Any]) -> bool:
389
+ """Return the Brevo webhooks."""
390
+ from brevo_python import UpdateWebhook
391
+ wbhs = self._get_webhooks_api()
392
+ update = UpdateWebhook(url=webhook_data.get("url"))
393
+ webhook_id = webhook_data.get("id")
394
+ wbhs.update_webhook(webhook_id, update)
395
+ return self.get_normalize_webhook_id({"id": webhook_id})
396
+
397
+ def get_webhook_whatsapp(self, webhook_id: str):
398
+ """Return the Brevo webhook."""
399
+ wbhs = self._get_webhooks_api()
400
+ return next((wbh for wbh in wbhs if str(wbh.id) == str(webhook_id)), None)
401
+
402
+ def get_webhooks_whatsapp(self):
403
+ """Return only transactional WhatsApp webhooks."""
404
+ webhooks = self.get_webhooks()
405
+ return [webhook for webhook in webhooks if webhook["type"] == "transactional"]
406
+
407
+ def handle_webhook_whatsapp(self, payload: dict[str, Any]) -> dict[str, Any]:
408
+ """Handle a Brevo webhook and normalize event type."""
409
+ payload = payload.decode("utf-8")
410
+ payload = json.loads(payload)
411
+ event = payload.get("event")
412
+ message_id = payload.get("message-id") or payload.get("messageId")
413
+ return {
414
+ "recipient": payload.get("phone"),
415
+ "external_id": str(message_id),
416
+ "event": self.events_association.get(event, "unknown"),
417
+ "description": payload.get("reason"),
418
+ "occurred_at": payload.get("date"),
419
+ "trace": payload,
420
+ }
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class CerteuropeProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "certeurope"
7
+ display_name = "Certeurope (LRE)"
8
+ description = "Electronic registered email with legal value (LRE)"
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class DjangoEmailProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "django_email"
7
+ display_name = "Django Email Backend"
8
+ description = "Lightweight email provider delegating to SMTP or local file delivery. Mimics Django's console/backend behaviour without importing Django."
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class FCMProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "fcm"
7
+ display_name = "Firebase Cloud Messaging"
8
+ description = "Mobile push notifications for Android and iOS (Google Firebase)"
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class LaposteProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "La Poste"
7
+ display_name = "La Poste"
8
+ description = "Registered mail and AR email sending on French territory"
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class MailevaProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "Maileva"
7
+ display_name = "Maileva"
8
+ description = "Electronic postal mail and registered mail services"
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class MailgunProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "Mailgun"
7
+ display_name = "Mailgun"
8
+ description = "Transactional email service with advanced validation and routing"
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class MessengerProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "messenger"
7
+ display_name = "Facebook Messenger"
8
+ description = "Facebook Messenger - Consumer instant messaging (Meta)"
@@ -0,0 +1,8 @@
1
+ from .base import MissiveProviderBase
2
+
3
+
4
+ class NotificationProvider(MissiveProviderBase):
5
+ abstract = True
6
+ name = "notification"
7
+ display_name = "Notification In-App"
8
+ description = "In-app notifications without external dependency"