קבלת אימייל באמצעות Mail API

במדריך הזה מוסבר איך להשתמש ב-Mail API כדי לקבל אימייל.

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

לפני שמתחילים

צריך לרשום את כתובות האימייל של השולחים כשולחים מורשים. מידע נוסף זמין במאמר בנושא מי יכול לשלוח אימייל.

הגדרת האפליקציה לקבלת אימייל

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

כדי להפעיל את שירות האימייל הנכנס, משנים את קובצי התצורה appengine-web.xml ו-web.xml:

הפעלת אימייל ב-appengine-web.xml

משנים את appengine-web.xml על ידי הוספת קטע inbound-services שמאפשר את שירות האימייל הנכנס:

<inbound-services>
  <!-- Used to handle incoming mail. -->
  <service>mail</service>
  <!-- Used to handle bounced mail notifications. -->
  <service>mail_bounce</service>
</inbound-services>

הודעות אימייל נשלחות לאפליקציה שלכם כבקשות HTTP POST באמצעות כתובת ה-URL הבאה:

/_ah/mail/<ADDRESS>

כאשר <ADDRESS> היא כתובת אימייל מלאה, כולל שם הדומיין. שימו לב: גם אם האפליקציה שלכם נפרסה בדומיין מותאם אישית, היא לא יכולה לקבל אימיילים שנשלחים לכתובות בדומיין הזה.

הפעלת אימייל בקובץ web.xml

משנים את web.xml על ידי מיפוי כתובות URL של אימיילים לסרוולטים:

<servlet>
  <servlet-name>mailhandler</servlet-name>
  <servlet-class>com.example.appengine.mail.MailHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>mailhandler</servlet-name>
  <url-pattern>/_ah/mail/*</url-pattern>
</servlet-mapping>
<security-constraint>
  <web-resource-collection>
    <web-resource-name>mail</web-resource-name>
    <url-pattern>/_ah/mail/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>admin</role-name>
  </auth-constraint>
</security-constraint>

בקטעי הקוד שלמעלה, /_ah/mail/* תואם לכל כתובות האימייל של האפליקציה. רכיבי Mail Servlet פועלים בגרסה הנוכחית של האפליקציה ב-App Engine.

שליחה של הודעות נכנסות על סמך דפוסים

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

Concrete handler

public class HandleDiscussionEmail extends MailHandlerBase {

  private static final Logger log = Logger.getLogger(HandleDiscussionEmail.class.getName());
  public HandleDiscussionEmail() { super("discuss-(.*)@(.*)"); }

  @Override
  protected boolean processMessage(HttpServletRequest req, HttpServletResponse res)
    throws ServletException
  {
    log.info("Received e-mail sent to discuss list.");
    MimeMessage msg = getMessageFromRequest(req);
    Matcher match = getMatcherFromRequest(req);
    // ...
    return true;
  }
}

ה-handler הקונקרטי שלמעלה רשום באמצעות קטע הקוד הבא ב-web.xml:

<filter>
  <filter-name>HandleDiscussionEmail</filter-name>
  <filter-class>com.example.appengine.mail.HandleDiscussionEmail</filter-class>
</filter>
<filter-mapping>
  <filter-name>HandleDiscussionEmail</filter-name>
  <url-pattern>/_ah/mail/*</url-pattern>
</filter-mapping>

הערה: אי אפשר להשתמש בהנחיות security-constraint במסננים. צריך להגדיר את מדיניות האבטחה ב-handler בדרך אחרת.

Abstract handler

public abstract class MailHandlerBase implements Filter {

  private Pattern pattern = null;

  protected MailHandlerBase(String pattern) {
    if (pattern == null || pattern.trim().length() == 0)
    {
      throw new IllegalArgumentException("Expected non-empty regular expression");
    }
    this.pattern = Pattern.compile("/_ah/mail/"+pattern);
  }

  @Override public void init(FilterConfig config) throws ServletException { }

  @Override public void destroy() { }

  /**
   * Process the message. A message will only be passed to this method
   * if the servletPath of the message (typically the recipient for
   * appengine) satisfies the pattern passed to the constructor. If
   * the implementation returns false, control is passed
   * to the next filter in the chain. If the implementation returns
   * true, the filter chain is terminated.
   *
   * The Matcher for the pattern can be retrieved via
   * getMatcherFromRequest (e.g. if groups are used in the pattern).
   */
  protected abstract boolean processMessage(HttpServletRequest req, HttpServletResponse res) throws ServletException;

  @Override
  public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain)
      throws IOException, ServletException {

    HttpServletRequest req = (HttpServletRequest) sreq;
    HttpServletResponse res = (HttpServletResponse) sres;

    MimeMessage message = getMessageFromRequest(req);
    Matcher m = applyPattern(req);

    if (m != null && processMessage(req, res)) {
      return;
    }

    chain.doFilter(req, res); // Try the next one

  }

  private Matcher applyPattern(HttpServletRequest req) {
    Matcher m = pattern.matcher(req.getServletPath());
    if (!m.matches()) m = null;

    req.setAttribute("matcher", m);
    return m;
  }

  protected Matcher getMatcherFromRequest(ServletRequest req) {
    return (Matcher) req.getAttribute("matcher");
  }

  protected MimeMessage getMessageFromRequest(ServletRequest req) throws ServletException {
    MimeMessage message = (MimeMessage) req.getAttribute("mimeMessage");
    if (message == null) {
      try {
        Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);
        message = new MimeMessage(session, req.getInputStream());
        req.setAttribute("mimeMessage", message);

      } catch (MessagingException e) {
        throw new ServletException("Error processing inbound message", e);
      } catch (IOException e) {
        throw new ServletException("Error processing inbound message", e);
      }
    }
    return message;
  }
}

