پرش به مستندات
Python

ارسال یک ایمیل

`emails.send`: یک پیام، حالا یا بعداً.

emails.send

send_email.py
from openemail import openemail email = openemail.emails.send({    'from': {'email': '[email protected]', 'name': 'Acme Billing'},    'to': ['[email protected]', 'Grace <[email protected]>'],    'cc': '[email protected]',    'bcc': [{'email': '[email protected]'}],    'replyTo': '[email protected]',    'subject': 'Your September invoice',    'html': '<p>Invoice attached.</p>',    'text': 'Invoice attached.',    'headers': {'X-Campaign': 'invoices'},    'attachments': [{'filename': 'invoice.pdf', 'content': pdf_bytes}],    'threadId': 'thread_…',    'scheduledAt': 'PT1H',    'tags': {'order': '4021'},    'tracking': {'opens': True, 'clicks': True},})

to، cc و bcc یک گیرنده یا چند گیرنده می‌گیرند، و اگر یکی تنها بدهید برایتان پیچیده می‌شود. هرکدام می‌تواند نشانی خام، Name <addr@host> یا {'email': ..., 'name': ...} باشد.

پارامترها

fromRecipientInputالزامی
فرستنده. یک نشانی خام، `Name <addr@host>` یا یک دیکشنری. باید نشانی‌ای باشد که این کلید اجازهٔ ارسال با آن را دارد. هیچ فرستندهٔ جایگزینی وجود ندارد، پس هر ارسال همیشه نشانی‌ای را که از آن فرستاده می‌شود نام می‌برد.
toRecipientInput | list[RecipientInput]الزامی
یک گیرنده یا چند گیرنده؛ اگر یکی تنها بدهید برایتان پیچیده می‌شود. حداکثر 50 نشانی روی هم در to، cc و bcc.
ccRecipientInput | list[RecipientInput]
به سقف 50 گیرنده شمرده می‌شود.
bccRecipientInput | list[RecipientInput]
هرگز در بایت‌هایی که دیگران دریافت می‌کنند نام برده نمی‌شود، چون به ازای هر گیرنده یک پاکت منتقل می‌شود.
replyToRecipientInput
یک نشانی تکی، که به شکل هدر Reply-To فرستاده می‌شود.
subjectstr
حداکثر 998 نویسه، همان حد طول خط RFC 5322. پیش‌فرض: خالی.
htmlstr
یکی از html، text، draftId یا template الزامی است. وقتی هر دو html و text داده شوند، آنچه گیرندگان می‌بینند HTML است.
textstr
بخش متن ساده.
templateEmailSendTemplate
یک قالب ذخیره‌شده را سمت سرور رندر می‌کند. `version` آن را سنجاق می‌کند؛ ننویسیدش تا هرچه هنگام پذیرش درخواست منتشر شده باشد به کار رود. prop ناشناخته یا نیامده، به‌جای جای خالی در پیام، یک 422 است.
draftIdstr
یک پیش‌نویس ذخیره‌شده را زیر این پاکت بفرستید.
headersdict[str, str]
`X-*`، `List-*`، Reply-To، Precedence، Auto-Submitted، Importance، Priority و Feedback-Id. هر چیزی که خودِ حمل‌ونقل تنظیم می‌کند به‌جای حذف بی‌سروصدا رد می‌شود.
attachmentslist[AttachmentInput]
`{'filename': ..., 'content': ...}` با یک `'contentType'` اختیاری، یا `{'fileId': ...}` که فایلی را نام می‌برد که از پیش در فضای کاری هست، مثلاً فایلی از `files.upload`. برای content بایت بدهید تا برایتان با base64 رمزگذاری شود. 20 فایل، با سقف مجموع 5 MB برای فایل‌های درون‌خطی پس از رمزگشایی. فایل ذخیره‌شده می‌تواند بزرگ‌تر باشد و به شکل پیوند دانلود می‌رود.
attachmentDeliveryAttachmentDeliveryMode
`mime`، `link` یا `auto`. `auto` فایل‌ها را وقتی روی دامنه‌ای با files domain فعال از 2 MB بگذرند به شکل پیوند دانلود می‌برد و در غیر این صورت درون پیام. اگر ننویسید، تنظیم صندوق اعمال می‌شود، که پیش‌فرضش `auto` است.
threadIdstr
پاسخ‌دادن درون یک thread موجود. حمل‌ونقل In-Reply-To و References را می‌نویسد.
scheduledAtdatetime | str
یک `datetime`، یک لحظهٔ ISO-8601، یا مدتی مانند `PT1H`. تا یک سال بعد، هرگز در گذشته. نمی‌شود با cancellableForSeconds ترکیبش کرد.
cancellableForSecondsint
0 تا 900. پنجرهٔ لغو روی یک ارسال فوری: همان سازوکار لغوِ composer، که به‌جای hardcode شدن در دسترس گذاشته شده است.
tagsdict[str, str]
تا 10 برچسب، که بازتاب داده می‌شوند و قابل فیلترند. هرگز تفسیر نمی‌شوند.
signaturebool
اینکه این پیام امضای نشانی فرستنده را داشته باشد یا نه: امضای خودش، وگرنه امضای catch-all برای نشانی‌ای که catch-all دریافت کرده، وگرنه پانویس OpenEmail، مگر اینکه آن نشانی خاموشش کرده باشد. اگر مشخص نشود، بدنهٔ `html` دقیقاً همان‌طور که نوشته شده و بدون امضا می‌رود و بدنهٔ فقط `text` آن را دارد. برای نامه‌ای که برنامه‌ای از طرف کسی می‌فرستد، مثل رسید، بازنشانی گذرواژه یا خلاصه، `False` بگذارید؛ هیچ‌کدام امضای یک شخص را زیر خود نمی‌خواهند.
trackingTrackingRequest
اینکه آیا برای این پیام pixel باز شدن افزوده و پیوندها بازنویسی شوند یا نه. خاموش است مگر ردیابی برای نشانی‌ای که پیام از آن می‌رود (یا catch-all‌ای که آن را گرفته) روشن شده باشد، و هر یک از این فیلدها که اینجا بیاید تکلیف همان یک پیام را، هرطور که نشانی تنظیم شده باشد، روشن می‌کند.
translateSendTranslateOptions
آن را به زبان گیرنده بفرستید. `to` یک کد، یک نام انگلیسی یا نام خودِ زبان می‌گیرد؛ `subject` و `includeOriginal` هر دو پیش‌فرض true دارند. هنگام پذیرفته‌شدن درخواست حل می‌شود، پس پیام زمان‌بندی‌شده همان کلماتی را می‌برد که تأیید شده‌اند. در کنار `draftId` رد می‌شود.

