├── VERSION ├── lib ├── postmark-mitt.rb ├── postmark_mitt.rb └── postmark │ └── mitt.rb ├── .gitignore ├── Gemfile ├── spec ├── spec_helper.rb ├── postmark │ └── mitt_spec.rb └── fixtures │ └── email.json ├── LICENSE ├── Rakefile ├── postmark-mitt.gemspec └── README.rdoc /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.11 -------------------------------------------------------------------------------- /lib/postmark-mitt.rb: -------------------------------------------------------------------------------- 1 | require 'postmark_mitt' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | -------------------------------------------------------------------------------- /lib/postmark_mitt.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.dirname(__FILE__) 2 | require 'multi_json' 3 | require 'base64' 4 | require 'tempfile' 5 | require 'postmark/mitt' 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "json_pure" 4 | gem "multi_json" 5 | 6 | group :development do 7 | gem "rspec", "~> 2.9.0" 8 | gem "bundler", "~> 1.0" 9 | gem "jeweler", "~> 1.8.3" 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'rspec' 4 | require 'postmark_mitt' 5 | 6 | # Requires supporting files with custom matchers and macros, etc, 7 | # in ./support/ and its subdirectories. 8 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 9 | 10 | SPEC_ROOT = Pathname.new(File.dirname(__FILE__)) 11 | 12 | def read_fixture(file='email.json') 13 | File.read(File.join(SPEC_ROOT, 'fixtures', file)) 14 | end 15 | 16 | RSpec.configure do |config| 17 | 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Randy Schmidt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | begin 4 | Bundler.setup(:default, :development) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | require 'rake' 11 | 12 | require 'jeweler' 13 | Jeweler::Tasks.new do |gem| 14 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 15 | gem.name = "postmark-mitt" 16 | gem.homepage = "http://github.com/r38y/postmark-mitt" 17 | gem.license = "MIT" 18 | gem.summary = %q{Mitt for incoming email through Postmark} 19 | gem.description = %q{This gem will help you take JSON posted to your app from incoming email through Postmark. It will turn it back into an object with methods to help inspect the contents of the email} 20 | gem.email = "randy@forge38.com" 21 | gem.authors = ["Randy Schmidt"] 22 | end 23 | Jeweler::RubygemsDotOrgTasks.new 24 | 25 | require 'rspec/core' 26 | require 'rspec/core/rake_task' 27 | RSpec::Core::RakeTask.new(:spec) do |spec| 28 | spec.pattern = FileList['spec/**/*_spec.rb'] 29 | end 30 | 31 | RSpec::Core::RakeTask.new(:rcov) do |spec| 32 | spec.pattern = 'spec/**/*_spec.rb' 33 | spec.rcov = true 34 | end 35 | 36 | task :default => :spec 37 | 38 | require 'rdoc/task' 39 | Rake::RDocTask.new do |rdoc| 40 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 41 | 42 | rdoc.rdoc_dir = 'rdoc' 43 | rdoc.title = "postmark-mitt #{version}" 44 | rdoc.rdoc_files.include('README*') 45 | rdoc.rdoc_files.include('lib/**/*.rb') 46 | end 47 | -------------------------------------------------------------------------------- /postmark-mitt.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "postmark-mitt" 8 | s.version = "0.0.10" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Randy Schmidt"] 12 | s.date = "2012-05-22" 13 | s.description = "This gem will help you take JSON posted to your app from incoming email through Postmark. It will turn it back into an object with methods to help inspect the contents of the email" 14 | s.email = "randy@forge38.com" 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.rdoc" 18 | ] 19 | s.files = [ 20 | "Gemfile", 21 | "LICENSE", 22 | "README.rdoc", 23 | "Rakefile", 24 | "VERSION", 25 | "lib/postmark-mitt.rb", 26 | "lib/postmark/mitt.rb", 27 | "lib/postmark_mitt.rb", 28 | "postmark-mitt.gemspec", 29 | "spec/fixtures/email.json", 30 | "spec/postmark/mitt_spec.rb", 31 | "spec/spec_helper.rb" 32 | ] 33 | s.homepage = "http://github.com/r38y/postmark-mitt" 34 | s.licenses = ["MIT"] 35 | s.require_paths = ["lib"] 36 | s.rubygems_version = "1.8.22" 37 | s.summary = "Mitt for incoming email through Postmark" 38 | 39 | if s.respond_to? :specification_version then 40 | s.specification_version = 3 41 | 42 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 43 | s.add_runtime_dependency(%q, [">= 0"]) 44 | s.add_runtime_dependency(%q, [">= 0"]) 45 | s.add_development_dependency(%q, ["~> 2.9.0"]) 46 | s.add_development_dependency(%q, ["~> 1.0"]) 47 | s.add_development_dependency(%q, ["~> 1.8.3"]) 48 | else 49 | s.add_dependency(%q, [">= 0"]) 50 | s.add_dependency(%q, [">= 0"]) 51 | s.add_dependency(%q, ["~> 2.9.0"]) 52 | s.add_dependency(%q, ["~> 1.0"]) 53 | s.add_dependency(%q, ["~> 1.8.3"]) 54 | end 55 | else 56 | s.add_dependency(%q, [">= 0"]) 57 | s.add_dependency(%q, [">= 0"]) 58 | s.add_dependency(%q, ["~> 2.9.0"]) 59 | s.add_dependency(%q, ["~> 1.0"]) 60 | s.add_dependency(%q, ["~> 1.8.3"]) 61 | end 62 | end 63 | 64 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Postmark Mitt 2 | 3 | WARNING: ABANDONWARE-ish USE WITH CAUTION 4 | 5 | This gem provides a little wrapper object for taking JSON from Postmark's incoming email system and composing it back into an object resembling an email. This comes in handy when you are trying to figure out what to do with the email. 6 | 7 | Postmark-mitt has been verified to work on Ruby Enterprise Edition 1.8.7, 1.9.2 and 1.9.3. 8 | 9 | == Install 10 | 11 | gem install postmark-mitt 12 | 13 | == Example 14 | 15 | require 'lib/postmark_mitt' 16 | 17 | # if you are just playing with this library 18 | email = Postmark::Mitt.new(File.read("path/to/json/file/in/spec/fixtures")) 19 | 20 | # if you are doing this in a controller 21 | email = Postmark::Mitt.new(request.body.read) 22 | 23 | email.to # "api-hash@inbound.postmarkapp.com" 24 | email.from # Bob Bobson 25 | email.from_email # bob@bob.com 26 | email.text_body 27 | email.html_body 28 | email.headers # returns a hash of the headers 29 | 30 | email.attachments # array of attachment objects 31 | 32 | attachment = email.attachments.first 33 | attachment.file_name 34 | attachment.content_type 35 | attachment.read # will base64 decode the content... eventually this will lazy-load the file from Postmark 36 | attachment.size # NYI 37 | # You get the idea 38 | 39 | A lot of times you want to do something with the email, like create a post 40 | 41 | mitt = Postmark::Mitt.new(request.body.read) 42 | Post.create_from_postmark(mitt) 43 | 44 | class Post 45 | def self.create_from_postmark(mitt) 46 | author = User.find_by_api_email(mitt.to) 47 | handle_no_author # send an email back saying we couldn't find them 48 | post = new 49 | post.title = mitt.subject 50 | post.author = author 51 | post.photo = mitt.attachments.first.read 52 | post.message_id = mitt.message_id # Make sure we don't process the same email twice 53 | # You get the idea, right? 54 | post.save 55 | post 56 | end 57 | end 58 | 59 | == Note on Patches/Pull Requests 60 | 61 | * Fork the project. 62 | * Make your feature addition or bug fix. 63 | * Add tests for it. This is important so I don't break it in a 64 | future version unintentionally. 65 | * Commit, do not mess with rakefile, version, or history. 66 | * Send me a pull request. Bonus points for topic branches. 67 | 68 | == Authors & Contributors 69 | 70 | * Randy Schmidt 71 | 72 | == Copyright 73 | 74 | Copyright (c) 2011 Randy Schmidt. See LICENSE for details. 75 | 76 | -------------------------------------------------------------------------------- /lib/postmark/mitt.rb: -------------------------------------------------------------------------------- 1 | module Postmark 2 | class Mitt 3 | def initialize(json) 4 | @raw = json 5 | @source = MultiJson.decode(json) 6 | end 7 | 8 | attr_reader :raw, :source 9 | 10 | def inspect 11 | "" 12 | end 13 | 14 | def subject 15 | source["Subject"] 16 | end 17 | 18 | def from 19 | source["From"].gsub('"', '') 20 | end 21 | 22 | def from_email 23 | source["FromFull"]["Email"] || from 24 | end 25 | 26 | def from_name 27 | source["FromFull"]["Name"] || from 28 | end 29 | 30 | def to 31 | source["To"].gsub('"', '') 32 | end 33 | 34 | def to_full 35 | source["ToFull"] || [] 36 | end 37 | 38 | def to_email 39 | to_full.any? ? to_full.first["Email"] : to 40 | end 41 | 42 | def to_name 43 | to_full.any? ? to_full.first["Name"] : to 44 | end 45 | 46 | 47 | def bcc 48 | source["Bcc"] 49 | end 50 | 51 | def bcc_email 52 | source["BccFull"]["Email"] || bcc 53 | end 54 | 55 | def bcc_name 56 | source["BccFull"]["Name"] || bcc 57 | end 58 | 59 | def cc 60 | source["Cc"] 61 | end 62 | 63 | def cc_email 64 | source["CcFull"]["Email"] || cc 65 | end 66 | 67 | def cc_name 68 | source["CcFull"]["Name"] || cc 69 | end 70 | 71 | def reply_to 72 | source["ReplyTo"] 73 | end 74 | 75 | def html_body 76 | source["HtmlBody"] 77 | end 78 | 79 | def text_body 80 | source["TextBody"] 81 | end 82 | 83 | def stripped_text_reply 84 | source["StrippedTextReply"] 85 | end 86 | 87 | def mailbox_hash 88 | source["MailboxHash"] 89 | end 90 | 91 | def tag 92 | source["Tag"] 93 | end 94 | 95 | def headers 96 | @headers ||= Array(source["Headers"]).inject({}){|hash,obj| 97 | hash[obj["Name"]] = obj["Value"] 98 | hash 99 | } 100 | end 101 | 102 | def message_id 103 | source["MessageID"] 104 | end 105 | 106 | def attachments 107 | @attachments ||= begin 108 | raw_attachments = source["Attachments"] || [] 109 | AttachmentsArray.new(raw_attachments.map{|a| Attachment.new(a)}) 110 | end 111 | end 112 | 113 | def has_attachments? 114 | !attachments.empty? 115 | end 116 | 117 | class Attachment 118 | def initialize(attachment_source) 119 | @source = attachment_source 120 | end 121 | attr_accessor :source 122 | 123 | def content_type 124 | source["ContentType"] 125 | end 126 | 127 | def file_name 128 | source["Name"] 129 | end 130 | 131 | def read 132 | tempfile = MittTempfile.new(file_name, content_type) 133 | if size > 0 134 | tempfile.write(Base64.decode64(source["Content"])) 135 | end 136 | tempfile 137 | end 138 | 139 | def size 140 | source["ContentLength"].to_i 141 | end 142 | end 143 | 144 | class AttachmentsArray < Array 145 | def sorted 146 | @sorted ||= self.sort{|x,y|x.size <=> y.size} 147 | end 148 | 149 | def largest 150 | sorted.last 151 | end 152 | 153 | def smallest 154 | sorted.first 155 | end 156 | end 157 | end 158 | 159 | class MittTempfile < Tempfile 160 | def initialize(basename, content_type, tmpdir=Dir::tmpdir) 161 | basename = basename.gsub(/[\/\\]/, "-") 162 | if Postmark.ruby19? 163 | super(basename, tmpdir, :encoding => 'ascii-8bit') 164 | else 165 | super(basename, tmpdir) 166 | end 167 | @basename = basename 168 | @content_type = content_type 169 | end 170 | 171 | # the content type of the "uploaded" file 172 | attr_accessor :content_type 173 | 174 | def original_filename 175 | @basename || file.basename(path) 176 | end 177 | end 178 | 179 | def self.ruby19? 180 | RUBY_VERSION > "1.9" 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /spec/postmark/mitt_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/..' + '/spec_helper') 2 | 3 | describe Postmark::Mitt do 4 | let(:mitt) do 5 | Postmark::Mitt.new(read_fixture) 6 | end 7 | 8 | it "should have a subject" do 9 | mitt.subject.should == "Hi There" 10 | end 11 | 12 | it "should have a html_body" do 13 | mitt.html_body.should == "

