Postmark

Postmark Outbound

Postmark Inbound

Message Retrieval

Stats API

Account API

Server API

Triggers API

Parse an email with Postmark

A server’s inbound mailbox is a unique hash generated when the server is created. Likely you will not share this mailbox identity directly with your customer, but instead set up email forwarding (i.e. from inbound@yourapp.com to yourhash@inbound.postmarkapp.com). You can do this by using Postmark's Inbound Domain Forwarding or you can read our help article to learn how to configure a custom forwarding email address from you Gmail/Google Apps account. Your unique hash is provided in the InboundHash property of the JSON object returned from the GET /servers/{ID} command in the Servers API.

If you haven’t already, you may want to review the process of configuring your inbound server.

When an email is received by Postmark at your unique mailbox address, it is immediately sent the hook URL you provided for that mailbox during configuration. The HTTP POST will contain a JSON body similar to the following:

{
  "From": "myUser@theirDomain.com",
  "FromName": "My User",
  "FromFull": {
    "Email": "myUser@theirDomain.com",
    "Name": "John Doe",
    "MailboxHash":""
  },
  "To": "451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com",
  "ToFull": [
    {
      "Email": "451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com",
      "Name": "",
      "MailboxHash": "ahoy"
    }
  ],
  "Cc": "\"Full name\" <sample.cc@emailDomain.com>, \"Another Cc\" <another.cc@emailDomain.com>",
  "CcFull": [
    {
      "Email": "sample.cc@emailDomain.com",
      "Name": "Full name",
      "MailboxHash": ""
    },
    {
      "Email": "another.cc@emailDomain.com",
      "Name": "Another Cc",
      "MailboxHash": ""
    }
  ],
  "Bcc": "\"Full name\" <451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com>",
  "BccFull": [
    {
      "Email": "451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com",
      "Name": "Full name",
      "MailboxHash": "ahoy"
    }
  ],
  "ReplyTo": "myUsersReplyAddress@theirDomain.com",
  "Subject": "This is an inbound message",
  "MessageID": "22c74902-a0c1-4511-804f2-341342852c90",
  "Date": "Thu, 5 Apr 2012 16:59:01 +0200",
  "MailboxHash": "ahoy",
  "TextBody": "[ASCII]",
  "HtmlBody": "[HTML(encoded)]",
  "StrippedTextReply": "Ok, thanks for letting me know!",
  "Tag": "",
  "Headers": [
    {
      "Name": "X-Spam-Checker-Version",
      "Value": "SpamAssassin 3.3.1 (2010-03-16) onrs-ord-pm-inbound1.wildbit.com"
    },
    {
      "Name": "X-Spam-Status",
      "Value": "No"
    },
    {
      "Name": "X-Spam-Score",
      "Value": "-0.1"
    },
    {
      "Name": "X-Spam-Tests",
      "Value": "DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_PASS"
    },
    {
      "Name": "Received-SPF",
      "Value": "Pass (sender SPF authorized) identity=mailfrom; client-ip=209.85.160.180; helo=mail-gy0-f180.google.com; envelope-from=myUser@theirDomain.com; receiver=451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com"
    },
    {
      "Name": "DKIM-Signature",
      "Value": "v=1; a=rsa-sha256; c=relaxed\/relaxed;        d=wildbit.com; s=google;        h=mime-version:reply-to:date:message-id:subject:from:to:cc         :content-type;        bh=cYr\/+oQiklaYbBJOQU3CdAnyhCTuvemrU36WT7cPNt0=;        b=QsegXXbTbC4CMirl7A3VjDHyXbEsbCUTPL5vEHa7hNkkUTxXOK+dQA0JwgBHq5C+1u         iuAJMz+SNBoTqEDqte2ckDvG2SeFR+Edip10p80TFGLp5RucaYvkwJTyuwsA7xd78NKT         Q9ou6L1hgy\/MbKChnp2kxHOtYNOrrszY3JfQM="
    },
    {
      "Name": "MIME-Version",
      "Value": "1.0"
    },
    {
      "Name": "Message-ID",
      "Value": "<CAGXpo2WKfxHWZ5UFYCR3H_J9SNMG+5AXUovfEFL6DjWBJSyZaA@mail.gmail.com>"
    }
  ],
  "Attachments": [
    {
      "Name": "myimage.png",
      "Content": "[BASE64-ENCODED CONTENT]",
      "ContentType": "image/png",
      "ContentLength": 4096,
      "ContentID": "myimage.png@01CE7342.75E71F80"
    },
    {
      "Name": "mypaper.doc",
      "Content": "[BASE64-ENCODED CONTENT]",
      "ContentType": "application/msword",
      "ContentLength": 16384,
      "ContentID": ""
    }
  ]
}