پاسخ

idstr
شناسهٔ ارسال، `msg_…`. برای `get`، `cancel`، `reschedule` و `get_tracking` از آن استفاده کنید.
statusEmailStatus
queued، scheduled، sending، sent، partial، bounced، cancelled یا failed. به‌جای این واقعیت که فراخوانی برگشت، همین را بخوانید. `partial` وضعیتی از آنِ خودش است: بعضی گیرندگان پیام را دریافت کرده‌اند و نمی‌شود پس گرفت، پس تلاش دوباره اشتباه است و گزارش شکست دروغ.
modeApiKeyMode
کدام نوع کلید آن را فرستاده. ارسال در حالت test ثبت می‌شود و هرگز منتقل نمی‌شود.
fromstr
نشانی‌ای که واقعاً مجاز شمرده و روی سیم گذاشته شد، که همیشه همان نشانی درخواست‌شده نیست.
subjectstr | None
همان‌طور که فرستاده شد.
messageIdstr | None
همان Message-ID مربوط به RFC 5322. تا وقتی MIME وجود نداشته باشد null است. سرویس ارسال هدر را در مسیر خروج بازنویسی می‌کند، پس هیچ bounce یا گزارش تحویلی این مقدار را حمل نمی‌کند. چیزی که رویداد با آن برمی‌گردد `id` است.
threadIdstr | None
thread ای که در آن نشست.
transportEmailTransport | str | None
پیام چگونه رفت. تا پیش از ارسال null است.
attemptsint
چند بار ارسال تلاش شده است.
lastErrorstr | None
چرا آخرین تلاش شکست خورد، عیناً.
scheduledAtstr | None
لحظهٔ ISO که باید برود.
cancellableUntilstr | None
تا وقتی اکنون پیش از این لحظه است، لغو هنوز کار می‌کند.
sentAtstr | None
لحظهٔ ISO که رفت.
tagsdict[str, str]
آنچه فرستادید، بازتاب‌داده‌شده.
sourceEmailSource | str
composer، api، mcp، ai، oauth یا form: کدام سطح درخواست داده است. `api` همین کلاینت با کلید API است، و `oauth` همین کلاینت با توکن دسترسی.
createdAtstr
لحظهٔ ISO که رکورد نوشته شد.
replayedbool
وقتی True است که یک Idempotency-Key با ارسالی که از پیش وجود داشته مطابقت کرده باشد. چیز تازه‌ای فرستاده نشده، و این همان پیام اصلی است.
translationNotRequired[EmailTranslationResource]
فقط روی پیامی حاضر است که ترجمه شده، و تنها جایی که کل درخواست ذخیره‌شده حمل می‌شود: همین پاسخ و `get`. یک دیکشنری از `language`، `languageName`، `detectedSourceLanguage`، `subject` و `includeOriginal`، همه به شکل کد نه ردیف زبان. ردیف فهرست هرگز آن را ندارد، پس غیابش آنجا به هیچ سمتی چیزی نمی‌گوید. آن را با `email.get('translation')` بخوانید.