טיפול באימייל נכנס

‫JavaMail API כולל את המחלקה MimeMessage שבה אפשר להשתמש כדי לנתח הודעות אימייל נכנסות. ל-MimeMessage יש constructor שמקבל java.io.InputStream וסשן JavaMail, שיכול להיות עם הגדרה ריקה.

יוצרים מכונת MimeMessage כמו זו:

import java.io.IOException;
import java.util.logging.Logger;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MailHandlerServlet extends HttpServlet {

  private static final Logger log = Logger.getLogger(MailHandlerServlet.class.getName());

  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);
    try {
      MimeMessage message = new MimeMessage(session, req.getInputStream());
      log.info("Received mail message.");
    } catch (MessagingException e) {
      // ...
    }
    // ...
  }
}

לאחר מכן, אפשר להשתמש בשיטות שונות כדי לנתח את אובייקט message:

  • מתקשרים אל getFrom() כדי להחזיר את השולח של ההודעה.
  • מתקשרים אל getContentType() כדי לחלץ את סוג התוכן של ההודעה. השיטה getContent() מחזירה אובייקט שמיישם את הממשק Multipart.
  • מתקשרים אל getCount() כדי לברר את מספר החלקים
  • מתקשרים אל getBodyPart(int index) כדי להחזיר חלק גוף מסוים.

אחרי שמגדירים את האפליקציה לטיפול באימיילים נכנסים, אפשר להשתמש במסוף של שרת הפיתוח כדי לדמות הודעות אימייל נכנסות. מידע נוסף, כולל הוראות להפעלת שרת הפיתוח, זמין במאמר שרת הפיתוח ל-Java. אחרי שמפעילים את האפליקציה בשרת הפיתוח המקומי, אפשר לגשת אליה דרך כתובת ה-URL http://localhost:8888/_ah/admin/. אם לא משתמשים ביציאה שמוגדרת כברירת מחדל לשרת הפיתוח המקומי, צריך להחליף את הערך 8888 ביציאה שבה משתמשים.

בשרת הפיתוח, לוחצים על Inbound Mail (אימייל נכנס) בצד ימין, ממלאים את הטופס שמופיע ולוחצים על Send Email (שליחת אימייל).