Mail API for Python

בדף הזה מוסבר איך להשתמש ב-Mail API, אחד מהשירותים המצורפים מדור קודם, עם Python runtime בסביבה הרגילה. האפליקציה יכולה לגשת לשירותים בחבילה באמצעות App Engine services SDK for Python.

סקירה כללית

ב-Python, הפונקציונליות של טיפול בדואר כלולה במודול google.appengine.api.mail. זה שונה מ-Python 2, שבו מודול mail_handlers סופק על ידי webapp. אפשר להשתמש ב-Mail API for Python כדי לקבל אימיילים והתראות על אימיילים חוזרים.

שימוש ב-Mail API

האפליקציה מקבלת אימייל כשאימייל נשלח כגוף הבקשה בבקשת HTTP POST. כדי שהאפליקציה תטפל באימיילים נכנסים, היא צריכה להתאים לכתובת ה-URL עם הנתיב /_ah/mail/[ADDRESS]. החלק [ADDRESS] בנתיב הוא בדרך כלל כתובת אימייל עם הסיומת @<Cloud-Project-ID>.appspotmail.com. אימיילים שיישלחו לאפליקציה בפורמט הזה ינותבו לפונקציה.

ב-Python לא צריך לציין סקריפט לטיפול בבקשות בקובץ app.yaml, לכן אפשר להסיר את כל הקטעים handler בקובץ app.yaml.

בקובץ app.yaml צריכות להישאר השורות הבאות:

inbound_services:
- mail
- mail_bounce

שליחת אימייל

אין צורך לבצע שינויים בהגדרות של האפליקציה כשמשדרגים ל-Python. ההתנהגות, התכונות והוראות ההגדרה לשליחת אימיילים נשארים זהים. מידע נוסף זמין בהפניית ה-API של Mail ל-Python.

קבלת דואר

כדי לקבל אימייל, צריך לייבא את המודול google.appengine.api.mail ולהשתמש במחלקה InboundEmailMessage כדי לייצג אימייל. צריך ליצור מופע של המחלקה הזו כדי לאחזר את תוכן האימייל מבקשת ה-HTTP הנכנסת.

ב-Python 2, אפליקציות יכלו לגשת למחלקה InboundEmailMessage על ידי החלפת השיטה receive() ב-handler של אפליקציית האינטרנט InboundEmailHandler. אין צורך בכך ב-Python, במקום זאת האפליקציה צריכה ליצור מופע של אובייקט חדש.

מסגרות אינטרנט

כשמשתמשים ב-frameworks של Python לאינטרנט, הקונסטרוקטור InboundEmailMessage מקבל את הבייטים של גוף בקשת ה-HTTP. יש כמה דרכים ליצור את האובייקט InboundEmailMessage ב-Python. דוגמאות לאפליקציות Flask ו-Django:

Python 3 (Flask)

ב-Flask, ‏ request.get_data() מחזיר את הבייטים של הבקשה.

@app.route("/_ah/mail/<path>", methods=["POST"])
def receive_mail(path):
    message = mail.InboundEmailMessage(request.get_data())

    # Do something with the message
    print(
        f"Received greeting for {escape(message.to)} at {escape(message.date)} from {escape(message.sender)}"
    )
    for content_type, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    return "OK", 200

‫Python 3 (Django)

ב-Django, ‏ request.body מחזיר את הבייטים של גוף בקשת ה-HTTP.

def receive_mail(request):
    message = mail.InboundEmailMessage(request.body)

    print(f"Received greeting for {message.to} at {message.date} from {message.sender}")
    for _, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    return HttpResponse("OK")

כדי לראות את דוגמאות הקוד המלאות מהמדריך הזה, אפשר לעיין ב-GitHub.

מסגרות אחרות שתואמות ל-WSGI

במסגרות אחרות שתואמות ל-WSGI, מומלץ להשתמש באותה שיטה כמו בדוגמאות של Flask ו-Django כדי ליצור את InboundEmailMessage. השיטה הזו פועלת כשהבייטים של גוף בקשת ה-HTTP זמינים ישירות.

אפליקציית WSGI ללא מסגרת אינטרנט

אם האפליקציה שלכם היא אפליקציית WSGI שלא משתמשת במסגרת אינטרנט, יכול להיות שהבייטים של גוף בקשת ה-HTTP לא זמינים ישירות. אם הבייטים של גוף בקשת ה-HTTP זמינים ישירות, מומלץ להשתמש במסגרת אינטרנט של Python.

בשפת Python, מוגדרת מתודת factory בשם from_environ בשביל InboundEmailMessage. השיטה הזו היא שיטת מחלקה שמקבלת את מילון ה-WSGI‏ environ כקלט, ואפשר להשתמש בה בכל אפליקציית WSGI.

בדוגמה הבאה אפשר לראות איך environ משמש כקלט כדי לקבל את mail_message:

‫Python 3 (אפליקציית WSGI)

def HelloReceiver(environ, start_response):
    if environ["REQUEST_METHOD"] != "POST":
        return ("", http.HTTPStatus.METHOD_NOT_ALLOWED, [("Allow", "POST")])

    message = mail.InboundEmailMessage.from_environ(environ)

    print(f"Received greeting for {message.to} at {message.date} from {message.sender}")
    for content_type, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    response = http.HTTPStatus.OK
    start_response(f"{response.value} {response.phrase}", [])
    return ["success".encode("utf-8")]

קבלת הודעות חוזרות