به زبان گیرنده

translate پیام را پیش از رفتنش به زبان کسی دیگر می‌نویسد. بدنه (و موضوع، مگر آن را خاموش کنید) وقتی API درخواست را می‌پذیرد ترجمه می‌شود، و آنچه بیرون آمد همان است که بیرون می‌رود: ترجمه‌ای که نشود تولیدش کرد، به‌جای فرستادن پیام به زبانی که نوشته‌اید، ارسال را رد می‌کند.

translate.py
from openemail import openemail email = openemail.emails.send({    'from': '[email protected]',    'to': '[email protected]',    'subject': 'Your September invoice',    'html': '<p>Invoice attached. Payment is due on the 14th.</p>',    'translate': {'to': 'de'},}) print(email.get('translation'))

کسی پیش از رفتنش آن را نخواند. emails.translate همان رفت‌وبرگشت است که یک گام زودتر متوقف شده. آن را به یک آدم نشان دهید، بگذارید تغییرش دهد، و بعد آنچه را تأیید کرده بدون هیچ translate ای روی فراخوانی بفرستید. دادن دوبارهٔ آن، متن را بار دوم ترجمه می‌کرد و ویرایش‌هایش را دور می‌ریخت.

preview_translation.py
from openemail import openemail preview = openemail.emails.translate({    'subject': 'Your September invoice',    'html': '<p>Invoice attached. Payment is due on the 14th.</p>',    'to': 'de',}) print(preview['language']['native'], preview['detectedSourceLanguage'])print(preview['html']) approved_subject = input(f"Subject [{preview['subject']}]: ") or preview['subject'] or '' openemail.emails.send({    'from': '[email protected]',    'to': '[email protected]',    'subject': approved_subject,    'html': preview['html'] or '',})
render_picker.py
from openemail import LANGUAGES, is_rtl_language, language_by_code, openemail, resolve_language current = openemail.languages.list() german = resolve_language('Deutsch')traditional = resolve_language('zh-TW')upper = language_by_code('DE') assert len(LANGUAGES) == 200assert german is not None and german['code'] == 'de'assert traditional is not None and traditional['code'] == 'zh-Hant'assert upper is not None and upper['native'] == 'Deutsch'assert is_rtl_language('ar')