With the JSON data above, you can easily process the email data into your application, including all included email headers, content, and attachments. It will be up to you to choose how you process the data, since it depends heavily on the use case, programming language, and environment.

Code Examples

To help you get started, we’ve collected some community developed examples and “mitts”, or code designed to “catch” Postmark Inbound webhook JSON.

Rails Gem

Ruby Gem

PHP

Python

Node.js

C# / MVC

We are actively looking for your contributions for other languages. If you write a code sample or library for Postmark Inbound, please email us at support@postmarkapp.com. Contributions are appreciated and rewarded with free Postmark credits. If you have questions, problems, or ideas for how to improve this API, we have a low-traffic API developer email list that you can join as well.

Helpful Custom Information

We add some extra custom data to emails that are processed which can be useful as you process messages. Here is a list and description of each piece of information:

New!StrippedTextReply

When a user replies to a message, and your Inbound address is set as the To recipient, Postmark will attempt to parse the text portion of the reply and display it in the StrippedTextReply field. This is to make parsing reply messages simpler for users. Reply parsing will also work when the Inbound address is in the Cc contact and the message was replied-to-all.

Note: The StrippedTextReply field is limited to English text replies and is currently tested on the following email platforms: Yahoo, iCloud, Gmail, Outlook.com, iOS Mail, Apple Mail, Microsoft Outlook (Windows & Mac), and Mozilla Thunderbird. Postmark makes a 'best attempt' to parse all inbound replies.

MailboxHash

If your users send email to an email address that includes a custom hash after the mailbox name (user+hash@yourdomain.com) Postmark will split that hash out into it’s own field. You can see the example above as "MailboxHash": "ahoy". This is useful for linking replies to outbound messages and other tasks where you want to tie the inbound message to a specific item in your application.

New! Contact with Name

Available fields: FromFull, ToFull and CcFull

These “full” fields contain the contact info of the sender, recipient and any Cc recipients. The general format of these fields is a JSON object with following structure:

{
  "Email": "myUser@theirDomain.com",
  "Name": "Full name",
  "MailboxHash": ""
}

The “Full” variation of the contact info contains both the email of the person, as well as their full name and MailboxHash (if they specified it, it is optional). These three pieces of data are in separate fields, so no need to parse anything further. If message has multiple To recipients, then ToFull field will be JSON array containing several JSON documents of previously stated contents. Same goes for CcFull field.

Legacy Contact

Note: if you are implementing Postmark Inbound, please use the "Full" contact variations as we will deprecate these fields in the future in favor of the Full variation above.

Available fields: From, To and Cc are legacy counter-parts for FromFull, ToFull and CcFull fields. They are ordinary strings in format:

\"Full name\" <myUser@theirDomain.com>

If multiple people are present in To or Cc list, they are separated with comma.

Attachments

Postmark will happily support attachments from your senders and include them in the JSON object as a tuple of filename, a mime content type, and the base64 encoded data.

For example, if a sender attaches an image, cat.jpeg, to their email, the following JSON structure is generated:

