कॉन्फ़िगरेशन
क्लाइंट बनाने के तीन तरीके, हर option, और रिक्वेस्ट भेजे जाने से पहले वह क्या अस्वीकार करता है।
विकल्प
import os from openemail import AsyncOpenEmail, OpenEmail, init, openemail init(os.environ['OPENEMAIL_API_KEY'])openemail.me.ping() billing = OpenEmail(os.environ['BILLING_API_KEY']) pinned = OpenEmail('oe_live_...', base_url='https://api.openemail.uk') background = AsyncOpenEmail()| प्रवेश बिंदु | यह आपको क्या देता है |
|---|---|
| init(...) | साझा क्लाइंट को कॉन्फ़िगर करके उसे लौटाता है। इसके बाद हर मॉड्यूल में openemail वही क्लाइंट होता है, और जो कुछ आप छोड़ देते हैं वह एनवायरनमेंट से पढ़ा जाता है। |
| openemail | साझा क्लाइंट। init से पहले इस्तेमाल करने पर यह पहली कॉल पर खुद को OPENEMAIL_API_KEY और OPENEMAIL_BASE_URL से बना लेता है। |
| OpenEmail(...) | एक अलग क्लाइंट, साझा कुंजी के साथ-साथ दूसरी कुंजी के लिए, या वह instance बनाने के लिए जिसे आपका अपना मॉड्यूल export करता है। जो कुछ आप छोड़ देते हैं वह init की तरह एनवायरनमेंट से पढ़ा जाता है, और create_client यही क्लास है, बस दूसरे नाम से। |
| AsyncOpenEmail(...) | उन्हीं विकल्पों वाला एक अलग एसिंक्रोनस क्लाइंट, जिसके हर मेथड को await किया जाता है। साझा openemail सिंक्रोनस है, इसलिए यह क्लाइंट आपको ख़ुद बनाना होता है। |
| get_client() और reset_client() | get_client साझा क्लाइंट को ही लौटाता है, और अगर init नहीं चला है तो उसे एनवायरनमेंट से बना लेता है। reset_client उसे भुला देता है, इसलिए अगली बार इस्तेमाल होने पर नया क्लाइंट बनता है। |
import httpxfrom openemail import init init( 'oe_live_...', base_url='https://api.openemail.uk', timeout=30, max_retries=2, http_client=httpx.Client(proxy='http://proxy.internal:3128'), headers={'X-Team': 'billing'}, user_agent='billing-service/1.4', disable_update_notice=True,)| विकल्प | डिफ़ॉल्ट | टिप्पणियाँ |
|---|---|---|
| api_key | OPENEMAIL_API_KEY | init और OpenEmail इसे एनवायरनमेंट से पढ़ते हैं। इसकी शुरुआत oe_live_ या oe_test_ से होनी चाहिए। |
| access_token | OPENEMAIL_ACCESS_TOKEN | api_key की जगह एक OAuth एक्सेस टोकन, या उसे लौटाने वाला फ़ंक्शन। नीचे OAuth एक्सेस टोकन देखें। |
| base_url | https://api.openemail.uk | या OPENEMAIL_BASE_URL। आख़िर का slash हटा दिया जाता है, और init तथा OpenEmail सादे होस्ट के आगे https:// लगाते हैं, और इसी मशीन के होस्ट के आगे http://: localhost, कोई 127.x.x.x पता या ::1। कोई क्रेडेंशियल कभी सादे http पर किसी दूसरे होस्ट को नहीं भेजा जाता, और 0.0.0.0 या [::] क्लाइंट बनाते समय ही त्रुटि raise करता है, क्योंकि ये वे पते हैं जिन पर सर्वर सुनता है, अनुरोध भेजने के पते नहीं। |
| timeout | 30 | सेकंड में, प्रति प्रयास, प्रति कॉल नहीं। यह सिर्फ़ headers ही नहीं, body पढ़ने को भी कवर करता है। 0 इसे बंद कर देता है। files.upload कम से कम 600 सेकंड देता है, जब तक कॉल अपना timeout पास न करे। |
| max_retries | 2 | पहली कोशिश के बाद के अतिरिक्त प्रयास, उन कॉलों पर जिन्हें दोहराना सुरक्षित है। यह क्लाइंट पर सेट होता है, प्रति कॉल नहीं। |
| http_client | एक नया httpx.Client | proxy, अपनी TLS सेटिंग्स, mounted transport या test double के लिए अपना ख़ुद का क्लाइंट पास करें: OpenEmail को एक httpx.Client, और AsyncOpenEmail को एक httpx.AsyncClient। क्लाइंट बंद करने पर भी आपका पास किया हुआ क्लाइंट खुला रहता है। |
| headers | {} | हर रिक्वेस्ट पर भेजा जाता है। |
| user_agent | openemail-python/<version> | हर रिक्वेस्ट पर भेजा जाता है। |
| disable_update_notice | False | PyPI पर नए वर्शन की प्रति-प्रोसेस-एक-बार वाली जाँच को छोड़ देता है। यह जाँच तभी चलती है जब आउटपुट किसी terminal पर जा रहा हो, और OPENEMAIL_DISABLE_UPDATE_NOTICE भी इसे बंद कर देता है। |
भेजने से पहले यह क्या अस्वीकार करता है
ये आपके पहले send पर किसी उलझाऊ विफलता के रूप में सामने आने के बजाय, कोई भी अनुरोध भेजे जाने से पहले ही ValueError raise करते हैं, या जहाँ तालिका ऐसा बताती है वहाँ TypeError। संदेश बताता है कि क्या ग़लत था और उसकी जगह क्या पास करना है।
| अस्वीकृत | क्यों |
|---|---|
| कोई कुंजी ही नहीं | न api_key सेट था और न OPENEMAIL_API_KEY, इसलिए प्रमाणीकरण के लिए कुछ है ही नहीं। |
| कोई session cookie या session token | यहाँ केवल oe_live_ और oe_test_ ही प्रमाणित होते हैं, और API भी यही कहता है। यह जाँच सिर्फ़ prefix देखती है, इससे ज़्यादा कुछ नहीं, इसलिए रद्द की गई कुंजी फिर भी नेटवर्क पर जाकर ही विफल होती है। |
| ऐसा base_url जो http या https URL न हो | और कुछ fetch किया ही नहीं जा सकता, इसलिए क्लाइंट पहले अनुरोध पर विफल होने के बजाय बनते समय ही इसे अस्वीकार कर देता है। |
| 0.0.0.0 या [::] पर कोई base_url | यह वह पता है जिस पर सर्वर सुनता है, अनुरोध भेजने का पता नहीं। संदेश इसकी जगह उसी पोर्ट के साथ 127.0.0.1 या [::1] का नाम लेता है। |
| सादे http पर कोई क्रेडेंशियल | कॉल पर ही, अनुरोध निकलने से पहले, अस्वीकार कर दिया जाता है, जब तक सर्वर इसी मशीन पर न हो। नेटवर्क पर मौजूद कोई भी उसे पढ़ सकता है। |
| किसी भी method पर खाली या सिर्फ़ बिंदुओं वाला id | मेथड कॉल होते ही raise होता है। बिंदुओं वाला path segment हर URL parser हटा देता है, इसलिए अनुरोध किसी दूसरे endpoint पर पहुँच जाता। |
| ग़लत किस्म का http_client | क्लाइंट बनते समय एक TypeError। OpenEmail एक httpx.Client लेता है, और AsyncOpenEmail एक httpx.AsyncClient। |
| body का ऐसा मान जिसे JSON नहीं ले जा सकता | उसके type का नाम बताता हुआ एक TypeError। JSON types जैसे के तैसे जाते हैं, और datetime, date या set आपके लिए बदल दिया जाता है। |
test_mode नाम का कोई विकल्प नहीं है और न होगा। कुंजी की योजना संकेत नहीं, बल्कि credential का हिस्सा है, इसलिए mode कुंजी का ही गुण है। client.mode prefix पढ़ता है और तय कुछ नहीं करता।
एक क्लाइंट, कई कुंजियाँ
क्लाइंट एक बार बनाइए और उसी को साझा कीजिए। हर अनुरोध पर नया instance बनाना कनेक्शन पूल और कॉन्फ़िगरेशन को बेवजह फेंक देता है, और उस पर रखी कोई भी स्थिति प्रति-कॉलर नहीं होती।
एक क्लाइंट को कई थ्रेड्स के बीच साझा करना सुरक्षित है। close() या with ब्लॉक का अंत उसके खोले हुए कनेक्शन पूल को बंद कर देता है, और आपका पास किया हुआ http_client खुला रहता है ताकि आप उसे ख़ुद बंद करें।
जिस स्थिति में वरना हर कुंजी के लिए एक instance बनाना पड़ता (जैसे कई workspaces की ओर से भेजने वाला कोई job), वहाँ कॉल पर ही api_key पास करें। यह उस रिक्वेस्ट के लिए Authorization header बदल देता है और क्लाइंट पर कुछ भी पीछे नहीं छोड़ता।
from openemail.types import EmailSend workspace_key = 'oe_live_...' message: EmailSend = {'from': sender, 'to': recipient, 'subject': subject, 'text': text} client.emails.send(message) client.emails.send(message, api_key=workspace_key) client.threads.list(folder='inbox', api_key=workspace_key)client.webhooks.list(api_key=workspace_key)temp_mail के बाहर हर मेथड इसे timeout के साथ एक keyword आर्ग्युमेंट के रूप में लेता है। अनुरोध भेजे जाने से पहले इसकी जाँच उसी नियम से होती है जो कंस्ट्रक्टर इस्तेमाल करता है, इसलिए टाइपो पर api_key= on this call का नाम लेता हुआ एक ValueError raise होता है, न कि किसी ऐसे क्रेडेंशियल के बारे में 401 जिसे फिर आपको ढूँढ़ने जाना पड़े। दोबारा आज़माई गई कॉल वही कुंजी रखती है जो उसे दी गई थी।
timeout सेकंड में होता है और सिर्फ़ उस एक कॉल के लिए, उसके हर प्रयास पर, क्लाइंट के timeout की जगह लेता है।
client.mode उस कुंजी का वर्णन करता है जिससे क्लाइंट बनाया गया था और यह किसी override के पीछे नहीं चलता। जब एक ही क्लाइंट कई कुंजियों को सेवा देता है तो बताने लायक कोई एक mode रहता ही नहीं, इसलिए इसे उसी कुंजी से पढ़ें जो आपने पास की थी।
ऐसा endpoint जिसे कोई मेथड नहीं लपेटता
client.raw.request क्लाइंट का क्रेडेंशियल, base URL, timeout और retry नीति लागू करके एक अनुरोध भेजता है, और पार्स किया हुआ JSON लौटाता है। यह method, query, body, api_key और timeout लेता है। यह GET को दोबारा आज़माता है और बाकी सब कुछ एक ही बार भेजता है, जब तक आप repeatable=True पास न करें। idempotent=True एक Idempotency-Key जोड़ता है: वह जो आप idempotency_key के रूप में पास करते हैं, या एक नई।
result = client.raw.request('/something-new', method='POST', body={'name': 'Invoices'})path एक अकेले / से शुरू होना चाहिए। इसके अलावा कुछ भी, जैसे //host/x, अनुरोध भेजे जाने से पहले ही त्रुटि raise करता है, और ऐसा path भी जिसका पूरा बना URL base URL के origin से बाहर चला जाए, ताकि उसके साथ जाने वाला क्रेडेंशियल कभी किसी दूसरे होस्ट तक न पहुँचे।
डिस्पोज़ेबल इनबॉक्स
create_temp_mail() ऐसा क्लाइंट बनाता है जो कोई API कुंजी नहीं रखता, और create_async_temp_mail() उसका एसिंक्रोनस जुड़वाँ है। यह गुमनाम रूप से इनबॉक्स बनाता है, और हर पढ़ाई वह इनबॉक्स टोकन भेजती है जो create ने लौटाया था, या वह नया टोकन जो extend ने लौटाया था, या तो प्रति कॉल inbox_token के रूप में, या एक बार create_temp_mail(inbox_token=...) के रूप में।
from openemail import create_temp_mail temp_mail = create_temp_mail() inbox = temp_mail.create()messages = temp_mail.list_messages(inbox['id'], inbox_token=inbox['token']) print(messages['items'], messages['expiresAt'])OAuth एक्सेस टोकन
अभी जारी नहीं हुआकिसी व्यक्ति का OAuth से जोड़ा गया ऐप, जैसे कमांड लाइन टूल या कोई एजेंट, API कुंजी के बजाय एक्सेस टोकन रखता है। उसे access_token के रूप में दें: या तो ख़ुद टोकन, या ऐसा फ़ंक्शन जो उसे लौटाए, और AsyncOpenEmail पर यह फ़ंक्शन async भी हो सकता है। फ़ंक्शन हर कॉल के लिए एक बार चलता है, और उस कॉल की दोबारा कोशिशें वही इस्तेमाल करती हैं जो उसने लौटाया, इसलिए समय-सीमा पास आने पर टोकन को उसी फ़ंक्शन के अंदर नया करें और क्लाइंट को कभी दोबारा बनाना नहीं पड़ेगा।
from openemail import OpenEmail client = OpenEmail(access_token=session.fresh_access_token) me = client.me.get() if me['object'] == 'oauth_token': print(me['clientId'], me['expiresAt'])| स्थिति | क्या होता है |
|---|---|
| api_key और access_token दोनों साथ, या कोई भी नहीं | कंस्ट्रक्टर ValueError raise करता है। कोई भी न होने पर संदेश OPENEMAIL_API_KEY और OPENEMAIL_ACCESS_TOKEN का नाम लेता है। |
| ऐसा मान जो टोकन नहीं है | टोकन 1 से 512 अक्षरों का होता है और oe_ से शुरू नहीं होता, यही जाँच is_access_token करता है। इसमें विफल string कंस्ट्रक्टर से ही raise करती है, और ऐसा मान लौटाने वाला फ़ंक्शन कुछ भी भेजे जाने से पहले कॉल को विफल कर देता है। |
| OPENEMAIL_ACCESS_TOKEN | जब आप दोनों में से कोई क्रेडेंशियल नहीं देते और OPENEMAIL_API_KEY सेट नहीं है, तब init, OpenEmail और साझा openemail इसे पढ़ते हैं, यानी वातावरण में मौजूद कुंजी को प्राथमिकता मिलती है। |
| ऐसा फ़ंक्शन जो त्रुटि raise करे | कॉल वही त्रुटि, बिना बदले, raise करती है, और कुछ नहीं भेजा जाता। |
| OpenEmail पर ऐसा फ़ंक्शन जो awaitable लौटाए | एक ValueError, क्योंकि सिंक्रोनस क्लाइंट उसका इंतज़ार नहीं कर सकता। AsyncOpenEmail पर फ़ंक्शन async हो सकता है। |
| हर कॉल का अपना api_key | सिर्फ़ उस एक अनुरोध के लिए टोकन की जगह लेता है, और फ़ंक्शन नहीं चलता। |
| mode | टोकन के साथ हमेशा live। |
| create_temp_mail() | वातावरण में जो भी हो, कोई क्रेडेंशियल नहीं भेजता। |
| me.get() और me.ping() | टोकन के लिए get जवाब देता है: object में oauth_token, id और roleId में None, जुड़े ऐप का clientId, और expiresAt, यानी वह समय जब व्यक्ति की ऐप को दी मंज़ूरी समाप्त होती है। ping जवाब देता है: kind में oauth, keyId में None और clientId। KeyResource और PingResource union हैं, इसलिए clientId या expiresAt पढ़ने से पहले object या kind से पहचानें। |
टोकन किसी व्यक्ति की ओर से काम करता है और उसका मेल वैसे ही पढ़ता है जैसे वह ख़ुद पढ़ सकता है, इसलिए इसे कुंजी की तरह सर्वर पर ही रखें।
सत्यापन कोड
अभी जारी नहीं हुआकिसी संवेदनशील बदलाव से पहले, जैसे डोमेन हटाना या webhook बदलना, API एक्सेस टोकन से वह सत्यापन कोड माँगता है जो वेब ऐप व्यक्ति से माँगता। कॉल एक OpenEmailApiError raise करती है जिसका is_step_up_required True होता है, और कुछ नहीं बदला गया। कोड माँगें, व्यक्ति से मिला कोड सत्यापित करें, फिर कॉल दोबारा करें। API कुंजी से कभी नहीं पूछा जाता।
from openemail import OpenEmailApiError try: client.domains.delete(domain_id)except OpenEmailApiError as error: if not error.is_step_up_required: raise challenge = client.security.begin_step_up() if challenge['method'] == 'email': prompt = f'Enter the code we emailed to {challenge.get("sentTo")}: ' else: prompt = 'Enter the code from your authenticator app, or a backup code: ' client.security.verify_step_up({'code': input(prompt)}) client.domains.delete(domain_id)| मेथड | यह क्या करता है |
|---|---|
| security.step_up_status() | ऐप अभी सत्यापित है या नहीं (elevated, elevatedUntil), अगला कोड कैसे जाँचा जाएगा (method, email या totp), और minutes, यानी अवधि की लंबाई। यह कुछ नहीं भेजता और रोक की जानकारी नहीं देता। |
| security.begin_step_up(body=None) | एक सत्यापन शुरू करता है। email में उस पते पर छह अंकों का कोड जाता है जिससे व्यक्ति साइन इन करता है, और sentTo उसे छिपे रूप में दिखाता है। totp में व्यक्ति अपने ऑथेंटिकेटर ऐप से कोड पढ़ता है या रिकवरी कोड इस्तेमाल करता है। अभी खुला सत्यापन, जिसकी कोशिशें बाकी हों, दोबारा इस्तेमाल होता है, जब तक आप {'resend': True} न दें, और लॉक या समाप्त हुए सत्यापन की जगह सादी कॉल नया खोलती है। हर ऐप हर व्यक्ति के लिए एक घंटे में 5 और 24 घंटे में 20 सत्यापन खोल सकता है, और अगला 429 step_up_throttled raise करता है। |
| security.verify_step_up({'code': code}) | कोड जाँचता है और इस ऐप के लिए संवेदनशील बदलावों को 60 मिनट तक, elevatedUntil तक, REST पर और वही बदलाव करने वाले MCP टूल के ज़रिए खोल देता है। इस ऐप से 24 घंटे में 10 गलत कोड, या व्यक्ति के सभी ऐप से मिलाकर 20, के बाद यह कॉल और begin_step_up 429 step_up_locked raise करते हैं, एक संदेश के साथ जो बताता है कि सत्यापन फिर कब शुरू होगा। |
क्लाइंट ख़ुद कभी कोड नहीं माँगता और न कॉल दोहराता है, और न begin_step_up और न verify_step_up अपने-आप दोबारा आज़माया जाता है, क्योंकि खोए जवाब के बाद दोबारा कोशिश दूसरा ईमेल भेज सकती है या दूसरी कोशिश ख़र्च कर सकती है। इन्हें scope की ज़रूरत नहीं, और इनमें से किसी को कॉल करने वाली API कुंजी को 400 step_up_not_applicable मिलता है। STEP_UP_ERROR_CODES उन सब तरीकों के नाम देता है जिनसे सत्यापन विफल हो सकता है, और API का त्रुटियों वाला पेज बताता है कि हर एक पर क्या करें।