این جدول درون پکیج هست، به ترتیب picker، پس می‌شود یک picker را پیش از نخستین درخواست پر کرد. languages.list() همان ردیف‌ها را از روی سیم، به شکل فهرستی ساده، برمی‌گرداند، برای فراخواننده‌ای که ردیف‌های کنونی را به ردیف‌هایی که این نسخه با آن‌ها عرضه شده ترجیح می‌دهد. resolve_language یک کد، یک نام انگلیسی، یک نام بومی یا یک نام مستعار می‌گیرد (zh-TW نام مستعار کدی است که دیگر فهرست نمی‌شود)، language_by_code کد را دقیقاً و بدون حساسیت به بزرگی و کوچکی حروف تطبیق می‌دهد، و شانزده ردیف از راست به چپ‌اند. native، label و code را با هم جست‌وجو کنید، native را اول نشان دهید، و کد را ذخیره کنید.

emails.translate به‌طور خودکار دوباره تلاش نمی‌شود. فراخوانی مدل خرج می‌کند و چیزی نمی‌نویسد، پس چیزی برای idempotent کردن نیست و تلاش دوباره پس از درخواستی بی‌پاسخ فقط همان پاسخ را دو بار می‌خرد.

  • زبانی که به هیچ‌چیز حل نشود یک validation_error روی translate.to است، پیش از آنکه چیزی فرستاده شود.
  • translation_too_long برای بیش از 30,000 نویسه، translation_not_configured وقتی نصب هیچ AI پیکربندی‌شده‌ای ندارد، یک 429 با ai_quota_exceeded وقتی آن فضای کاری کنش‌های هوش مصنوعی امروز را مصرف کرده باشد (در نیمه‌شب UTC بازنشانی می‌شود و تلاش دوباره نمی‌شود)، translation_failed وقتی ارائه‌دهنده پاسخ نداده است. هیچ‌کدام به‌عنوان جایگزین، پیام را ترجمه‌نشده نمی‌فرستند.
  • با template کار می‌کند: آنچه ترجمه می‌شود خروجیِ RENDER شده است، پس یک بدنهٔ ذخیره‌شده به هر زبانی که مشتریانتان می‌خوانند خدمت می‌کند. قالبی که یک سند کامل رندر می‌کند، doctype، بلوک‌های <style> و قاعده‌های @font-face خود را نگه می‌دارد: فقط بدنه به مدل می‌رود و بقیه دوباره دورش گذاشته می‌شود. <title> آن دست‌نخورده می‌ماند، که به‌هرحال چیزی نمایشش نمی‌دهد.
  • تلاش دوباره هزینهٔ اضافه ندارد. ترجمه بخشی از اثرانگشت idempotency نیست (خودِ درخواست هست، با translate و همه)، پس بازفرستادن یک ارسال بی‌پاسخ با همان Idempotency-Key پیامی را که از پیش وجود دارد بازپخش می‌کند، نه اینکه بار دوم ترجمه و ارسال کند.
  • پیام ترجمه‌شده‌ای که queued یا scheduled باشد در برابر تغییر واژه‌ها منجمد است. emails.reschedule هنوز جابه‌جایش می‌کند؛ تغییر آنچه می‌گوید یعنی لغو و ارسال دوباره.

پیوست‌ها

content روی سیم base64 است. بایت بدهید تا برایتان رمزگذاری شود.

attachment.py
from pathlib import Path from openemail.types import AttachmentInput attachments: list[AttachmentInput] = [    {        'filename': 'invoice.pdf',        'content': Path('invoice.pdf').read_bytes(),        'contentType': 'application/pdf',    },]

to_base64 export شده است، اگر جای دیگری لازمش دارید. یک str در content همان‌طور که هست فرستاده می‌شود، پس باید از پیش base64 باشد.

مرجع