התראה על החזרה (bounce) היא הודעה אוטומטית ממערכת אימייל שמציינת בעיה בשליחת ההודעות מהאפליקציה. כדי לעבד הודעות על החזרת אימייל, האפליקציה צריכה להתאים בין נתיבי כתובות URL נכנסות לבין /_ah/bounceהנתיב.

בדומה ל-InboundEmailMessage, המחלקה BounceNotification ל-Python 2 הייתה נגישה על ידי החלפת השיטה receive() ב-handler של אפליקציית האינטרנט BounceNotificationHandler.

ב-Python, האפליקציה צריכה ליצור מופע של אובייקט BounceNotification, שאפשר ליצור בכמה דרכים בהתאם למסגרת האינטרנט של Python שבה נעשה שימוש.

מסגרות אינטרנט

האובייקט BounceNotification מאותחל עם הערכים שמתקבלים מקריאה ל-post_vars.get(key).

כשמשתמשים במסגרת אינטרנט של Python, כמו Flask או Django, מחלצת הנתונים BounceNotification מקבלת מילון בשם post_vars, שמכיל את בקשת ה-POST של נתוני הטופס. כדי לאחזר את הנתונים, צריך להגדיר את השיטה get() באובייקט הקלט. הפרמטר key הוא רשימה של ערכי קלט שאפשר לקרוא ולאחזר באמצעות BounceNotification, והוא יכול להיות כל אחת מהאפשרויות הבאות:

original-to, original-cc, original-bcc, original-subject, original-text, notification-from, notification-to, notification-cc, notification-bcc, notification-subject, notification-text, raw-message

ברוב מסגרות האינטרנט, הנתונים האלה זמינים כמילון מרובה באובייקט הבקשה. אפשר להמיר את רוב הסוגים האלה למילון עם מפתחות מסוג מחרוזת.

לכל המפתחות חוץ מ-raw-message, הערך יכול להיות כל דבר. בדרך כלל, הערך הוא ערך יחיד כמו מחרוזת, או רשימת ערכים כמו {'to': ['bob@example.com', 'alice@example.com']}. ערך ברירת המחדל של כל השדות הוא מחרוזת ריקה. הערכים האלה יאכלסו את המאפיינים original ו-notification.

עבור המפתח raw-message, הערך צריך להיות קלט תקין לבונה של EmailMessage. הוא יכול להיות ערך יחיד או רשימה עם ערך יחיד. המפתח raw-message משמש לאתחול המאפיין original_raw_message של האובייקט.

‫Python 2 (webapp2)

class LogBounceHandler(BounceNotificationHandler):
    def receive(self, bounce_message):
        logging.info('Received bounce post ... [%s]', self.request)
        logging.info('Bounce original: %s', bounce_message.original)
        logging.info('Bounce notification: %s', bounce_message.notification)

Python 3 (Flask)

ב-Flask, request.form מהסוג werkzeug.datastructures.MultiDict נותן את משתני ה-POST. עם זאת, ה-method get() עבור הסוג הזה מחזירה רק ערך אחד, גם אם יש כמה ערכים למפתח.

כדי לקבל את כל הערכים שמתאימים למפתח, האפליקציה צריכה לקרוא ל-dict(request.form.lists()), וכתוצאה מכך מתקבל מילון שבו כל ערך הוא רשימה.

@app.route("/_ah/bounce", methods=["POST"])
def receive_bounce():
    bounce_message = mail.BounceNotification(dict(request.form.lists()))

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    return "OK", 200

‫Python 3 (Django)

ב-Django, ‏ request.POST מסוג django.http.QueryDict מחזיר את משתני ה-POST. עם זאת, ה-method get() עבור הסוג הזה מחזירה רק ערך אחד, גם אם יש כמה ערכים למפתח.

כדי לקבל את כל הערכים שמתאימים למפתח מסוים, האפליקציה צריכה לקרוא ל-dict(request.POST.lists()), וכתוצאה מכך מתקבל מילון שבו כל ערך הוא רשימה.

def receive_bounce(request):
    bounce_message = mail.BounceNotification(dict(request.POST.lists()))

    # Do something with the message
    print(f"Bounce original: {bounce_message.original}")
    print(f"Bounce notification: {bounce_message.notification}")

    return HttpResponse("OK")

אפליקציית WSGI ללא מסגרת אינטרנט

אם האפליקציה שלכם היא אפליקציית WSGI שלא משתמשת במסגרת אינטרנט, יכול להיות שמשתני הטופס של בקשת ה-HTTP POST לא זמינים ישירות במילון.

בשפת Python, מוגדרת מתודת factory בשם from_environ בשביל BounceNotification. השיטה הזו היא שיטת מחלקה שמקבלת את מילון ה-WSGI‏ environ כקלט, ואפשר להשתמש בה בכל אפליקציית WSGI.

בדוגמה הבאה אפשר לראות איך environ משמש כקלט כדי לקבל את bounce_message:

Python 3

def BounceReceiver(environ, start_response):
    if environ["REQUEST_METHOD"] != "POST":
        return ("", http.HTTPStatus.METHOD_NOT_ALLOWED, [("Allow", "POST")])

    bounce_message = mail.BounceNotification.from_environ(environ)

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    # Return suitable response
    response = http.HTTPStatus.OK
    start_response(f"{response.value} {response.phrase}", [])
    return ["success".encode("utf-8")]

דוגמאות קוד

כדי לראות את דוגמאות הקוד המלאות מהמדריך הזה, אפשר לעיין ב-GitHub.