{
  "Attachments":[
    {
       "Content":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAwADADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDg7nQ9XlmtLWPTbku9wg5jIA5zknsPevbvBXhjTdPRXvJUubleW5+RT7D+prlRq32WFpWb5+w9qzrTxFMrqyySlyMhR1J/Dmvl62aNyUGtArNqXLE9+tNSs4IFRpB6cdfpWX4slsdQszGzblUhlZTgow6Mp9Qa8lXxFqEeQ0bRrkACVtgyfYkUsmu3sbhneHDHgeepz9eaueLpyp8ljD2VTojSuLhNQeWz1FwL+3+5Oox5qdmI/nWDdu0bPC55U4NUp9Tkn1aGSMoZERg21wfl69vxrT0fTpPEV61skyx3WwtFu4D46g+9eLWwbrNKC978z0sFiJUXyVNvyOR1q4un1E2sTFV3Dn2xzVPxN4qbwwZoI7Ca5iwpMm8ouCDnBHNb99FG80h2jcQPyq9p+lQanb7poEkOMHcM9AAQfyruwc4OpaSuPl/eNI47Rtf0DxKkV4iNFcQuu+OX1/vA/wAQ7ZFHjrxNc6fbQWfh+wM9wSz7kiL+SvQHb6nP6VoeJfDjxTi80i1U/ZQdwGApXHIAHXFS+EdNuJ7U6vfwqUuiDHsJ3bBwMg/nXalBS5re72OhwbjYzfAEfiImK91i6maWSVd8WFVQp4xwOeDzXXeE79tO8W2xHL293sI/2Twf0JrVuLa1t7FZFUgLgjjvmuZmRk8bXAU9HLfjsJrGtUftIzRxV4bF+ewvjI0hsZyFPeM4xT7HUlsLS6OGKqpfjqB3rqvFOszaZZkQ8tIhB78e3vXl15qpgnWGGPDj5pFJyCT2zWMMHKmlNanRUfNLmOl0nVbTW7XYsvkRHtnDGpgINKgCQXO4AdCcjFc5p9iNzTonlbuVQnpV9bIRQt50oZic5zx+FdShJo1UtCZtTfUNXsrN+I3cMwHcDn+lJApk8QXt86OyKG5UZxngH8s0y3gjF0bu3kTzQhVQTwuRjNa/hieDS4/MEglcH98c/eHfHtRTpKVVKWyOetG7uj//2Q==",
       "ContentLength":1357,
       "ContentType":"image/jpeg",
       "Name":"cat.jpeg",
       "ContentID": ""
    }
  ],
  "Cc":"",
  "Dump":null,
  "From":"\"Some Cat Fan\" <user@internet>",
  "Subject":"Hello World!",
  "To":"76fa022d4a33c45ddf5be8c24c47f4da@inbound.postmarkapp.com"
}

You will then need to decode the content to generate the original attachment file. The following is a python snippet for doing so:

from base64 import b64decode

for attachment in inbound_json["attachment"]:
    blob = b64decode(attachment["Content"])
    file = open(attachment["Name"],"w")
    file.write(blob)
    file.close()

Total cumulative size for all Inbound attachment files may not exceed 25 megabytes.

SpamAssassin Headers

By default, Postmark will accept any emails that are sent to your inbound address, including spam. If you’re using Gmail forwarding, their filters will take care of much of this for you. To make it easier to find and filter spam that is sent to your app, we scan all inbound emails using SpamAssassin, and add a few unique headers to the header content. For example:

  • X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on rs-mail1 – Know the version of SpamAssassin that checked your inbound email.
  • X-Spam-Status: No – Either Yes or No depending on how SpamAssassin considers the message.
  • X-Spam-Score -0.8 – The spam score of the message, ranging from a negative (better) to positive (worse) number. The default consideration of spam is a score above 5.
  • X-Spam-Tests: DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,RCVD_IN_DNSWL_LOW – The series of tests that were triggered when processing the message. A full list of the test can be found at SpamAssassin Tests page.

Note: While SpamAssassin is a powerful and effective tool, it is possible that legitimate messages could be marked with a higher score. We recommend thorough testing before filtering spam messages into your app.

SPF Headers

In addition to spam, it is also helpful to know if the identity of the sender is legitimate. SPF goes a long way for this. We check the from address of the email and check the SPF record, then add a header to the email with a result. It looks something like this:

Received-SPF: Pass (sender SPF authorized) identity=mailfrom; client-ip=209.85.160.42; helo=mail-pw0-f42.google.com; envelope-from=support@postmarkapp.com; receiver=451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com

There are three possible states:

  • Received-SPF: neutral – A record does not exists and is neither permitted nor denied.
  • Received-SPF: pass – a record exists and the IP is approved for sending email.
  • Received-SPF: fail – a record exists and the IP is not approved for sending email.

Date Header

You can easily fetch the exact time when the message was sent by checking out Date field. This will show the time in sender’s local timezone, so you have that info available too. This field contains the Date header fetched from original email, so the exact format of this field matches Date/time specification in RFC 2822