mails-agent 1.4.0b1__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.
@@ -0,0 +1,18 @@
1
+ """mails-agent — Python SDK for email capabilities for AI agents."""
2
+
3
+ from .client import AsyncMailsClient, MailsClient
4
+ from .exceptions import ApiError, AuthError, MailsError, NotFoundError
5
+ from .models import Email, SendResult, VerificationCode
6
+
7
+ __version__ = "1.4.0b1"
8
+ __all__ = [
9
+ "MailsClient",
10
+ "AsyncMailsClient",
11
+ "Email",
12
+ "SendResult",
13
+ "VerificationCode",
14
+ "MailsError",
15
+ "AuthError",
16
+ "NotFoundError",
17
+ "ApiError",
18
+ ]
mails_agent/client.py ADDED
@@ -0,0 +1,408 @@
1
+ """Synchronous and asynchronous HTTP clients for the mails-agent API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional, Sequence, Union
6
+
7
+ import httpx
8
+
9
+ from .exceptions import ApiError, AuthError, NotFoundError
10
+ from .models import Email, SendResult, VerificationCode
11
+
12
+
13
+ def _parse_email(data: Dict[str, Any]) -> Email:
14
+ """Convert a raw API dict into an Email dataclass."""
15
+ return Email(
16
+ id=data["id"],
17
+ mailbox=data.get("mailbox", ""),
18
+ from_address=data.get("from_address", ""),
19
+ from_name=data.get("from_name", ""),
20
+ subject=data.get("subject", ""),
21
+ direction=data.get("direction", "inbound"),
22
+ status=data.get("status", ""),
23
+ received_at=data.get("received_at", ""),
24
+ has_attachments=bool(data.get("has_attachments", False)),
25
+ attachment_count=int(data.get("attachment_count", 0)),
26
+ body_text=data.get("body_text", ""),
27
+ body_html=data.get("body_html", ""),
28
+ code=data.get("code"),
29
+ )
30
+
31
+
32
+ def _handle_error(response: httpx.Response) -> None:
33
+ """Raise the appropriate exception for non-2xx responses."""
34
+ if response.status_code == 401 or response.status_code == 403:
35
+ raise AuthError(f"Authentication failed ({response.status_code})")
36
+ if response.status_code == 404:
37
+ raise NotFoundError("Resource not found")
38
+ if not response.is_success:
39
+ try:
40
+ body = response.json()
41
+ message = body.get("error", response.reason_phrase)
42
+ except Exception:
43
+ message = response.reason_phrase or f"HTTP {response.status_code}"
44
+ raise ApiError(message, response.status_code)
45
+
46
+
47
+ class MailsClient:
48
+ """Synchronous client for the mails-agent API.
49
+
50
+ Args:
51
+ api_url: Worker API base URL (e.g. ``https://mails-worker.genedai.workers.dev``).
52
+ token: API key or worker token for authentication.
53
+ mailbox: Your email address (e.g. ``agent@mails0.com``).
54
+ timeout: Request timeout in seconds. Defaults to 60 to accommodate
55
+ long-polling ``wait_for_code`` calls.
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ api_url: str,
61
+ token: str,
62
+ mailbox: str,
63
+ *,
64
+ timeout: float = 60.0,
65
+ ) -> None:
66
+ self.api_url = api_url.rstrip("/")
67
+ self.token = token
68
+ self.mailbox = mailbox
69
+ self._client = httpx.Client(
70
+ base_url=self.api_url,
71
+ headers={"Authorization": f"Bearer {token}"},
72
+ timeout=timeout,
73
+ )
74
+
75
+ # ------------------------------------------------------------------
76
+ # Context manager support
77
+ # ------------------------------------------------------------------
78
+
79
+ def __enter__(self) -> "MailsClient":
80
+ return self
81
+
82
+ def __exit__(self, *args: Any) -> None:
83
+ self.close()
84
+
85
+ def close(self) -> None:
86
+ """Close the underlying HTTP client."""
87
+ self._client.close()
88
+
89
+ # ------------------------------------------------------------------
90
+ # Public API
91
+ # ------------------------------------------------------------------
92
+
93
+ def send(
94
+ self,
95
+ to: Union[str, List[str]],
96
+ subject: str,
97
+ *,
98
+ text: Optional[str] = None,
99
+ html: Optional[str] = None,
100
+ reply_to: Optional[str] = None,
101
+ attachments: Optional[Sequence[Dict[str, Any]]] = None,
102
+ ) -> SendResult:
103
+ """Send an email.
104
+
105
+ Args:
106
+ to: Recipient address or list of addresses.
107
+ subject: Email subject line.
108
+ text: Plain-text body.
109
+ html: HTML body.
110
+ reply_to: Reply-to address.
111
+ attachments: List of attachment dicts with ``filename``, ``content``,
112
+ and optionally ``content_type`` / ``content_id``.
113
+
114
+ Returns:
115
+ A :class:`SendResult` with the message id and provider info.
116
+ """
117
+ recipients = [to] if isinstance(to, str) else list(to)
118
+ payload: Dict[str, Any] = {
119
+ "from": self.mailbox,
120
+ "to": recipients,
121
+ "subject": subject,
122
+ }
123
+ if text is not None:
124
+ payload["text"] = text
125
+ if html is not None:
126
+ payload["html"] = html
127
+ if reply_to is not None:
128
+ payload["reply_to"] = reply_to
129
+ if attachments:
130
+ payload["attachments"] = list(attachments)
131
+
132
+ response = self._client.post("/api/send", json=payload)
133
+ _handle_error(response)
134
+ data = response.json()
135
+ return SendResult(
136
+ id=data["id"],
137
+ provider=data.get("provider", ""),
138
+ provider_id=data.get("provider_id"),
139
+ )
140
+
141
+ def get_inbox(
142
+ self,
143
+ *,
144
+ limit: int = 20,
145
+ offset: int = 0,
146
+ direction: Optional[str] = None,
147
+ query: Optional[str] = None,
148
+ ) -> List[Email]:
149
+ """Fetch emails from the inbox.
150
+
151
+ Args:
152
+ limit: Maximum number of emails to return.
153
+ offset: Pagination offset.
154
+ direction: Filter by ``'inbound'`` or ``'outbound'``.
155
+ query: Optional search query string.
156
+
157
+ Returns:
158
+ A list of :class:`Email` objects.
159
+ """
160
+ params: Dict[str, Any] = {
161
+ "to": self.mailbox,
162
+ "limit": limit,
163
+ "offset": offset,
164
+ }
165
+ if direction is not None:
166
+ params["direction"] = direction
167
+ if query is not None:
168
+ params["query"] = query
169
+
170
+ response = self._client.get("/api/inbox", params=params)
171
+ _handle_error(response)
172
+ data = response.json()
173
+ return [_parse_email(e) for e in data.get("emails", [])]
174
+
175
+ def search(
176
+ self,
177
+ query: str,
178
+ *,
179
+ limit: int = 20,
180
+ direction: Optional[str] = None,
181
+ ) -> List[Email]:
182
+ """Search emails by query string.
183
+
184
+ Args:
185
+ query: Search query.
186
+ limit: Maximum number of results.
187
+ direction: Filter by ``'inbound'`` or ``'outbound'``.
188
+
189
+ Returns:
190
+ A list of matching :class:`Email` objects.
191
+ """
192
+ return self.get_inbox(query=query, limit=limit, direction=direction)
193
+
194
+ def get_email(self, email_id: str) -> Email:
195
+ """Fetch a single email by ID.
196
+
197
+ Args:
198
+ email_id: The email's unique identifier.
199
+
200
+ Returns:
201
+ The :class:`Email` object.
202
+
203
+ Raises:
204
+ NotFoundError: If the email does not exist.
205
+ """
206
+ response = self._client.get("/api/email", params={"id": email_id})
207
+ _handle_error(response)
208
+ return _parse_email(response.json())
209
+
210
+ def wait_for_code(
211
+ self,
212
+ *,
213
+ timeout: int = 30,
214
+ ) -> Optional[VerificationCode]:
215
+ """Wait for a verification code to arrive.
216
+
217
+ This long-polls the server until a code is found or the timeout
218
+ expires.
219
+
220
+ Args:
221
+ timeout: Maximum seconds to wait. Defaults to 30.
222
+
223
+ Returns:
224
+ A :class:`VerificationCode` if one arrived, or ``None`` on timeout.
225
+ """
226
+ # Use a longer HTTP timeout to cover the server-side polling window.
227
+ response = self._client.get(
228
+ "/api/code",
229
+ params={"to": self.mailbox, "timeout": timeout},
230
+ timeout=max(timeout + 10, 60.0),
231
+ )
232
+ _handle_error(response)
233
+ data = response.json()
234
+ if not data.get("code"):
235
+ return None
236
+ return VerificationCode(
237
+ code=data["code"],
238
+ from_address=data.get("from", ""),
239
+ subject=data.get("subject", ""),
240
+ )
241
+
242
+ def delete_email(self, email_id: str) -> bool:
243
+ """Delete an email by ID.
244
+
245
+ Args:
246
+ email_id: The email's unique identifier.
247
+
248
+ Returns:
249
+ ``True`` if the email was deleted, ``False`` if it was not found.
250
+ """
251
+ response = self._client.delete("/api/email", params={"id": email_id})
252
+ if response.status_code == 404:
253
+ return False
254
+ _handle_error(response)
255
+ return True
256
+
257
+
258
+ # ======================================================================
259
+ # Async client
260
+ # ======================================================================
261
+
262
+
263
+ class AsyncMailsClient:
264
+ """Asynchronous client for the mails-agent API.
265
+
266
+ Mirrors :class:`MailsClient` but all methods are ``async``.
267
+
268
+ Args:
269
+ api_url: Worker API base URL.
270
+ token: API key or worker token for authentication.
271
+ mailbox: Your email address.
272
+ timeout: Request timeout in seconds (default 60).
273
+ """
274
+
275
+ def __init__(
276
+ self,
277
+ api_url: str,
278
+ token: str,
279
+ mailbox: str,
280
+ *,
281
+ timeout: float = 60.0,
282
+ ) -> None:
283
+ self.api_url = api_url.rstrip("/")
284
+ self.token = token
285
+ self.mailbox = mailbox
286
+ self._client = httpx.AsyncClient(
287
+ base_url=self.api_url,
288
+ headers={"Authorization": f"Bearer {token}"},
289
+ timeout=timeout,
290
+ )
291
+
292
+ async def __aenter__(self) -> "AsyncMailsClient":
293
+ return self
294
+
295
+ async def __aexit__(self, *args: Any) -> None:
296
+ await self.close()
297
+
298
+ async def close(self) -> None:
299
+ """Close the underlying HTTP client."""
300
+ await self._client.aclose()
301
+
302
+ # ------------------------------------------------------------------
303
+ # Public API
304
+ # ------------------------------------------------------------------
305
+
306
+ async def send(
307
+ self,
308
+ to: Union[str, List[str]],
309
+ subject: str,
310
+ *,
311
+ text: Optional[str] = None,
312
+ html: Optional[str] = None,
313
+ reply_to: Optional[str] = None,
314
+ attachments: Optional[Sequence[Dict[str, Any]]] = None,
315
+ ) -> SendResult:
316
+ """Send an email. See :meth:`MailsClient.send` for details."""
317
+ recipients = [to] if isinstance(to, str) else list(to)
318
+ payload: Dict[str, Any] = {
319
+ "from": self.mailbox,
320
+ "to": recipients,
321
+ "subject": subject,
322
+ }
323
+ if text is not None:
324
+ payload["text"] = text
325
+ if html is not None:
326
+ payload["html"] = html
327
+ if reply_to is not None:
328
+ payload["reply_to"] = reply_to
329
+ if attachments:
330
+ payload["attachments"] = list(attachments)
331
+
332
+ response = await self._client.post("/api/send", json=payload)
333
+ _handle_error(response)
334
+ data = response.json()
335
+ return SendResult(
336
+ id=data["id"],
337
+ provider=data.get("provider", ""),
338
+ provider_id=data.get("provider_id"),
339
+ )
340
+
341
+ async def get_inbox(
342
+ self,
343
+ *,
344
+ limit: int = 20,
345
+ offset: int = 0,
346
+ direction: Optional[str] = None,
347
+ query: Optional[str] = None,
348
+ ) -> List[Email]:
349
+ """Fetch emails from the inbox. See :meth:`MailsClient.get_inbox`."""
350
+ params: Dict[str, Any] = {
351
+ "to": self.mailbox,
352
+ "limit": limit,
353
+ "offset": offset,
354
+ }
355
+ if direction is not None:
356
+ params["direction"] = direction
357
+ if query is not None:
358
+ params["query"] = query
359
+
360
+ response = await self._client.get("/api/inbox", params=params)
361
+ _handle_error(response)
362
+ data = response.json()
363
+ return [_parse_email(e) for e in data.get("emails", [])]
364
+
365
+ async def search(
366
+ self,
367
+ query: str,
368
+ *,
369
+ limit: int = 20,
370
+ direction: Optional[str] = None,
371
+ ) -> List[Email]:
372
+ """Search emails by query. See :meth:`MailsClient.search`."""
373
+ return await self.get_inbox(query=query, limit=limit, direction=direction)
374
+
375
+ async def get_email(self, email_id: str) -> Email:
376
+ """Fetch a single email by ID. See :meth:`MailsClient.get_email`."""
377
+ response = await self._client.get("/api/email", params={"id": email_id})
378
+ _handle_error(response)
379
+ return _parse_email(response.json())
380
+
381
+ async def wait_for_code(
382
+ self,
383
+ *,
384
+ timeout: int = 30,
385
+ ) -> Optional[VerificationCode]:
386
+ """Wait for a verification code. See :meth:`MailsClient.wait_for_code`."""
387
+ response = await self._client.get(
388
+ "/api/code",
389
+ params={"to": self.mailbox, "timeout": timeout},
390
+ timeout=max(timeout + 10, 60.0),
391
+ )
392
+ _handle_error(response)
393
+ data = response.json()
394
+ if not data.get("code"):
395
+ return None
396
+ return VerificationCode(
397
+ code=data["code"],
398
+ from_address=data.get("from", ""),
399
+ subject=data.get("subject", ""),
400
+ )
401
+
402
+ async def delete_email(self, email_id: str) -> bool:
403
+ """Delete an email by ID. See :meth:`MailsClient.delete_email`."""
404
+ response = await self._client.delete("/api/email", params={"id": email_id})
405
+ if response.status_code == 404:
406
+ return False
407
+ _handle_error(response)
408
+ return True
@@ -0,0 +1,27 @@
1
+ """Custom exceptions for the mails-agent SDK."""
2
+
3
+
4
+ class MailsError(Exception):
5
+ """Base exception for mails-agent SDK."""
6
+
7
+ pass
8
+
9
+
10
+ class AuthError(MailsError):
11
+ """Authentication failed (HTTP 401/403)."""
12
+
13
+ pass
14
+
15
+
16
+ class NotFoundError(MailsError):
17
+ """Resource not found (HTTP 404)."""
18
+
19
+ pass
20
+
21
+
22
+ class ApiError(MailsError):
23
+ """API returned an error response."""
24
+
25
+ def __init__(self, message: str, status_code: int) -> None:
26
+ super().__init__(message)
27
+ self.status_code = status_code
mails_agent/models.py ADDED
@@ -0,0 +1,43 @@
1
+ """Data models for the mails-agent SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional
7
+
8
+
9
+ @dataclass
10
+ class Email:
11
+ """Represents an email message."""
12
+
13
+ id: str
14
+ mailbox: str
15
+ from_address: str
16
+ from_name: str
17
+ subject: str
18
+ direction: str # 'inbound' | 'outbound'
19
+ status: str
20
+ received_at: str
21
+ has_attachments: bool = False
22
+ attachment_count: int = 0
23
+ body_text: str = ""
24
+ body_html: str = ""
25
+ code: Optional[str] = None
26
+
27
+
28
+ @dataclass
29
+ class SendResult:
30
+ """Result of a send operation."""
31
+
32
+ id: str
33
+ provider: str
34
+ provider_id: Optional[str] = None
35
+
36
+
37
+ @dataclass
38
+ class VerificationCode:
39
+ """A verification code extracted from an email."""
40
+
41
+ code: str
42
+ from_address: str
43
+ subject: str
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: mails-agent
3
+ Version: 1.4.0b1
4
+ Summary: Python SDK for mails-agent — email capabilities for AI agents
5
+ Project-URL: Homepage, https://mails0.com
6
+ Project-URL: Repository, https://github.com/Digidai/mails-python
7
+ Project-URL: Documentation, https://github.com/Digidai/mails-python#readme
8
+ Author: Gene Dai
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agent,email,mails,verification-code
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Requires-Python: >=3.9
17
+ Requires-Dist: httpx>=0.24.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # mails-agent
21
+
22
+ Python SDK for [mails0.com](https://mails0.com) -- email capabilities for AI agents.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install mails-agent
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```python
33
+ from mails_agent import MailsClient
34
+
35
+ client = MailsClient(
36
+ api_url="https://mails-worker.your-domain.com",
37
+ token="your-api-token",
38
+ mailbox="agent@mails0.com",
39
+ )
40
+
41
+ # Send an email
42
+ result = client.send(
43
+ to="user@example.com",
44
+ subject="Hello from my agent",
45
+ text="This email was sent by an AI agent.",
46
+ )
47
+ print(f"Sent: {result.id}")
48
+
49
+ # Check inbox
50
+ emails = client.get_inbox(limit=5)
51
+ for email in emails:
52
+ print(f"{email.from_address}: {email.subject}")
53
+
54
+ # Wait for a verification code (long-polls up to 30s)
55
+ code = client.wait_for_code(timeout=30)
56
+ if code:
57
+ print(f"Got code: {code.code}")
58
+ ```
59
+
60
+ ## API reference
61
+
62
+ ### `MailsClient(api_url, token, mailbox, *, timeout=60.0)`
63
+
64
+ Create a synchronous client. Supports use as a context manager:
65
+
66
+ ```python
67
+ with MailsClient(api_url, token, mailbox) as client:
68
+ emails = client.get_inbox()
69
+ ```
70
+
71
+ ---
72
+
73
+ ### `send(to, subject, *, text=None, html=None, reply_to=None, attachments=None) -> SendResult`
74
+
75
+ Send an email. `to` can be a single address or a list.
76
+
77
+ ```python
78
+ result = client.send(
79
+ to=["alice@example.com", "bob@example.com"],
80
+ subject="Team update",
81
+ html="<h1>Update</h1><p>Everything is on track.</p>",
82
+ reply_to="noreply@mails0.com",
83
+ )
84
+ ```
85
+
86
+ **Attachments** are passed as a list of dicts:
87
+
88
+ ```python
89
+ client.send(
90
+ to="user@example.com",
91
+ subject="Report",
92
+ text="See attached.",
93
+ attachments=[{
94
+ "filename": "report.pdf",
95
+ "content": base64_encoded_string,
96
+ "content_type": "application/pdf",
97
+ }],
98
+ )
99
+ ```
100
+
101
+ ---
102
+
103
+ ### `get_inbox(*, limit=20, offset=0, direction=None, query=None) -> list[Email]`
104
+
105
+ Fetch emails from the inbox with optional filtering.
106
+
107
+ ```python
108
+ # Get latest 10 inbound emails
109
+ emails = client.get_inbox(limit=10, direction="inbound")
110
+
111
+ # Search for emails containing "invoice"
112
+ emails = client.get_inbox(query="invoice")
113
+ ```
114
+
115
+ ---
116
+
117
+ ### `search(query, *, limit=20, direction=None) -> list[Email]`
118
+
119
+ Search emails by query string. Convenience wrapper around `get_inbox`.
120
+
121
+ ```python
122
+ results = client.search("verification code", limit=5)
123
+ ```
124
+
125
+ ---
126
+
127
+ ### `get_email(email_id) -> Email`
128
+
129
+ Fetch a single email by its ID. Raises `NotFoundError` if it does not exist.
130
+
131
+ ```python
132
+ email = client.get_email("abc-123")
133
+ print(email.body_text)
134
+ ```
135
+
136
+ ---
137
+
138
+ ### `wait_for_code(*, timeout=30) -> VerificationCode | None`
139
+
140
+ Long-poll the server for a verification code. Returns `None` if no code arrives within the timeout.
141
+
142
+ ```python
143
+ code = client.wait_for_code(timeout=60)
144
+ if code:
145
+ print(f"Code: {code.code}, From: {code.from_address}")
146
+ ```
147
+
148
+ ---
149
+
150
+ ### `delete_email(email_id) -> bool`
151
+
152
+ Delete an email. Returns `True` if deleted, `False` if not found.
153
+
154
+ ```python
155
+ deleted = client.delete_email("abc-123")
156
+ ```
157
+
158
+ ## Async usage
159
+
160
+ All methods are available as `async` via `AsyncMailsClient`:
161
+
162
+ ```python
163
+ import asyncio
164
+ from mails_agent import AsyncMailsClient
165
+
166
+ async def main():
167
+ async with AsyncMailsClient(
168
+ api_url="https://mails-worker.your-domain.com",
169
+ token="your-api-token",
170
+ mailbox="agent@mails0.com",
171
+ ) as client:
172
+ # Send
173
+ result = await client.send("user@example.com", "Hello", text="Hi!")
174
+
175
+ # Inbox
176
+ emails = await client.get_inbox()
177
+
178
+ # Wait for code
179
+ code = await client.wait_for_code(timeout=30)
180
+
181
+ asyncio.run(main())
182
+ ```
183
+
184
+ ## Data models
185
+
186
+ ### `Email`
187
+
188
+ | Field | Type | Description |
189
+ |-------|------|-------------|
190
+ | `id` | `str` | Unique email ID |
191
+ | `mailbox` | `str` | Mailbox address |
192
+ | `from_address` | `str` | Sender email |
193
+ | `from_name` | `str` | Sender display name |
194
+ | `subject` | `str` | Subject line |
195
+ | `direction` | `str` | `"inbound"` or `"outbound"` |
196
+ | `status` | `str` | `"received"`, `"sent"`, `"failed"`, `"queued"` |
197
+ | `received_at` | `str` | ISO 8601 timestamp |
198
+ | `has_attachments` | `bool` | Whether email has attachments |
199
+ | `attachment_count` | `int` | Number of attachments |
200
+ | `body_text` | `str` | Plain text body |
201
+ | `body_html` | `str` | HTML body |
202
+ | `code` | `str \| None` | Extracted verification code, if any |
203
+
204
+ ### `SendResult`
205
+
206
+ | Field | Type | Description |
207
+ |-------|------|-------------|
208
+ | `id` | `str` | Message ID |
209
+ | `provider` | `str` | Send provider used |
210
+ | `provider_id` | `str \| None` | Provider-specific ID |
211
+
212
+ ### `VerificationCode`
213
+
214
+ | Field | Type | Description |
215
+ |-------|------|-------------|
216
+ | `code` | `str` | The verification code |
217
+ | `from_address` | `str` | Sender of the code email |
218
+ | `subject` | `str` | Subject of the code email |
219
+
220
+ ## Exceptions
221
+
222
+ | Exception | When |
223
+ |-----------|------|
224
+ | `MailsError` | Base class for all SDK errors |
225
+ | `AuthError` | 401 or 403 response |
226
+ | `NotFoundError` | 404 response |
227
+ | `ApiError` | Any other non-2xx response (has `.status_code`) |
228
+
229
+ ## License
230
+
231
+ MIT
@@ -0,0 +1,8 @@
1
+ mails_agent/__init__.py,sha256=8Qm9eTDhl9s4s76uhGyaFTLVFjLS9xRzaPAdFUJIuBA,458
2
+ mails_agent/client.py,sha256=pJiovsmHz-0bmFd9nkkFr79uC1SH7YRMWcL8TBwE6dU,12984
3
+ mails_agent/exceptions.py,sha256=oJau3nBfIXi5r1s1Tv1vJa5Ec_udzg52w971AaVaDf4,521
4
+ mails_agent/models.py,sha256=sNbZyOO22snc53EfBABawsocBObxi6xLOqSntcChm1Y,811
5
+ mails_agent-1.4.0b1.dist-info/METADATA,sha256=0rJfUIBxlAJaqdgGAksn0q4BIZsvVxXp_0xj66tUQ98,5623
6
+ mails_agent-1.4.0b1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ mails_agent-1.4.0b1.dist-info/licenses/LICENSE,sha256=TPvRtAS8APyBl4qODyF_s2fUg5i1NPrR5I540nqbQhk,1065
8
+ mails_agent-1.4.0b1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Gene Dai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.