We no speak americano

" 14 | end 15 | 16 | it "should have a text_body" do 17 | mitt.text_body.should == "\nThis is awesome!\n\n" 18 | end 19 | 20 | it "should have a stripped_text_reply" do 21 | mitt.stripped_text_reply.should == "This is awesome!" 22 | end 23 | 24 | it "should be to someone" do 25 | mitt.to.should == "Api Hash " 26 | end 27 | 28 | it "should pull out the to_name" do 29 | mitt.to_name.should == "Api Hash" 30 | end 31 | 32 | it "should pull out the to_email" do 33 | mitt.to_email.should == "api-hash@inbound.postmarkapp.com" 34 | end 35 | 36 | it "should be from someone" do 37 | mitt.from.should == "Bob Bobson " 38 | end 39 | 40 | it "should pull out the from_email" do 41 | mitt.from_email.should == "bob@bob.com" 42 | end 43 | 44 | it "should pull out the from_name" do 45 | mitt.from_name.should == "Bob Bobson" 46 | end 47 | 48 | it "should have a bcc" do 49 | mitt.bcc.should == "FBI " 50 | end 51 | 52 | it "should pull out the bcc_email" do 53 | mitt.bcc_email.should == "hi@fbi.com" 54 | end 55 | 56 | it "should pull out the bcc_name" do 57 | mitt.bcc_name.should == "FBI" 58 | end 59 | 60 | it "should have a cc" do 61 | mitt.cc.should == "Your Mom " 62 | end 63 | 64 | it "should pull out the cc_email" do 65 | mitt.cc_email.should == "hithere@hotmail.com" 66 | end 67 | 68 | it "should pull out the cc_name" do 69 | mitt.cc_name.should == "Your Mom" 70 | end 71 | 72 | it "should have a reply_to" do 73 | mitt.reply_to.should == "new-comment+sometoken@yeah.com" 74 | end 75 | 76 | it "should have a mailbox_hash" do 77 | mitt.mailbox_hash.should == 'moitoken' 78 | end 79 | 80 | it "should have a tag" do 81 | mitt.tag.should == 'yourit' 82 | end 83 | 84 | it "should have a message_id" do 85 | mitt.message_id.should == "a8c1040e-db1c-4e18-ac79-bc5f64c7ce2c" 86 | end 87 | 88 | it "should have headers" do 89 | mitt.headers["Date"].should =="Thu, 31 Mar 2011 12:01:17 -0400" 90 | end 91 | 92 | it "handles null headers" do 93 | mitt.stub(:source).and_return({"Headers" => nil}) 94 | mitt.headers.should == {} 95 | end 96 | 97 | it "should have an attachment" do 98 | mitt.attachments.size.should == 2 99 | end 100 | 101 | it "should have attachment objects" do 102 | mitt.attachments.first.class.name.should == 'Postmark::Mitt::Attachment' 103 | end 104 | 105 | it "should know if it has attachments" do 106 | mitt.attachments.should_not be_empty 107 | mitt.should have_attachments 108 | mitt.stub!(:attachments).and_return([]) 109 | mitt.should_not have_attachments 110 | end 111 | 112 | describe "#attachments.largest" do 113 | it "should return the attachment with the largest content length" do 114 | largest = mitt.attachments.largest 115 | largest.file_name.should == "chart.png" 116 | largest.size.should == 2000 117 | end 118 | end 119 | 120 | describe "#attachments.smallest" do 121 | it "should return the attachment with the smallest content length" do 122 | smallest = mitt.attachments.smallest 123 | smallest.file_name.should == "chart2.png" 124 | smallest.size.should == 1000 125 | end 126 | end 127 | 128 | describe Postmark::Mitt::Attachment do 129 | let(:attachment) do 130 | mitt.attachments.first 131 | end 132 | 133 | it "should have a content_type" do 134 | attachment.content_type.should == 'image/png' 135 | end 136 | 137 | it "should have a file_name" do 138 | attachment.file_name.should == 'chart.png' 139 | end 140 | 141 | describe "read" do 142 | it "should read the content" do 143 | attachment.read.class.name.should == "Postmark::MittTempfile" 144 | end 145 | 146 | it "should have a content_type" do 147 | attachment.read.content_type.should == 'image/png' 148 | end 149 | 150 | it "should have a original_filename" do 151 | attachment.read.original_filename.should == 'chart.png' 152 | end 153 | 154 | it "should not blow up with a zero content length and no source" do 155 | attachment = ::Postmark::Mitt::Attachment.new({"Name"=>"logo.gif", 156 | "ContentType"=>"image/gif", 157 | "ContentID"=>"logo.gif", 158 | "ContentLength"=>0}) 159 | expect { 160 | attachment.read.read 161 | }.to_not raise_error 162 | end 163 | 164 | it "should not blow up with a nil content length" do 165 | attachment = ::Postmark::Mitt::Attachment.new({"Name"=>"logo.gif", 166 | "ContentType"=>"image/gif", 167 | "ContentID"=>"logo.gif", 168 | "ContentLength"=>nil}) 169 | expect { 170 | attachment.read.read 171 | }.to_not raise_error 172 | end 173 | end 174 | 175 | it "should have a size" do 176 | attachment.size.should == 2000 177 | end 178 | end 179 | 180 | describe ::Postmark::MittTempfile do 181 | it "should not explode with path chars in the name" do 182 | expect { 183 | ::Postmark::MittTempfile.new("file/with/../path", "text/csv") 184 | }.to_not raise_error 185 | end 186 | 187 | it "should escape path chars" do 188 | instance = ::Postmark::MittTempfile.new("file/with/../path", "text/csv") 189 | instance.path.should include("file-with-..-path") 190 | end 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /spec/fixtures/email.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attachments": [ 3 | { 4 | "Content": "iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAABmJLR0QA\/wD\/AP+gvaeTAAAHFklEQVR4nO3dUWojSRRFwdHQ+9+yegE99BQ4eXonFfFt7HJJHPLnkq\/3+\/0PQMG\/n34AgKcEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjJ+ffoB\/tvr9fr0I3zM+\/3+35958n62\/Z5TnjzPKb6H2zhhARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkLF0S\/jEzq3T353apk3u+2595lPfn2\/+Hs5zwgIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCAjvCV8wn15P3fq\/yregXhK8fPayQkLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CAjMu3hMzYdgcit3LCAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIMOW8FqT9wmecupewrvv5vtmTlhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkXL4lvHVTtm0neGoDOPm3Jr8bt34P5zlhARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkBHeEk7el7fN5FZucru3bQP4xDd\/D+c5YQEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpCxdEu4bS+2za3vZ9ve8Nb33OWEBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQMZr51pq211vp97Stvv7bn3PT0xuEiff87Y95llOWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWRcfi\/htr3Yrfu+bc88+TzbdnnbnucsJywgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIylm4Jt23TTtm2bZz8Pczo7gSfcMICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+mWcNLkvq+489r2zMU95uTf2vZ5neWEBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQEZ4S\/hkM3Vqd7bt92xTfD+T273J93P3JtEJC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgIylW8Jtu7NTJp9n2+bulG2f++T7ufUzfc4JC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgIylW8Jb3Xpn3DdvP2+9S3EnJywgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyXjtXRaf2Wbdur259P9vu+Nu2Eyz+rbOcsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8hYuiWcNLlfe2LbLm9yc7dtI\/nNdpbBCQvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsICMX59+gM\/buZn6u233CRbf4eQzb7sHsLu1dMICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+mWsLt1+rknm7Li3XzbnueJyQ1g8f3Mc8ICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+mW8IniXXjFvdg336k3+X\/d+g7PcsICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgI7wlfOLW3dmkbf\/XqTsZu3u6n9v2mT7nhAVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWEDG5VvCW227K\/CJ4h1\/2\/aGtrFOWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWTYEl7r1O5sclO2bbs3yU7wCScsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMi7fEnY3U3ts2\/dtu3PwyfOcukeyeB\/lWU5YQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZLx2Lo+27dcmndqUTf6tnd+iGbd+V3d+pk5YQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZCzdEgL8yQkLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyPgN6\/l0MFbvnZQAAAAASUVORK5CYII=", 5 | "ContentType": "image\/png", 6 | "Name": "chart.png", 7 | "ContentLength": 2000 8 | }, 9 | { 10 | "Content": "iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAABmJLR0QA\/wD\/AP+gvaeTAAAHFklEQVR4nO3dUWojSRRFwdHQ+9+yegE99BQ4eXonFfFt7HJJHPLnkq\/3+\/0PQMG\/n34AgKcEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjJ+ffoB\/tvr9fr0I3zM+\/3+35958n62\/Z5TnjzPKb6H2zhhARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkLF0S\/jEzq3T353apk3u+2595lPfn2\/+Hs5zwgIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCAjvCV8wn15P3fq\/yregXhK8fPayQkLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CAjMu3hMzYdgcit3LCAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIMOW8FqT9wmecupewrvv5vtmTlhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkXL4lvHVTtm0neGoDOPm3Jr8bt34P5zlhARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkBHeEk7el7fN5FZucru3bQP4xDd\/D+c5YQEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpCxdEu4bS+2za3vZ9ve8Nb33OWEBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQMZr51pq211vp97Stvv7bn3PT0xuEiff87Y95llOWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWRcfi\/htr3Yrfu+bc88+TzbdnnbnucsJywgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIylm4Jt23TTtm2bZz8Pczo7gSfcMICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+mWcNLkvq+489r2zMU95uTf2vZ5neWEBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQEZ4S\/hkM3Vqd7bt92xTfD+T273J93P3JtEJC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgIylW8Jtu7NTJp9n2+bulG2f++T7ufUzfc4JC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgIylW8Jb3Xpn3DdvP2+9S3EnJywgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyXjtXRaf2Wbdur259P9vu+Nu2Eyz+rbOcsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8hYuiWcNLlfe2LbLm9yc7dtI\/nNdpbBCQvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsICMX59+gM\/buZn6u233CRbf4eQzb7sHsLu1dMICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+mWsLt1+rknm7Li3XzbnueJyQ1g8f3Mc8ICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+mW8IniXXjFvdg336k3+X\/d+g7PcsICMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgI7wlfOLW3dmkbf\/XqTsZu3u6n9v2mT7nhAVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWEDG5VvCW227K\/CJ4h1\/2\/aGtrFOWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWTYEl7r1O5sclO2bbs3yU7wCScsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMi7fEnY3U3ts2\/dtu3PwyfOcukeyeB\/lWU5YQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZLx2Lo+27dcmndqUTf6tnd+iGbd+V3d+pk5YQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZCzdEgL8yQkLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyPgN6\/l0MFbvnZQAAAAASUVORK5CYII=", 11 | "ContentType": "image\/png", 12 | "Name": "chart2.png", 13 | "ContentLength": 1000 14 | } 15 | ], 16 | "Bcc": "FBI ", 17 | "BccFull": { 18 | "Email": "hi@fbi.com", 19 | "Name": "FBI" 20 | }, 21 | "Cc": "Your Mom ", 22 | "CcFull": { 23 | "Email": "hithere@hotmail.com", 24 | "Name": "Your Mom" 25 | }, 26 | "From": "\"Bob Bobson\" ", 27 | "FromFull": { 28 | "Email": "bob@bob.com", 29 | "Name": "Bob Bobson" 30 | }, 31 | "Headers": [ 32 | { 33 | "Name": "Received-SPF", 34 | "Value": "None (no SPF record) identity=mailfrom; client-ip=209.85.212.52; helo=mail-vw0-f52.google.com; envelope-from=bob@bob.com; receiver=4e8d6dec234dd90018e7bfd2b5d79107@inbound.postmarkapp.com" 35 | }, 36 | { 37 | "Name": "MIME-Version", 38 | "Value": "1.0" 39 | }, 40 | { 41 | "Name": "Date", 42 | "Value": "Thu, 31 Mar 2011 12:01:17 -0400" 43 | }, 44 | { 45 | "Name": "Message-ID", 46 | "Value": "" 47 | }, 48 | { 49 | "Name": "Subject", 50 | "Value": "Hi There" 51 | }, 52 | { 53 | "Name": "From", 54 | "Value": "Bob Bobson " 55 | }, 56 | { 57 | "Name": "To", 58 | "Value": "api-hash@inbound.postmarkapp.com" 59 | }, 60 | { 61 | "Name": "Content-Type", 62 | "Value": "multipart\/mixed" 63 | } 64 | ], 65 | "HtmlBody": "

We no speak americano

", 66 | "MailboxHash": "moitoken", 67 | "MessageID": "a8c1040e-db1c-4e18-ac79-bc5f64c7ce2c", 68 | "ReplyTo": "new-comment+sometoken@yeah.com", 69 | "Subject": "Hi There", 70 | "Tag": "yourit", 71 | "TextBody": "\nThis is awesome!\n\n", 72 | "StrippedTextReply": "This is awesome!", 73 | "To": "\"Api Hash\" ", 74 | "ToFull": [ 75 | { 76 | "Email": "api-hash@inbound.postmarkapp.com", 77 | "Name": "Api Hash" 78 | } 79 | ] 80 | } 81 | --------------------------------------------------------------------------------