postmark-1.22.0/0000755000175100017510000000000014123042137013613 5ustar vivekdebvivekdebpostmark-1.22.0/spec/0000755000175100017510000000000014123042137014545 5ustar vivekdebvivekdebpostmark-1.22.0/spec/unit/0000755000175100017510000000000014123042137015524 5ustar vivekdebvivekdebpostmark-1.22.0/spec/unit/postmark_spec.rb0000644000175100017510000001213514123042137020725 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark do let(:api_token) { double } let(:secure) { double } let(:proxy_host) { double } let(:proxy_port) { double } let(:proxy_user) { double } let(:proxy_pass) { double } let(:host) { double } let(:port) { double } let(:path_prefix) { double } let(:max_retries) { double } before do subject.api_token = api_token subject.secure = secure subject.proxy_host = proxy_host subject.proxy_port = proxy_port subject.proxy_user = proxy_user subject.proxy_pass = proxy_pass subject.host = host subject.port = port subject.path_prefix = path_prefix subject.max_retries = max_retries end context "attr readers" do it { expect(subject).to respond_to(:secure) } it { expect(subject).to respond_to(:api_key) } it { expect(subject).to respond_to(:api_token) } it { expect(subject).to respond_to(:proxy_host) } it { expect(subject).to respond_to(:proxy_port) } it { expect(subject).to respond_to(:proxy_user) } it { expect(subject).to respond_to(:proxy_pass) } it { expect(subject).to respond_to(:host) } it { expect(subject).to respond_to(:port) } it { expect(subject).to respond_to(:path_prefix) } it { expect(subject).to respond_to(:http_open_timeout) } it { expect(subject).to respond_to(:http_read_timeout) } it { expect(subject).to respond_to(:max_retries) } end context "attr writers" do it { expect(subject).to respond_to(:secure=) } it { expect(subject).to respond_to(:api_key=) } it { expect(subject).to respond_to(:api_token=) } it { expect(subject).to respond_to(:proxy_host=) } it { expect(subject).to respond_to(:proxy_port=) } it { expect(subject).to respond_to(:proxy_user=) } it { expect(subject).to respond_to(:proxy_pass=) } it { expect(subject).to respond_to(:host=) } it { expect(subject).to respond_to(:port=) } it { expect(subject).to respond_to(:path_prefix=) } it { expect(subject).to respond_to(:http_open_timeout=) } it { expect(subject).to respond_to(:http_read_timeout=) } it { expect(subject).to respond_to(:max_retries=) } it { expect(subject).to respond_to(:response_parser_class=) } it { expect(subject).to respond_to(:api_client=) } end describe ".response_parser_class" do after do subject.instance_variable_set(:@response_parser_class, nil) end it "returns :ActiveSupport when ActiveSupport::JSON is available" do expect(subject.response_parser_class).to eq :ActiveSupport end it "returns :Json when ActiveSupport::JSON is not available" do hide_const("ActiveSupport::JSON") expect(subject.response_parser_class).to eq :Json end end describe ".configure" do it 'yields itself to the block' do expect { |b| subject.configure(&b) }.to yield_with_args(subject) end end describe ".api_client" do let(:api_client) { double } context "when shared client instance already exists" do it 'returns the existing instance' do subject.instance_variable_set(:@api_client, api_client) expect(subject.api_client).to eq api_client end end context "when shared client instance does not exist" do it 'creates a new instance of Postmark::ApiClient' do allow(Postmark::ApiClient).to receive(:new). with(api_token, :secure => secure, :proxy_host => proxy_host, :proxy_port => proxy_port, :proxy_user => proxy_user, :proxy_pass => proxy_pass, :host => host, :port => port, :path_prefix => path_prefix, :max_retries => max_retries). and_return(api_client) expect(subject.api_client).to eq api_client end end end describe ".deliver_message" do let(:api_client) { double } let(:message) { double } before do subject.api_client = api_client end it 'delegates the method to the shared api client instance' do allow(api_client).to receive(:deliver_message).with(message) subject.deliver_message(message) end it 'is also accessible as .send_through_postmark' do allow(api_client).to receive(:deliver_message).with(message) subject.send_through_postmark(message) end end describe ".deliver_messages" do let(:api_client) { double } let(:message) { double } before do subject.api_client = api_client end it 'delegates the method to the shared api client instance' do allow(api_client).to receive(:deliver_messages).with(message) subject.deliver_messages(message) end end describe ".delivery_stats" do let(:api_client) { double } before do subject.api_client = api_client end it 'delegates the method to the shared api client instance' do allow(api_client).to receive(:delivery_stats) subject.delivery_stats end end endpostmark-1.22.0/spec/unit/postmark/0000755000175100017510000000000014123042137017364 5ustar vivekdebvivekdebpostmark-1.22.0/spec/unit/postmark/message_extensions/0000755000175100017510000000000014123042137023267 5ustar vivekdebvivekdebpostmark-1.22.0/spec/unit/postmark/message_extensions/mail_spec.rb0000644000175100017510000002603114123042137025552 0ustar vivekdebvivekdebrequire 'spec_helper' describe Mail::Message do before do allow(Kernel).to receive(:warn) end let(:mail_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" body "Hello Sheldon!" end end let(:mail_html_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" content_type 'text/html; charset=UTF-8' body "Hello Sheldon!" end end let(:templated_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" template_alias "Hello!" template_model :name => "Sheldon" end end describe '#tag' do it 'value set on tag=' do mail_message.tag='value' expect(mail_message.tag).to eq 'value' end it 'value set on tag()' do mail_message.tag('value') expect(mail_message.tag).to eq 'value' end end describe '#track_opens' do it 'returns nil if unset' do expect(mail_message.track_opens).to eq '' end context 'when assigned via #track_opens=' do it 'returns assigned value to track opens' do mail_message.track_opens = true expect(mail_message.track_opens).to eq 'true' end it 'returns assigned value to not track opens' do mail_message.track_opens = false expect(mail_message.track_opens).to eq 'false' end end context 'flag set on track_opens()' do it 'true' do mail_message.track_opens(true) expect(mail_message.track_opens).to eq 'true' end it 'false' do mail_message.track_opens(false) expect(mail_message.track_opens).to eq 'false' end end end describe '#metadata' do let(:metadata) { { :test => 'test' } } it 'returns a mutable empty hash if unset' do expect(mail_message.metadata).to eq({}) expect(mail_message.metadata.equal?(mail_message.metadata)).to be true end it 'supports assigning non-null values (for the builder DSL)' do expect { mail_message.metadata(metadata) }.to change { mail_message.metadata }.to(metadata) expect { mail_message.metadata(nil) }.to_not change { mail_message.metadata } end it 'returns value assigned via metadata=' do expect { mail_message.metadata = metadata }.to change { mail_message.metadata }.to(metadata) end end describe '#track_links' do it 'return empty string when if unset' do expect(mail_message.track_links).to eq '' end context 'when assigned via #track_links=' do it 'returns track html only body value in Postmark format' do mail_message.track_links=:html_only expect(mail_message.track_links).to eq 'HtmlOnly' end end context 'when assigned via track_links()' do it 'returns track html only body value in Postmark format' do mail_message.track_links(:html_only) expect(mail_message.track_links).to eq 'HtmlOnly' end end end describe "#html?" do it 'is true for html only email' do expect(mail_html_message).to be_html end end describe "#body_html" do it 'returns html body if present' do expect(mail_html_message.body_html).to eq "Hello Sheldon!" end end describe "#body_text" do it 'returns text body if present' do expect(mail_message.body_text).to eq "Hello Sheldon!" end end describe "#postmark_attachments=" do let(:attached_hash) { {'Name' => 'picture.jpeg', 'ContentType' => 'image/jpeg'} } it "stores attachments as an array" do mail_message.postmark_attachments = attached_hash expect(mail_message.instance_variable_get(:@_attachments)).to include(attached_hash) end it "is deprecated" do expect(Kernel).to receive(:warn).with(/deprecated/) mail_message.postmark_attachments = attached_hash end end describe "#postmark_attachments" do let(:attached_file) { double("file") } let(:attached_hash) { {'Name' => 'picture.jpeg', 'ContentType' => 'image/jpeg'} } let(:exported_file) { {'Name' => 'file.jpeg', 'ContentType' => 'application/octet-stream', 'Content' => ''} } before do allow(attached_file).to receive(:is_a?) { |arg| arg == File ? true : false } allow(attached_file).to receive(:path) { '/tmp/file.jpeg' } end it "supports multiple attachment formats" do expect(IO).to receive(:read).with("/tmp/file.jpeg").and_return("") mail_message.postmark_attachments = [attached_hash, attached_file] attachments = mail_message.export_attachments expect(attachments).to include(attached_hash) expect(attachments).to include(exported_file) end it "is deprecated" do mail_message.postmark_attachments = attached_hash expect(Kernel).to receive(:warn).with(/deprecated/) mail_message.postmark_attachments end end describe "#export_attachments" do let(:file_data) { 'binarydatahere' } let(:exported_data) { {'Name' => 'face.jpeg', 'Content' => "YmluYXJ5ZGF0YWhlcmU=\n", 'ContentType' => 'image/jpeg'} } context 'given a regular attachment' do it "exports native attachments" do mail_message.attachments["face.jpeg"] = file_data expect(mail_message.export_attachments).to include(exported_data) end it "still supports the deprecated attachments API" do mail_message.attachments["face.jpeg"] = file_data mail_message.postmark_attachments = exported_data expect(mail_message.export_attachments).to eq [exported_data, exported_data] end end context 'given an inline attachment' do it "exports the attachment with related content id" do mail_message.attachments.inline["face.jpeg"] = file_data attachments = mail_message.export_attachments expect(attachments.count).to_not be_zero expect(attachments.first).to include(exported_data) expect(attachments.first).to have_key('ContentID') expect(attachments.first['ContentID']).to start_with('cid:') end end end describe "#export_headers" do let(:mail_message_with_reserved_headers) do mail_message.header['Return-Path'] = 'bounce@wildbit.com' mail_message.header['From'] = 'info@wildbit.com' mail_message.header['Sender'] = 'info@wildbit.com' mail_message.header['Received'] = 'from mta.pstmrk.it ([72.14.252.155]:54907)' mail_message.header['Date'] = 'January 25, 2013 3:30:58 PM PDT' mail_message.header['Content-Type'] = 'application/json' mail_message.header['To'] = 'lenard@bigbangtheory.com' mail_message.header['Cc'] = 'sheldon@bigbangtheory.com' mail_message.header['Bcc'] = 'penny@bigbangtheory.com' mail_message.header['Subject'] = 'You want not to use a bogus header' mail_message.header['Tag'] = 'bogus-tag' mail_message.header['Attachment'] = 'anydatahere' mail_message.header['Allowed-Header'] = 'value' mail_message.header['TRACK-OPENS'] = 'true' mail_message.header['TRACK-LINKS'] = 'HtmlOnly' mail_message end it 'only allowed headers' do headers = mail_message_with_reserved_headers.export_headers header_names = headers.map { |h| h['Name'] } aggregate_failures do expect(header_names).to include('Allowed-Header') expect(header_names.count).to eq 1 end end it 'custom header character case preserved' do custom_header = {"Name"=>"custom-Header", "Value"=>"cUsTomHeaderValue"} mail_message.header[custom_header['Name']] = custom_header['Value'] expect(mail_message.export_headers.first).to match(custom_header) end end describe "#to_postmark_hash" do # See mail_message_converter_spec.rb end describe '#templated?' do it { expect(mail_message).to_not be_templated } it { expect(templated_message).to be_templated } end describe '#prerender' do let(:model) { templated_message.template_model } let(:model_text) { model[:name] } let(:template_response) do { :html_body => '{{ name }}', :text_body => '{{ name }}' } end let(:successful_render_response) do { :all_content_is_valid => true, :subject => { :rendered_content => 'Subject' }, :text_body => { :rendered_content => model_text }, :html_body => { :rendered_content => "#{model_text}" } } end let(:failed_render_response) do { :all_content_is_valid => false, :subject => { :rendered_content => 'Subject' }, :text_body => { :rendered_content => model_text }, :html_body => { :rendered_content => nil, :validation_errors => [ { :message => 'The syntax for this template is invalid.', :line => 1, :character_position => 1 } ] } } end subject(:rendering) { message.prerender } context 'when called on a non-templated message' do let(:message) { mail_message } it 'raises a Postmark::Error' do expect { rendering }.to raise_error(Postmark::Error, /Cannot prerender/) end end context 'when called on a templated message' do let(:message) { templated_message } before do message.delivery_method delivery_method end context 'and using a non-Postmark delivery method' do let(:delivery_method) { Mail::SMTP } it { expect { rendering }.to raise_error(Postmark::MailAdapterError) } end context 'and using a Postmark delivery method' do let(:delivery_method) { Mail::Postmark } before do expect_any_instance_of(Postmark::ApiClient). to receive(:get_template).with(message.template_alias). and_return(template_response) expect_any_instance_of(Postmark::ApiClient). to receive(:validate_template).with(template_response.merge(:test_render_model => model)). and_return(render_response) end context 'and rendering succeeds' do let(:render_response) { successful_render_response } it 'sets HTML and Text parts to rendered values' do expect { rendering }. to change { message.subject }.to(render_response[:subject][:rendered_content]). and change { message.body_text }.to(render_response[:text_body][:rendered_content]). and change { message.body_html }.to(render_response[:html_body][:rendered_content]) end end context 'and rendering fails' do let(:render_response) { failed_render_response } it 'raises Postmark::InvalidTemplateError' do expect { rendering }.to raise_error(Postmark::InvalidTemplateError) end end end end end end postmark-1.22.0/spec/unit/postmark/mail_message_converter_spec.rb0000644000175100017510000003055514123042137025450 0ustar vivekdebvivekdeb# encoding: utf-8 require 'spec_helper' describe Postmark::MailMessageConverter do subject {Postmark::MailMessageConverter} let(:mail_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" body "Hello Sheldon!" end end let(:mail_html_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" content_type 'text/html; charset=UTF-8' body "Hello Sheldon!" end end let(:mail_message_with_open_tracking) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" content_type 'text/html; charset=UTF-8' body "Hello Sheldon!" track_opens true end end let(:mail_message_with_open_tracking_disabled) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" content_type 'text/html; charset=UTF-8' body "Hello Sheldon!" track_opens false end end let(:mail_message_with_open_tracking_set_variable) do mail = mail_html_message mail.track_opens = true mail end let(:mail_message_with_open_tracking_disabled_set_variable) do mail = mail_html_message mail.track_opens = false mail end let(:mail_message_with_link_tracking_all) do mail = mail_html_message mail.track_links :html_and_text mail end let(:mail_message_with_link_tracking_html) do mail = mail_html_message mail.track_links = :html_only mail end let(:mail_message_with_link_tracking_text) do mail = mail_html_message mail.track_links = :text_only mail end let(:mail_message_with_link_tracking_none) do mail = mail_html_message mail.track_links = :none mail end let(:tagged_mail_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" body "Hello Sheldon!" tag "sheldon" end end let(:mail_message_without_body) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" end end let(:mail_multipart_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" text_part do body "Hello Sheldon!" end html_part do body "Hello Sheldon!" end end end let(:mail_message_with_attachment) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" body "Hello Sheldon!" add_file empty_gif_path end end let(:mail_message_with_named_addresses) do Mail.new do from "Sheldon " to "\"Leonard Hofstadter\" " subject "Hello!" body "Hello Sheldon!" reply_to '"Penny The Neighbor" ' end end let(:mail_message_quoted_printable) do Mail.new do from "Sheldon " to "\"Leonard Hofstadter\" " subject "Hello!" content_type 'text/plain; charset=utf-8' content_transfer_encoding 'quoted-printable' body 'Он здесь бывал: еще не в галифе.' reply_to '"Penny The Neighbor" ' end end let(:multipart_message_quoted_printable) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" text_part do content_type 'text/plain; charset=utf-8' content_transfer_encoding 'quoted-printable' body 'Загадочное послание.' end html_part do content_type 'text/html; charset=utf-8' content_transfer_encoding 'quoted-printable' body 'Загадочное послание.' end end end let(:templated_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" template_alias "hello" template_model :name => "Sheldon" end end it 'converts plain text messages correctly' do expect(subject.new(mail_message).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "TextBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com"}) end it 'converts tagged text messages correctly' do expect(subject.new(tagged_mail_message).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "TextBody" => "Hello Sheldon!", "Tag" => "sheldon", "To" => "lenard@bigbangtheory.com"}) end it 'converts plain text messages without body correctly' do expect(subject.new(mail_message_without_body).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "To" => "lenard@bigbangtheory.com"}) end it 'converts html messages correctly' do expect(subject.new(mail_html_message).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com"}) end it 'converts multipart messages correctly' do expect(subject.new(mail_multipart_message).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "TextBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com"}) end it 'converts messages with attachments correctly' do expect(subject.new(mail_message_with_attachment).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "Attachments" => [{"Name" => "empty.gif", "Content" => encoded_empty_gif_data, "ContentType" => "image/gif"}], "TextBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com"}) end it 'converts messages with named addresses correctly' do expect(subject.new(mail_message_with_named_addresses).run).to eq({ "From" => "Sheldon ", "Subject" => "Hello!", "TextBody" => "Hello Sheldon!", "To" => "Leonard Hofstadter ", "ReplyTo" => 'Penny The Neighbor '}) end it 'convertes templated messages correctly' do expect(subject.new(templated_message).run).to eq({ "From" => "sheldon@bigbangtheory.com", "TemplateAlias" => "hello", "TemplateModel" => {:name => "Sheldon"}, "To" => "lenard@bigbangtheory.com"}) end context 'open tracking' do context 'setup inside of mail' do it 'converts open tracking enabled messages correctly' do expect(subject.new(mail_message_with_open_tracking).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackOpens" => true}) end it 'converts open tracking disabled messages correctly' do expect(subject.new(mail_message_with_open_tracking_disabled).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackOpens" => false}) end end context 'setup with tracking variable' do it 'converts open tracking enabled messages correctly' do expect(subject.new(mail_message_with_open_tracking_set_variable).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackOpens" => true}) end it 'converts open tracking disabled messages correctly' do expect(subject.new(mail_message_with_open_tracking_disabled_set_variable).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackOpens" => false}) end end end context 'link tracking' do it 'converts html and text link tracking enabled messages correctly' do expect(subject.new(mail_message_with_link_tracking_all).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackLinks" => 'HtmlAndText'}) end it 'converts html only link tracking enabled messages correctly' do expect(subject.new(mail_message_with_link_tracking_html).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackLinks" => 'HtmlOnly'}) end it 'converts text only link tracking enabled messages correctly' do expect(subject.new(mail_message_with_link_tracking_text).run).to eq({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackLinks" => 'TextOnly'}) end it 'converts link tracking disabled messages correctly' do expect(subject.new(mail_message_with_link_tracking_none).run).to eq ({ "From" => "sheldon@bigbangtheory.com", "Subject" => "Hello!", "HtmlBody" => "Hello Sheldon!", "To" => "lenard@bigbangtheory.com", "TrackLinks" => 'None'}) end it 'converts link tracking options when set via header' do msg = mail_html_message msg[:track_links] = :html_and_text expect(subject.new(msg).run).to include('TrackLinks' => 'HtmlAndText') end end context 'metadata' do it 'converts single metadata field' do metadata = {:test => 'test'} msg = mail_html_message msg.metadata = metadata expect(subject.new(msg).run).to include('Metadata' => metadata) end it 'converts unicode metadata field metadata' do metadata = {:test => "Велик"} msg = mail_html_message msg.metadata = metadata expect(subject.new(msg).run).to include('Metadata' => metadata) end it 'converts multiple metadata fields' do metadata = {} 10.times {|i| metadata["test#{i + 1}"] = "t" * 80} msg = mail_html_message msg.metadata = metadata expect(subject.new(msg).run).to include('Metadata' => metadata) end end it 'correctly decodes unicode in messages transfered as quoted-printable' do expect(subject.new(mail_message_quoted_printable).run).to include('TextBody' => 'Он здесь бывал: еще не в галифе.') end it 'correctly decodes unicode in multipart quoted-printable messages' do expect(subject.new(multipart_message_quoted_printable).run).to include( 'TextBody' => 'Загадочное послание.', 'HtmlBody' => 'Загадочное послание.') end context 'when bcc is empty' do it 'excludes bcc from message' do mail_message.bcc = nil expect(mail_message.to_postmark_hash.keys).not_to include('Bcc') end end context 'when cc is empty' do it 'excludes cc from message' do mail_message.cc = nil expect(mail_message.to_postmark_hash.keys).not_to include('Cc') end end describe 'passing message stream' do context 'when not set' do specify { expect(subject.new(mail_message).run).not_to include('MessageStream') } end context 'when set' do before do mail_message.message_stream = 'weekly-newsletter' end it 'passes message stream to the API call' do expect(subject.new(mail_message).run).to include('MessageStream' => 'weekly-newsletter') end end end end postmark-1.22.0/spec/unit/postmark/json_spec.rb0000644000175100017510000000153014123042137021673 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::Json do let(:data) { {"bar" => "foo", "foo" => "bar"} } shared_examples "json parser" do it 'encodes and decodes data correctly' do hash = Postmark::Json.decode(Postmark::Json.encode(data)) expect(hash).to have_key("bar") expect(hash).to have_key("foo") end end context "given response parser is JSON" do before do Postmark.response_parser_class = :Json end it_behaves_like "json parser" end context "given response parser is ActiveSupport::JSON" do before do Postmark.response_parser_class = :ActiveSupport end it_behaves_like "json parser" end context "given response parser is Yajl", :skip_for_platform => 'java' do before do Postmark.response_parser_class = :Yajl end it_behaves_like "json parser" end endpostmark-1.22.0/spec/unit/postmark/inflector_spec.rb0000644000175100017510000000225314123042137022712 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::Inflector do describe ".to_postmark" do it 'converts rubyish underscored format to camel cased symbols accepted by the Postmark API' do expect(subject.to_postmark(:foo_bar)).to eq 'FooBar' expect(subject.to_postmark(:_bar)).to eq 'Bar' expect(subject.to_postmark(:really_long_long_long_long_symbol)).to eq 'ReallyLongLongLongLongSymbol' expect(subject.to_postmark(:foo_bar_1)).to eq 'FooBar1' end it 'accepts strings as well' do expect(subject.to_postmark('foo_bar')).to eq 'FooBar' end it 'acts idempotentely' do expect(subject.to_postmark('FooBar')).to eq 'FooBar' end end describe ".to_ruby" do it 'converts camel cased symbols returned by the Postmark API to underscored Ruby symbols' do expect(subject.to_ruby('FooBar')).to eq :foo_bar expect(subject.to_ruby('LongTimeAgoInAFarFarGalaxy')).to eq :long_time_ago_in_a_far_far_galaxy expect(subject.to_ruby('MessageID')).to eq :message_id end it 'acts idempotentely' do expect(subject.to_ruby(:foo_bar)).to eq :foo_bar expect(subject.to_ruby(:foo_bar_1)).to eq :foo_bar_1 end end endpostmark-1.22.0/spec/unit/postmark/inbound_spec.rb0000644000175100017510000001130714123042137022363 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::Inbound do # http://developer.postmarkapp.com/developer-inbound-parse.html#example-hook let(:example_inbound) { '{"From":"myUser@theirDomain.com","FromFull":{"Email":"myUser@theirDomain.com","Name":"John Doe"},"To":"451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com","ToFull":[{"Email":"451d9b70cf9364d23ff6f9d51d870251569e+ahoy@inbound.postmarkapp.com","Name":""}],"Cc":"\"Full name\" , \"Another Cc\" ","CcFull":[{"Email":"sample.cc@emailDomain.com","Name":"Full name"},{"Email":"another.cc@emailDomain.com","Name":"Another Cc"}],"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)]","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":""}],"Attachments":[{"Name":"myimage.png","Content":"[BASE64-ENCODED CONTENT]","ContentType":"image/png","ContentLength":4096},{"Name":"mypaper.doc","Content":"[BASE64-ENCODED CONTENT]","ContentType":"application/msword","ContentLength":16384}]}' } context "given a serialized inbound document" do subject { Postmark::Inbound.to_ruby_hash(example_inbound) } it { expect(subject).to have_key(:from) } it { expect(subject).to have_key(:from_full) } it { expect(subject).to have_key(:to) } it { expect(subject).to have_key(:to_full) } it { expect(subject).to have_key(:cc) } it { expect(subject).to have_key(:cc_full) } it { expect(subject).to have_key(:reply_to) } it { expect(subject).to have_key(:subject) } it { expect(subject).to have_key(:message_id) } it { expect(subject).to have_key(:date) } it { expect(subject).to have_key(:mailbox_hash) } it { expect(subject).to have_key(:text_body) } it { expect(subject).to have_key(:html_body) } it { expect(subject).to have_key(:tag) } it { expect(subject).to have_key(:headers) } it { expect(subject).to have_key(:attachments) } context "cc" do it 'has 2 CCs' do expect(subject[:cc_full].count).to eq 2 end it 'stores CCs as an array of Ruby hashes' do cc = subject[:cc_full].last expect(cc).to have_key(:email) expect(cc).to have_key(:name) end end context "to" do it 'has 1 recipients' do expect(subject[:to_full].count).to eq 1 end it 'stores TOs as an array of Ruby hashes' do cc = subject[:to_full].last expect(cc).to have_key(:email) expect(cc).to have_key(:name) end end context "from" do it 'is a hash' do expect(subject[:from_full]).to be_a Hash end it 'has all required fields' do expect(subject[:from_full]).to have_key(:email) expect(subject[:from_full]).to have_key(:name) end end context "headers" do it 'has 8 headers' do expect(subject[:headers].count).to eq 8 end it 'stores headers as an array of Ruby hashes' do header = subject[:headers].last expect(header).to have_key(:name) expect(header).to have_key(:value) end end context "attachments" do it 'has 2 attachments' do expect(subject[:attachments].count).to eq 2 end it 'stores attachemnts as an array of Ruby hashes' do attachment = subject[:attachments].last expect(attachment).to have_key(:name) expect(attachment).to have_key(:content) expect(attachment).to have_key(:content_type) expect(attachment).to have_key(:content_length) end end end endpostmark-1.22.0/spec/unit/postmark/http_client_spec.rb0000644000175100017510000002351314123042137023244 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::HttpClient do def response_body(status, message = "") {"ErrorCode" => status, "Message" => message}.to_json end let(:api_token) { "provided-postmark-api-token" } let(:http_client) { Postmark::HttpClient.new(api_token) } subject { http_client } context "attr writers" do it { expect(subject).to respond_to(:api_token=) } it { expect(subject).to respond_to(:api_key=) } end context "attr readers" do it { expect(subject).to respond_to(:http) } it { expect(subject).to respond_to(:secure) } it { expect(subject).to respond_to(:api_token) } it { expect(subject).to respond_to(:api_key) } it { expect(subject).to respond_to(:proxy_host) } it { expect(subject).to respond_to(:proxy_port) } it { expect(subject).to respond_to(:proxy_user) } it { expect(subject).to respond_to(:proxy_pass) } it { expect(subject).to respond_to(:host) } it { expect(subject).to respond_to(:port) } it { expect(subject).to respond_to(:path_prefix) } it { expect(subject).to respond_to(:http_open_timeout) } it { expect(subject).to respond_to(:http_read_timeout) } it { expect(subject).to respond_to(:http_ssl_version) } end context "when it is created without options" do its(:api_token) { is_expected.to eq api_token } its(:api_key) { is_expected.to eq api_token } its(:host) { is_expected.to eq 'api.postmarkapp.com' } its(:port) { is_expected.to eq 443 } its(:secure) { is_expected.to be true } its(:path_prefix) { is_expected.to eq '/' } its(:http_read_timeout) { is_expected.to eq 15 } its(:http_open_timeout) { is_expected.to eq 5 } it 'does not provide a default which utilizes the Net::HTTP default', :skip_ruby_version => ['1.8.7'] do http_client = subject.http expect(http_client.ssl_version).to eq nil end end context "when it is created with options" do let(:secure) { true } let(:proxy_host) { "providedproxyhostname.com" } let(:proxy_port) { 42 } let(:proxy_user) { "provided proxy user" } let(:proxy_pass) { "provided proxy pass" } let(:host) { "providedhostname.org" } let(:port) { 4443 } let(:path_prefix) { "/provided/path/prefix" } let(:http_open_timeout) { 42 } let(:http_read_timeout) { 42 } let(:http_ssl_version) { :TLSv1_2} subject { Postmark::HttpClient.new(api_token, :secure => secure, :proxy_host => proxy_host, :proxy_port => proxy_port, :proxy_user => proxy_user, :proxy_pass => proxy_pass, :host => host, :port => port, :path_prefix => path_prefix, :http_open_timeout => http_open_timeout, :http_read_timeout => http_read_timeout, :http_ssl_version => http_ssl_version) } its(:api_token) { is_expected.to eq api_token } its(:api_key) { is_expected.to eq api_token } its(:secure) { is_expected.to eq secure } its(:proxy_host) { is_expected.to eq proxy_host } its(:proxy_port) { is_expected.to eq proxy_port } its(:proxy_user) { is_expected.to eq proxy_user } its(:proxy_pass) { is_expected.to eq proxy_pass } its(:host) { is_expected.to eq host } its(:port) { is_expected.to eq port } its(:path_prefix) { is_expected.to eq path_prefix } its(:http_open_timeout) { is_expected.to eq http_open_timeout } its(:http_read_timeout) { is_expected.to eq http_read_timeout } its(:http_ssl_version) { is_expected.to eq http_ssl_version } it 'uses port 80 for plain HTTP connections' do expect(Postmark::HttpClient.new(api_token, :secure => false).port).to eq(80) end it 'uses port 443 for secure HTTP connections' do expect(Postmark::HttpClient.new(api_token, :secure => true).port).to eq(443) end it 'respects port over secure option' do client = Postmark::HttpClient.new(api_token, :port => 80, :secure => true) expect(client.port).to eq(80) expect(client.protocol).to eq('https') end end describe "#post" do let(:target_path) { "path/on/server" } let(:target_url) { "https://api.postmarkapp.com/#{target_path}" } it "sends a POST request to provided URI" do FakeWeb.register_uri(:post, target_url, :body => response_body(200)) subject.post(target_path) expect(FakeWeb.last_request.method).to eq('POST') expect(FakeWeb.last_request.path).to eq('/' + target_path) end it "raises a custom error when API token authorization fails" do FakeWeb.register_uri(:post, target_url, :body => response_body(401), :status => [ "401", "Unauthorized" ]) expect { subject.post(target_path) }.to raise_error Postmark::InvalidApiKeyError end it "raises a custom error when sent JSON was not valid" do FakeWeb.register_uri(:post, target_url, :body => response_body(422), :status => [ "422", "Invalid" ]) expect { subject.post(target_path) }.to raise_error Postmark::InvalidMessageError end it "raises a custom error when server fails to process the request" do FakeWeb.register_uri(:post, target_url, :body => response_body(500), :status => [ "500", "Internal Server Error" ]) expect { subject.post(target_path) }.to raise_error Postmark::InternalServerError end it "raises a custom error when the request times out" do expect(subject.http).to receive(:post).at_least(:once).and_raise(Timeout::Error) expect { subject.post(target_path) }.to raise_error Postmark::TimeoutError end it "raises a default error when unknown issue occurs" do FakeWeb.register_uri(:post, target_url, :body => response_body(485), :status => [ "485", "Custom HTTP response status" ]) expect { subject.post(target_path) }.to raise_error Postmark::UnknownError end end describe "#get" do let(:target_path) { "path/on/server" } let(:target_url) { "https://api.postmarkapp.com/#{target_path}" } it "sends a GET request to provided URI" do FakeWeb.register_uri(:get, target_url, :body => response_body(200)) subject.get(target_path) expect(FakeWeb.last_request.method).to eq('GET') expect(FakeWeb.last_request.path).to eq('/' + target_path) end it "raises a custom error when API token authorization fails" do FakeWeb.register_uri(:get, target_url, :body => response_body(401), :status => [ "401", "Unauthorized" ]) expect { subject.get(target_path) }.to raise_error Postmark::InvalidApiKeyError end it "raises a custom error when sent JSON was not valid" do FakeWeb.register_uri(:get, target_url, :body => response_body(422), :status => [ "422", "Invalid" ]) expect { subject.get(target_path) }.to raise_error Postmark::InvalidMessageError end it "raises a custom error when server fails to process the request" do FakeWeb.register_uri(:get, target_url, :body => response_body(500), :status => [ "500", "Internal Server Error" ]) expect { subject.get(target_path) }.to raise_error Postmark::InternalServerError end it "raises a custom error when the request times out" do expect(subject.http).to receive(:get).at_least(:once).and_raise(Timeout::Error) expect { subject.get(target_path) }.to raise_error Postmark::TimeoutError end it "raises a default error when unknown issue occurs" do FakeWeb.register_uri(:get, target_url, :body => response_body(485), :status => [ "485", "Custom HTTP response status" ]) expect { subject.get(target_path) }.to raise_error Postmark::UnknownError end end describe "#put" do let(:target_path) { "path/on/server" } let(:target_url) { "https://api.postmarkapp.com/#{target_path}" } it "sends a PUT request to provided URI" do FakeWeb.register_uri(:put, target_url, :body => response_body(200)) subject.put(target_path) expect(FakeWeb.last_request.method).to eq('PUT') expect(FakeWeb.last_request.path).to eq('/' + target_path) end it "raises a custom error when API token authorization fails" do FakeWeb.register_uri(:put, target_url, :body => response_body(401), :status => [ "401", "Unauthorized" ]) expect { subject.put(target_path) }.to raise_error Postmark::InvalidApiKeyError end it "raises a custom error when sent JSON was not valid" do FakeWeb.register_uri(:put, target_url, :body => response_body(422), :status => [ "422", "Invalid" ]) expect { subject.put(target_path) }.to raise_error Postmark::InvalidMessageError end it "raises a custom error when server fails to process the request" do FakeWeb.register_uri(:put, target_url, :body => response_body(500), :status => [ "500", "Internal Server Error" ]) expect { subject.put(target_path) }.to raise_error Postmark::InternalServerError end it "raises a custom error when the request times out" do expect(subject.http).to receive(:put).at_least(:once).and_raise(Timeout::Error) expect { subject.put(target_path) }.to raise_error Postmark::TimeoutError end it "raises a default error when unknown issue occurs" do FakeWeb.register_uri(:put, target_url, :body => response_body(485), :status => [ "485", "Custom HTTP response status" ]) expect { subject.put(target_path) }.to raise_error Postmark::UnknownError end end end postmark-1.22.0/spec/unit/postmark/helpers/0000755000175100017510000000000014123042137021026 5ustar vivekdebvivekdebpostmark-1.22.0/spec/unit/postmark/helpers/message_helper_spec.rb0000644000175100017510000001361514123042137025356 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::MessageHelper do let(:attachments) { [ File.open(empty_gif_path), {:name => "img2.gif", :content => Postmark::MessageHelper.encode_in_base64(File.read(empty_gif_path)), :content_type => "application/octet-stream"} ] } let(:postmark_attachments) { content = Postmark::MessageHelper.encode_in_base64(File.read(empty_gif_path)) [ {"Name" => "empty.gif", "Content" => content, "ContentType" => "application/octet-stream"}, {"Name" => "img2.gif", "Content" => content, "ContentType" => "application/octet-stream"} ] } let(:headers) { [{:name => "CUSTOM-HEADER", :value => "value"}] } let(:postmark_headers) { [{"Name" => "CUSTOM-HEADER", "Value" => "value"}] } describe ".to_postmark" do let(:message) { { :from => "sender@example.com", :to => "receiver@example.com", :cc => "copied@example.com", :bcc => "blank-copied@example.com", :subject => "Test", :tag => "Invitation", :html_body => "Hello", :text_body => "Hello", :reply_to => "reply@example.com" } } let(:postmark_message) { { "From" => "sender@example.com", "To" => "receiver@example.com", "Cc" => "copied@example.com", "Bcc"=> "blank-copied@example.com", "Subject" => "Test", "Tag" => "Invitation", "HtmlBody" => "Hello", "TextBody" => "Hello", "ReplyTo" => "reply@example.com", } } let(:message_with_headers) { message.merge(:headers => headers) } let(:postmark_message_with_headers) { postmark_message.merge("Headers" => postmark_headers) } let(:message_with_headers_and_attachments) { message_with_headers.merge(:attachments => attachments) } let(:postmark_message_with_headers_and_attachments) { postmark_message_with_headers.merge("Attachments" => postmark_attachments) } let(:message_with_open_tracking) { message.merge(:track_opens => true) } let(:message_with_open_tracking_false) { message.merge(:track_opens => false) } let(:postmark_message_with_open_tracking) { postmark_message.merge("TrackOpens" => true) } let(:postmark_message_with_open_tracking_false) { postmark_message.merge("TrackOpens" => false) } it 'converts messages without custom headers and attachments correctly' do expect(subject.to_postmark(message)).to eq postmark_message end it 'converts messages with custom headers and without attachments correctly' do expect(subject.to_postmark(message_with_headers)).to eq postmark_message_with_headers end it 'converts messages with custom headers and attachments correctly' do expect(subject.to_postmark(message_with_headers_and_attachments)).to eq postmark_message_with_headers_and_attachments end context 'open tracking' do it 'converts messages with open tracking flag set to true correctly' do expect(subject.to_postmark(message_with_open_tracking)).to eq(postmark_message_with_open_tracking) end it 'converts messages with open tracking flag set to false correctly' do expect(subject.to_postmark(message_with_open_tracking_false)).to eq(postmark_message_with_open_tracking_false) end end context 'metadata' do it 'converts messages with metadata correctly' do metadata = {"test" => "value"} data= message.merge(:metadata => metadata) expect(subject.to_postmark(data)).to include(postmark_message.merge("Metadata" => metadata)) end end context 'link tracking' do let(:message_with_link_tracking_html) { message.merge(:track_links => :html_only) } let(:message_with_link_tracking_text) { message.merge(:track_links => :text_only) } let(:message_with_link_tracking_all) { message.merge(:track_links => :html_and_text) } let(:message_with_link_tracking_none) { message.merge(:track_links => :none) } let(:postmark_message_with_link_tracking_html) { postmark_message.merge("TrackLinks" => 'HtmlOnly') } let(:postmark_message_with_link_tracking_text) { postmark_message.merge("TrackLinks" => 'TextOnly') } let(:postmark_message_with_link_tracking_all) { postmark_message.merge("TrackLinks" => 'HtmlAndText') } let(:postmark_message_with_link_tracking_none) { postmark_message.merge("TrackLinks" => 'None') } it 'converts html body link tracking to Postmark format' do expect(subject.to_postmark(message_with_link_tracking_html)).to eq(postmark_message_with_link_tracking_html) end it 'converts text body link tracking to Postmark format' do expect(subject.to_postmark(message_with_link_tracking_text)).to eq(postmark_message_with_link_tracking_text) end it 'converts html and text body link tracking to Postmark format' do expect(subject.to_postmark(message_with_link_tracking_all)).to eq(postmark_message_with_link_tracking_all) end it 'converts no link tracking to Postmark format' do expect(subject.to_postmark(message_with_link_tracking_none)).to eq(postmark_message_with_link_tracking_none) end end end describe ".headers_to_postmark" do it 'converts headers to Postmark format' do expect(subject.headers_to_postmark(headers)).to eq postmark_headers end it 'accepts single header as a non-array' do expect(subject.headers_to_postmark(headers.first)).to eq [postmark_headers.first] end end describe ".attachments_to_postmark" do it 'converts attachments to Postmark format' do expect(subject.attachments_to_postmark(attachments)).to eq postmark_attachments end it 'accepts single attachment as a non-array' do expect(subject.attachments_to_postmark(attachments.first)).to eq [postmark_attachments.first] end end endpostmark-1.22.0/spec/unit/postmark/helpers/hash_helper_spec.rb0000644000175100017510000000520014123042137024644 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::HashHelper do describe ".to_postmark" do let(:source) do { :level_one => { :level_two => { :level_three => [{ :array_item => 1 }] } } } end describe 'default behaviour' do let(:target) do { 'LevelOne' => { :level_two => { :level_three => [{ :array_item => 1 }] } } } end it 'does not convert nested elements' do expect(subject.to_postmark(source)).to eq(target) end end describe 'deep conversion' do let(:target) do { 'LevelOne' => { 'LevelTwo' => { 'LevelThree' => [{ 'ArrayItem' => 1 }] } } } end it 'converts nested elements when requested' do expect(subject.to_postmark(source, :deep => true)).to eq(target) end end it 'leaves CamelCase keys untouched' do expect(subject.to_postmark('ReplyTo' => 'alice@example.com')).to eq('ReplyTo' => 'alice@example.com') end end describe ".to_ruby" do let(:source) do { 'LevelOne' => { 'LevelTwo' => { 'LevelThree' => [{ 'ArrayItem' => 1 }] } } } end describe 'default behaviour' do let(:target) do { :level_one => { 'LevelTwo' => { 'LevelThree' => [{ 'ArrayItem' => 1 }] } } } end it 'does not convert nested elements' do expect(subject.to_ruby(source)).to eq(target) end end describe 'deep conversion' do let(:target) do { :level_one => { :level_two => { :level_three => [{ :array_item => 1 }] } } } end it 'converts nested elements when requested' do expect(subject.to_ruby(source, :deep => true)).to eq(target) end end describe 'compatibility mode' do let(:target) do { :level_one => { 'LevelTwo' => { 'LevelThree' => [{ 'ArrayItem' => 1 }] } }, 'LevelOne' => { 'LevelTwo' => { 'LevelThree' => [{ 'ArrayItem' => 1 }] } } } end it 'preserves the original structure' do expect(subject.to_ruby(source, :compatible => true)).to eq target end end it 'leaves symbol keys untouched' do expect(subject.to_ruby(:reply_to => 'alice@example.com')).to eq(:reply_to => 'alice@example.com') end end endpostmark-1.22.0/spec/unit/postmark/handlers/0000755000175100017510000000000014123042137021164 5ustar vivekdebvivekdebpostmark-1.22.0/spec/unit/postmark/handlers/mail_spec.rb0000644000175100017510000000511114123042137023443 0ustar vivekdebvivekdebrequire 'spec_helper' describe Mail::Postmark do let(:message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" subject "Hello!" body "Hello Sheldon!" end end before do message.delivery_method Mail::Postmark end subject(:handler) { message.delivery_method } it "can be set as delivery_method" do message.delivery_method Mail::Postmark is_expected.to be_a(Mail::Postmark) end describe '#deliver!' do it "returns self by default" do expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message) expect(message.deliver).to eq message end it "returns the actual response if :return_response setting is present" do expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message) message.delivery_method Mail::Postmark, :return_response => true expect(message.deliver).to eq message end it "allows setting the api token" do message.delivery_method Mail::Postmark, :api_token => 'api-token' expect(message.delivery_method.settings[:api_token]).to eq 'api-token' end it 'uses provided API token' do message.delivery_method Mail::Postmark, :api_token => 'api-token' expect(Postmark::ApiClient).to receive(:new).with('api-token', {}).and_return(double(:deliver_message => true)) message.deliver end it 'uses API token provided as legacy api_key' do message.delivery_method Mail::Postmark, :api_key => 'api-token' expect(Postmark::ApiClient).to receive(:new).with('api-token', {}).and_return(double(:deliver_message => true)) message.deliver end context 'when sending a pre-rendered message' do it "uses ApiClient#deliver_message to send the message" do expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message) message.deliver end end context 'when sending a Postmark template' do let(:message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" template_alias "hello" template_model :name => "Sheldon" end end it 'uses ApiClient#deliver_message_with_template to send the message' do expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message_with_template).with(message) message.deliver end end end describe '#api_client' do subject { handler.api_client } it { is_expected.to be_a(Postmark::ApiClient) } end end postmark-1.22.0/spec/unit/postmark/error_spec.rb0000644000175100017510000001757014123042137022066 0ustar vivekdebvivekdebrequire 'spec_helper' describe(Postmark::Error) do it {is_expected.to be_a(StandardError)} end describe(Postmark::HttpClientError) do it {is_expected.to be_a(Postmark::Error)} it {expect(subject.retry?).to be true} end describe(Postmark::HttpServerError) do it {is_expected.to be_a(Postmark::Error)} describe '.build' do context 'picks an appropriate subclass for code' do subject {Postmark::HttpServerError.build(code, Postmark::Json.encode({}))} context '401' do let(:code) {'401'} it {is_expected.to be_a(Postmark::InvalidApiKeyError)} its(:status_code) {is_expected.to eq 401} end context '422' do let(:code) {'422'} it {is_expected.to be_a(Postmark::ApiInputError)} its(:status_code) {is_expected.to eq 422} end context '500' do let(:code) {'500'} it {is_expected.to be_a(Postmark::InternalServerError)} its(:status_code) {is_expected.to eq 500} end context 'others' do let(:code) {'999'} it {is_expected.to be_a(Postmark::UnexpectedHttpResponseError)} its(:status_code) {is_expected.to eq code.to_i} end end end describe '#retry?' do it 'is true for 5XX status codes' do (500...600).each do |code| expect(Postmark::HttpServerError.new(code).retry?).to be true end end it 'is false for other codes except 5XX' do [200, 300, 400].each do |code| expect(Postmark::HttpServerError.new(code).retry?).to be false end end end describe '#message ' do it 'uses "Message" field on postmark response if available' do data = {'Message' => 'Postmark error message'} error = Postmark::HttpServerError.new(502, Postmark::Json.encode(data), data) expect(error.message).to eq data['Message'] end it 'falls back to a message generated from status code' do error = Postmark::HttpServerError.new(502, '') expect(error.message).to match(/The Postmark API responded with HTTP status \d+/) end end end describe(Postmark::ApiInputError) do describe '.build' do context 'picks an appropriate subclass for error code' do let(:response) {{'ErrorCode' => code}} subject do Postmark::ApiInputError.build(Postmark::Json.encode(response), response) end shared_examples_for 'api input error' do its(:status_code) {is_expected.to eq 422} it {expect(subject.retry?).to be false} it {is_expected.to be_a(Postmark::ApiInputError)} it {is_expected.to be_a(Postmark::HttpServerError)} end context '406' do let(:code) {Postmark::ApiInputError::INACTIVE_RECIPIENT} it {is_expected.to be_a(Postmark::InactiveRecipientError)} it_behaves_like 'api input error' end context '300' do let(:code) {Postmark::ApiInputError::INVALID_EMAIL_ADDRESS} it {is_expected.to be_a(Postmark::InvalidEmailAddressError)} it_behaves_like 'api input error' end context 'others' do let(:code) {'9999'} it_behaves_like 'api input error' end end end end describe Postmark::InvalidTemplateError do subject(:error) {Postmark::InvalidTemplateError.new(:foo => 'bar')} it 'is created with a response' do expect(error.message).to start_with('Failed to render the template.') expect(error.postmark_response).to eq(:foo => 'bar') end end describe(Postmark::TimeoutError) do it {is_expected.to be_a(Postmark::Error)} it {expect(subject.retry?).to be true} end describe(Postmark::UnknownMessageType) do it 'exists for backward compatibility' do is_expected.to be_a(Postmark::Error) end end describe(Postmark::InvalidApiKeyError) do it {is_expected.to be_a(Postmark::Error)} end describe(Postmark::InternalServerError) do it {is_expected.to be_a(Postmark::Error)} end describe(Postmark::UnexpectedHttpResponseError) do it {is_expected.to be_a(Postmark::Error)} end describe(Postmark::MailAdapterError) do it {is_expected.to be_a(Postmark::Error)} end describe(Postmark::InvalidEmailAddressError) do describe '.new' do let(:response) {{'Message' => message}} subject do Postmark::InvalidEmailAddressError.new( Postmark::ApiInputError::INVALID_EMAIL_ADDRESS, Postmark::Json.encode(response), response) end let(:message) do "Error parsing 'To': Illegal email address 'johne.xample.com'. It must contain the '@' symbol." end it 'body is set' do expect(subject.body).to eq(Postmark::Json.encode(response)) end it 'parsed body is set' do expect(subject.parsed_body).to eq(response) end it 'error code is set' do expect(subject.error_code).to eq(Postmark::ApiInputError::INVALID_EMAIL_ADDRESS) end end end describe(Postmark::InactiveRecipientError) do describe '.parse_recipients' do let(:recipients) do %w(nothing@wildbit.com noth.ing+2@wildbit.com noth.ing+2-1@wildbit.com) end subject {Postmark::InactiveRecipientError.parse_recipients(message)} context '1/1 inactive' do let(:message) do 'You tried to send to a recipient that has been marked as ' \ "inactive.\nFound inactive addresses: #{recipients[0]}.\n" \ 'Inactive recipients are ones that have generated a hard ' \ 'bounce or a spam complaint.' end it {is_expected.to eq(recipients.take(1))} end context 'i/n inactive, n > 1, i < n - new message format' do let(:message) { "Message OK, but will not deliver to these inactive addresses: #{recipients[0...2].join(', ')}" } it {is_expected.to eq(recipients.take(2))} end context 'i/n inactive, n > 1, i < n' do let(:message) do 'Message OK, but will not deliver to these inactive addresses: ' \ "#{recipients[0...2].join(', ')}. Inactive recipients are ones that " \ 'have generated a hard bounce or a spam complaint.' end it {is_expected.to eq(recipients.take(2))} end context 'n/n inactive, n > 1' do let(:message) do 'You tried to send to recipients that have all been marked as ' \ "inactive.\nFound inactive addresses: #{recipients.join(', ')}.\n" \ 'Inactive recipients are ones that have generated a hard bounce or a spam complaint.' end it {is_expected.to eq(recipients)} end context 'unknown error format' do let(:message) {recipients.join(', ')} it {is_expected.to eq([])} end end describe '.new' do let(:address) {'user@example.org'} let(:response) {{'Message' => message}} subject do Postmark::InactiveRecipientError.new( Postmark::ApiInputError::INACTIVE_RECIPIENT, Postmark::Json.encode(response), response) end let(:message) do 'You tried to send to a recipient that has been marked as ' \ "inactive.\nFound inactive addresses: #{address}.\n" \ 'Inactive recipients are ones that have generated a hard ' \ 'bounce or a spam complaint.' end it 'parses recipients from json payload' do expect(subject.recipients).to eq([address]) end it 'body is set' do expect(subject.body).to eq(Postmark::Json.encode(response)) end it 'parsed body is set' do expect(subject.parsed_body).to eq(response) end it 'error code is set' do expect(subject.error_code).to eq(Postmark::ApiInputError::INACTIVE_RECIPIENT) end end end describe(Postmark::DeliveryError) do it 'is an alias to Error for backwards compatibility' do expect(subject.class).to eq(Postmark::Error) end end describe(Postmark::InvalidMessageError) do it 'is an alias to Error for backwards compatibility' do expect(subject.class).to eq(Postmark::ApiInputError) end end describe(Postmark::UnknownError) do it 'is an alias for backwards compatibility' do expect(subject.class).to eq(Postmark::UnexpectedHttpResponseError) end end postmark-1.22.0/spec/unit/postmark/client_spec.rb0000644000175100017510000000274314123042137022207 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::Client do subject { Postmark::Client.new('abcd-efgh') } describe 'instance' do describe '#find_each' do let(:path) { 'resources' } let(:name) { 'Resources' } let(:response) { { 'TotalCount' => 10, name => [{'Foo' => 'bar'}, {'Bar' => 'foo'}] } } it 'returns an enumerator' do expect(subject.find_each(path, name)).to be_kind_of(Enumerable) end it 'can be iterated' do collection = [{:foo => 'bar'}, {:bar => 'foo'}].cycle(5) allow(subject.http_client). to receive(:get).with(path, an_instance_of(Hash)). exactly(5).times.and_return(response) expect { |b| subject.find_each(path, name, :count => 2).each(&b) }. to yield_successive_args(*collection) end # Only Ruby >= 2.0.0 supports Enumerator#size it 'lazily calculates the collection size', :skip_ruby_version => ['1.8.7', '1.9'] do allow(subject.http_client). to receive(:get).exactly(1).times.and_return(response) collection = subject.find_each(path, name, :count => 2) expect(collection.size).to eq(10) end it 'iterates over the collection to count it' do allow(subject.http_client). to receive(:get).exactly(5).times.and_return(response) expect(subject.find_each(path, name, :count => 2).count).to eq(10) end end end endpostmark-1.22.0/spec/unit/postmark/bounce_spec.rb0000644000175100017510000001054414123042137022202 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::Bounce do let(:bounce_data) { {:type => "HardBounce", :message_id => "d12c2f1c-60f3-4258-b163-d17052546ae4", :type_code => 1, :description => "The server was unable to deliver your message (ex: unknown user, mailbox not found).", :details => "test bounce", :email => "jim@test.com", :bounced_at => "2013-04-22 18:06:48 +0800", :dump_available => true, :inactive => false, :can_activate => true, :id => 42, :subject => "Hello from our app!"} } let(:bounce_data_postmark) { Postmark::HashHelper.to_postmark(bounce_data) } let(:bounces_data) { [bounce_data, bounce_data, bounce_data] } let(:bounce) { Postmark::Bounce.new(bounce_data) } subject { bounce } context "attr readers" do it { expect(subject).to respond_to(:email) } it { expect(subject).to respond_to(:bounced_at) } it { expect(subject).to respond_to(:type) } it { expect(subject).to respond_to(:description) } it { expect(subject).to respond_to(:details) } it { expect(subject).to respond_to(:name) } it { expect(subject).to respond_to(:id) } it { expect(subject).to respond_to(:server_id) } it { expect(subject).to respond_to(:tag) } it { expect(subject).to respond_to(:message_id) } it { expect(subject).to respond_to(:subject) } end context "given a bounce created from bounce_data" do it 'is not inactive' do expect(subject).not_to be_inactive end it 'allows to activate the bounce' do expect(subject.can_activate?).to be true end it 'has an available dump' do expect(subject.dump_available?).to be true end its(:type) { is_expected.to eq bounce_data[:type] } its(:message_id) { is_expected.to eq bounce_data[:message_id] } its(:description) { is_expected.to eq bounce_data[:description] } its(:details) { is_expected.to eq bounce_data[:details] } its(:email) { is_expected.to eq bounce_data[:email] } its(:bounced_at) { is_expected.to eq Time.parse(bounce_data[:bounced_at]) } its(:id) { is_expected.to eq bounce_data[:id] } its(:subject) { is_expected.to eq bounce_data[:subject] } end context "given a bounce created from bounce_data_postmark" do subject { Postmark::Bounce.new(bounce_data_postmark) } it 'is not inactive' do expect(subject).not_to be_inactive end it 'allows to activate the bounce' do expect(subject.can_activate?).to be true end it 'has an available dump' do expect(subject.dump_available?).to be true end its(:type) { is_expected.to eq bounce_data[:type] } its(:message_id) { is_expected.to eq bounce_data[:message_id] } its(:details) { is_expected.to eq bounce_data[:details] } its(:email) { is_expected.to eq bounce_data[:email] } its(:bounced_at) { is_expected.to eq Time.parse(bounce_data[:bounced_at]) } its(:id) { is_expected.to eq bounce_data[:id] } its(:subject) { is_expected.to eq bounce_data[:subject] } end describe "#dump" do let(:bounce_body) { double } let(:response) { {:body => bounce_body} } let(:api_client) { Postmark.api_client } it "calls #dump_bounce on shared api_client instance" do expect(Postmark.api_client).to receive(:dump_bounce).with(bounce.id) { response } expect(bounce.dump).to eq bounce_body end end describe "#activate" do let(:api_client) { Postmark.api_client } it "calls #activate_bounce on shared api_client instance" do expect(api_client).to receive(:activate_bounce).with(bounce.id) { bounce_data } expect(bounce.activate).to be_a Postmark::Bounce end end describe ".find" do let(:api_client) { Postmark.api_client } it "calls #get_bounce on shared api_client instance" do expect(api_client).to receive(:get_bounce).with(42) { bounce_data } expect(Postmark::Bounce.find(42)).to be_a Postmark::Bounce end end describe ".all" do let(:response) { bounces_data } let(:api_client) { Postmark.api_client } it "calls #get_bounces on shared api_client instance" do expect(api_client).to receive(:get_bounces) { response } expect(Postmark::Bounce.all.count).to eq(3) end end endpostmark-1.22.0/spec/unit/postmark/api_client_spec.rb0000644000175100017510000012766214123042137023050 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::ApiClient do let(:api_client) {Postmark::ApiClient.new(api_token)} let(:api_token) {"provided-api-token"} let(:message_hash) {{:from => "support@postmarkapp.com"}} let(:message) do Mail.new do from "support@postmarkapp.com" delivery_method Mail::Postmark end end let(:templated_message) do Mail.new do from "sheldon@bigbangtheory.com" to "lenard@bigbangtheory.com" template_alias "hello" template_model :name => "Sheldon" end end let(:http_client) {api_client.http_client} shared_examples "retryable" do |response| let(:api_client) do Postmark::ApiClient.new( api_token, max_retries.nil? ? {} : {:max_retries => max_retries} ) end let(:max_retries) {nil} context 'with no retries' do let(:max_retries) {nil} it "doesn't retry failed requests" do expect(http_client).to receive(:post).once.and_raise(Postmark::InternalServerError) expect {subject}.to raise_error(Postmark::InternalServerError) end end context 'with 3 retries' do let(:max_retries) {3} it 'retries 3 times' do expect(http_client).to receive(:post).twice.and_raise(Postmark::InternalServerError) expect(http_client).to receive(:post).and_return(response) expect {subject}.not_to raise_error end it 'gives up after 3 retries' do expect(http_client).to receive(:post).thrice.and_raise(Postmark::InternalServerError) expect {subject}.to raise_error(Postmark::InternalServerError) end it "retries on timeout" do expect(http_client).to receive(:post).and_raise(Postmark::TimeoutError) expect(http_client).to receive(:post).and_return(response) expect {subject}.not_to raise_error end end end describe "attr readers" do it { expect(api_client).to respond_to(:http_client) } it { expect(api_client).to respond_to(:max_retries) } end context "when it's created without options" do let(:api_client) {Postmark::ApiClient.new(api_token)} specify { expect(api_client.max_retries).to eq 0 } end context "when it's created with user options" do let(:api_client) {Postmark::ApiClient.new(api_token, options)} let(:options) { {:max_retries => 42, :foo => :bar} } it "sets max_retries" do expect(api_client.max_retries).to eq 42 end it 'passes other options to HttpClient instance' do expect(Postmark::HttpClient).to receive(:new).with(api_token, :foo => :bar) api_client end end describe "#api_token=" do let(:api_token) {"new-api-token-value"} it 'assigns the api token to the http client instance' do api_client.api_token = api_token expect(api_client.http_client.api_token).to eq api_token end it 'is aliased as api_key=' do api_client.api_key = api_token expect(api_client.http_client.api_token).to eq api_token end end describe "#deliver" do subject {api_client.deliver(message_hash)} let(:email) {Postmark::MessageHelper.to_postmark(message_hash)} let(:email_json) {Postmark::Json.encode(email)} let(:response) {{"MessageID" => 42}} it 'converts message hash to Postmark format and posts it to expected enpoint' do expect(http_client).to receive(:post).with('email', email_json) {response} subject end it 'converts response to ruby format' do expect(http_client).to receive(:post).with('email', email_json) {response} expect(subject).to have_key(:message_id) end it_should_behave_like "retryable" end describe "#deliver_in_batches" do subject {api_client.deliver_in_batches([message_hash, message_hash, message_hash])} let(:email) {Postmark::MessageHelper.to_postmark(message_hash)} let(:emails) {[email, email, email]} let(:emails_json) {Postmark::Json.encode(emails)} let(:response) {[{'ErrorCode' => 0}, {'ErrorCode' => 0}, {'ErrorCode' => 0}]} it 'turns array of messages into a JSON document and posts it to expected endpoint' do expect(http_client).to receive(:post).with('email/batch', emails_json) {response} subject end it 'converts response to ruby format' do expect(http_client).to receive(:post).with('email/batch', emails_json) {response} response = subject expect(response.first).to have_key(:error_code) end it_should_behave_like "retryable" end describe "#deliver_message" do subject {api_client.deliver_message(message)} let(:email) {message.to_postmark_hash} let(:email_json) {Postmark::Json.encode(email)} context 'when given a templated message' do let(:message) {templated_message} specify do expect { subject }.to raise_error(ArgumentError, /Please use Postmark::ApiClient\#deliver_message_with_template/) end end it 'turns message into a JSON document and posts it to /email' do expect(http_client).to receive(:post).with('email', email_json) subject end it "proxies errors" do allow(http_client).to receive(:post).and_raise(Postmark::TimeoutError) expect {subject}.to raise_error(Postmark::TimeoutError) end it_should_behave_like "retryable" end describe "#deliver_message_with_template" do subject {api_client.deliver_message_with_template(templated_message)} let(:email) {templated_message.to_postmark_hash} let(:email_json) {Postmark::Json.encode(email)} context 'when given a non-templated message' do let(:templated_message) {message} specify do expect { subject }.to raise_error(ArgumentError, 'Templated delivery requested, but the template is missing.') end end it 'turns message into a JSON document and posts it to expected endpoint' do expect(http_client).to receive(:post).with('email/withTemplate', email_json) subject end it "proxies errors" do allow(http_client).to receive(:post).and_raise(Postmark::TimeoutError) expect {subject}.to raise_error(Postmark::TimeoutError) end it_should_behave_like "retryable" end describe "#deliver_messages" do subject {api_client.deliver_messages(messages)} let(:messages) {[message, message, message]} let(:email) {message.to_postmark_hash} let(:emails_json) {Postmark::Json.encode(Array.new(3) { email })} let(:response) {[{}, {}, {}]} context 'when given templated messages' do let(:messages) {[templated_message]} specify do expect { subject }.to raise_error(ArgumentError, /Please use Postmark::ApiClient\#deliver_messages_with_templates/) end end it 'turns array of messages into a JSON document and posts it to /email/batch' do expect(http_client).to receive(:post).with('email/batch', emails_json) {response} subject end it_should_behave_like 'retryable', [] end describe "#deliver_messages_with_templates" do subject {api_client.deliver_messages_with_templates(messages)} let(:email) {templated_message.to_postmark_hash} let(:emails_json) {Postmark::Json.encode(:Messages => Array.new(3) { email })} let(:response) {[{}, {}, {}]} let(:messages) { Array.new(3) { templated_message } } context 'when given a non-templated message' do let(:messages) {[message]} it 'raises an error ' do expect { subject }. to raise_error(ArgumentError, 'Templated delivery requested, but one or more messages lack templates.') end end it 'turns array of messages into a JSON document and posts it to expected endpoint' do expect(http_client).to receive(:post).with('email/batchWithTemplates', emails_json) {response} subject end it_should_behave_like 'retryable', [] end describe "#delivery_stats" do let(:response) {{"Bounces" => [{"Foo" => "Bar"}]}} it 'requests data at /deliverystats' do expect(http_client).to receive(:get).with("deliverystats") {response} expect(api_client.delivery_stats).to have_key(:bounces) end end describe '#messages' do context 'given outbound' do let(:response) {{'TotalCount' => 5, 'Messages' => [{}].cycle(5).to_a}} it 'returns an enumerator' do expect(api_client.messages).to be_kind_of(Enumerable) end it 'loads outbound messages' do allow(api_client.http_client).to receive(:get). with('messages/outbound', an_instance_of(Hash)).and_return(response) expect(api_client.messages.count).to eq(5) end end context 'given inbound' do let(:response) {{'TotalCount' => 5, 'InboundMessages' => [{}].cycle(5).to_a}} it 'returns an enumerator' do expect(api_client.messages(:inbound => true)).to be_kind_of(Enumerable) end it 'loads inbound messages' do allow(api_client.http_client).to receive(:get).with('messages/inbound', an_instance_of(Hash)).and_return(response) expect(api_client.messages(:inbound => true).count).to eq(5) end end end describe '#get_messages' do context 'given outbound' do let(:response) {{"TotalCount" => 1, "Messages" => [{}]}} it 'requests data at /messages/outbound' do expect(http_client).to receive(:get). with('messages/outbound', :offset => 50, :count => 50). and_return(response) api_client.get_messages(:offset => 50, :count => 50) end end context 'given inbound' do let(:response) {{"TotalCount" => 1, "InboundMessages" => [{}]}} it 'requests data at /messages/inbound' do expect(http_client).to receive(:get).with('messages/inbound', :offset => 50, :count => 50).and_return(response) expect(api_client.get_messages(:inbound => true, :offset => 50, :count => 50)).to be_an(Array) end end end describe '#get_messages_count' do let(:response) {{'TotalCount' => 42}} context 'given outbound' do it 'requests and returns outbound messages count' do allow(api_client.http_client).to receive(:get). with('messages/outbound', an_instance_of(Hash)).and_return(response) expect(api_client.get_messages_count).to eq(42) expect(api_client.get_messages_count(:inbound => false)).to eq(42) end end context 'given inbound' do it 'requests and returns inbound messages count' do allow(api_client.http_client).to receive(:get). with('messages/inbound', an_instance_of(Hash)).and_return(response) expect(api_client.get_messages_count(:inbound => true)).to eq(42) end end end describe '#get_message' do let(:id) {'8ad0e8b0-xxxx-xxxx-951d-223c581bb467'} let(:response) {{"To" => "leonard@bigbangtheory.com"}} context 'given outbound' do it 'requests a single message by id at /messages/outbound/:id/details' do expect(http_client).to receive(:get). with("messages/outbound/#{id}/details", {}). and_return(response) expect(api_client.get_message(id)).to have_key(:to) end end context 'given inbound' do it 'requests a single message by id at /messages/inbound/:id/details' do expect(http_client).to receive(:get). with("messages/inbound/#{id}/details", {}). and_return(response) expect(api_client.get_message(id, :inbound => true)).to have_key(:to) end end end describe '#dump_message' do let(:id) {'8ad0e8b0-xxxx-xxxx-951d-223c581bb467'} let(:response) {{"Body" => "From: \r\n ..."}} context 'given outbound' do it 'requests a single message by id at /messages/outbound/:id/dump' do expect(http_client).to receive(:get). with("messages/outbound/#{id}/dump", {}). and_return(response) expect(api_client.dump_message(id)).to have_key(:body) end end context 'given inbound' do it 'requests a single message by id at /messages/inbound/:id/dump' do expect(http_client).to receive(:get). with("messages/inbound/#{id}/dump", {}). and_return(response) expect(api_client.dump_message(id, :inbound => true)).to have_key(:body) end end end describe '#bounces' do it 'returns an Enumerator' do expect(api_client.bounces).to be_kind_of(Enumerable) end it 'requests data at /bounces' do allow(api_client.http_client).to receive(:get). with('bounces', an_instance_of(Hash)). and_return('TotalCount' => 1, 'Bounces' => [{}]) expect(api_client.bounces.first(5).count).to eq(1) end end describe "#get_bounces" do let(:options) {{:foo => :bar}} let(:response) {{"Bounces" => []}} it 'requests data at /bounces' do allow(http_client).to receive(:get).with("bounces", options) {response} expect(api_client.get_bounces(options)).to be_an(Array) expect(api_client.get_bounces(options).count).to be_zero end end describe "#get_bounce" do let(:id) {42} it 'requests a single bounce by ID at /bounces/:id' do expect(http_client).to receive(:get).with("bounces/#{id}") api_client.get_bounce(id) end end describe "#dump_bounce" do let(:id) {42} it 'requests a specific bounce data at /bounces/:id/dump' do expect(http_client).to receive(:get).with("bounces/#{id}/dump") api_client.dump_bounce(id) end end describe "#activate_bounce" do let(:id) {42} let(:response) {{"Bounce" => {}}} it 'activates a specific bounce by sending a PUT request to /bounces/:id/activate' do expect(http_client).to receive(:put).with("bounces/#{id}/activate") {response} api_client.activate_bounce(id) end end describe '#opens' do it 'returns an Enumerator' do expect(api_client.opens).to be_kind_of(Enumerable) end it 'performs a GET request to /opens/tags' do allow(api_client.http_client).to receive(:get). with('messages/outbound/opens', an_instance_of(Hash)). and_return('TotalCount' => 1, 'Opens' => [{}]) expect(api_client.opens.first(5).count).to eq(1) end end describe '#clicks' do it 'returns an Enumerator' do expect(api_client.clicks).to be_kind_of(Enumerable) end it 'performs a GET request to /clicks/tags' do allow(api_client.http_client).to receive(:get). with('messages/outbound/clicks', an_instance_of(Hash)). and_return('TotalCount' => 1, 'Clicks' => [{}]) expect(api_client.clicks.first(5).count).to eq(1) end end describe '#get_opens' do let(:options) {{:offset => 5}} let(:response) {{'Opens' => [], 'TotalCount' => 0}} it 'performs a GET request to /messages/outbound/opens' do allow(http_client).to receive(:get).with('messages/outbound/opens', options) {response} expect(api_client.get_opens(options)).to be_an(Array) expect(api_client.get_opens(options).count).to be_zero end end describe '#get_clicks' do let(:options) {{:offset => 5}} let(:response) {{'Clicks' => [], 'TotalCount' => 0}} it 'performs a GET request to /messages/outbound/clicks' do allow(http_client).to receive(:get).with('messages/outbound/clicks', options) {response} expect(api_client.get_clicks(options)).to be_an(Array) expect(api_client.get_clicks(options).count).to be_zero end end describe '#get_opens_by_message_id' do let(:message_id) {42} let(:options) {{:offset => 5}} let(:response) {{'Opens' => [], 'TotalCount' => 0}} it 'performs a GET request to /messages/outbound/opens' do allow(http_client).to receive(:get).with("messages/outbound/opens/#{message_id}", options).and_return(response) expect(api_client.get_opens_by_message_id(message_id, options)).to be_an(Array) expect(api_client.get_opens_by_message_id(message_id, options).count).to be_zero end end describe '#get_clicks_by_message_id' do let(:message_id) {42} let(:options) {{:offset => 5}} let(:response) {{'Clicks' => [], 'TotalCount' => 0}} it 'performs a GET request to /messages/outbound/clicks' do allow(http_client).to receive(:get).with("messages/outbound/clicks/#{message_id}", options).and_return(response) expect(api_client.get_clicks_by_message_id(message_id, options)).to be_an(Array) expect(api_client.get_clicks_by_message_id(message_id, options).count).to be_zero end end describe '#opens_by_message_id' do let(:message_id) {42} it 'returns an Enumerator' do expect(api_client.opens_by_message_id(message_id)).to be_kind_of(Enumerable) end it 'performs a GET request to /opens/tags' do allow(api_client.http_client).to receive(:get). with("messages/outbound/opens/#{message_id}", an_instance_of(Hash)). and_return('TotalCount' => 1, 'Opens' => [{}]) expect(api_client.opens_by_message_id(message_id).first(5).count).to eq(1) end end describe '#clicks_by_message_id' do let(:message_id) {42} it 'returns an Enumerator' do expect(api_client.clicks_by_message_id(message_id)).to be_kind_of(Enumerable) end it 'performs a GET request to /clicks/tags' do allow(api_client.http_client).to receive(:get). with("messages/outbound/clicks/#{message_id}", an_instance_of(Hash)). and_return('TotalCount' => 1, 'Clicks' => [{}]) expect(api_client.clicks_by_message_id(message_id).first(5).count).to eq(1) end end describe '#create_trigger' do context 'inbound rules' do let(:options) {{:rule => 'example.com'}} let(:response) {{'Rule' => 'example.com'}} it 'performs a POST request to /triggers/inboundrules with given options' do allow(http_client).to receive(:post).with('triggers/inboundrules', {'Rule' => 'example.com'}.to_json) api_client.create_trigger(:inbound_rules, options) end it 'symbolizes response keys' do allow(http_client).to receive(:post).and_return(response) expect(api_client.create_trigger(:inbound_rules, options)).to eq(:rule => 'example.com') end end end describe '#get_trigger' do let(:id) {42} it 'performs a GET request to /triggers/tags/:id' do allow(http_client).to receive(:get).with("triggers/tags/#{id}") api_client.get_trigger(:tags, id) end it 'symbolizes response keys' do allow(http_client).to receive(:get).and_return('Foo' => 'Bar') expect(api_client.get_trigger(:tags, id)).to eq(:foo => 'Bar') end end describe '#delete_trigger' do context 'tags' do let(:id) {42} it 'performs a DELETE request to /triggers/tags/:id' do allow(http_client).to receive(:delete).with("triggers/tags/#{id}") api_client.delete_trigger(:tags, id) end it 'symbolizes response keys' do allow(http_client).to receive(:delete).and_return('Foo' => 'Bar') expect(api_client.delete_trigger(:tags, id)).to eq(:foo => 'Bar') end end context 'inbound rules' do let(:id) {42} it 'performs a DELETE request to /triggers/inboundrules/:id' do allow(http_client).to receive(:delete).with("triggers/inboundrules/#{id}") api_client.delete_trigger(:inbound_rules, id) end it 'symbolizes response keys' do allow(http_client).to receive(:delete).and_return('Rule' => 'example.com') expect(api_client.delete_trigger(:tags, id)).to eq(:rule => 'example.com') end end end describe '#get_triggers' do let(:options) {{:offset => 5}} context 'inbound rules' do let(:response) {{'InboundRules' => [], 'TotalCount' => 0}} it 'performs a GET request to /triggers/inboundrules' do allow(http_client).to receive(:get).with('triggers/inboundrules', options) {response} expect(api_client.get_triggers(:inbound_rules, options)).to be_an(Array) expect(api_client.get_triggers(:inbound_rules, options).count).to be_zero end end end describe '#triggers' do it 'returns an Enumerator' do expect(api_client.triggers(:tags)).to be_kind_of(Enumerable) end it 'performs a GET request to /triggers/tags' do allow(api_client.http_client).to receive(:get). with('triggers/tags', an_instance_of(Hash)). and_return('TotalCount' => 1, 'Tags' => [{}]) expect(api_client.triggers(:tags).first(5).count).to eq(1) end end describe "#server_info" do let(:response) { { "Name" => "Testing", "Color" => "blue", "InboundHash" => "c2425d77f74f8643e5f6237438086c81", "SmtpApiActivated" => true } } it 'requests server info from Postmark and converts it to ruby format' do expect(http_client).to receive(:get).with('server') {response} expect(api_client.server_info).to have_key(:inbound_hash) end end describe "#update_server_info" do let(:response) { { "Name" => "Testing", "Color" => "blue", "InboundHash" => "c2425d77f74f8643e5f6237438086c81", "SmtpApiActivated" => false } } let(:update) {{:smtp_api_activated => false}} it 'updates server info in Postmark and converts it to ruby format' do expect(http_client).to receive(:put).with('server', anything) {response} expect(api_client.update_server_info(update)[:smtp_api_activated]).to be false end end describe '#get_templates' do let(:response) do { 'TotalCount' => 31, 'Templates' => [ { 'Active' => true, 'TemplateId' => 123, 'Name' => 'ABC' }, { 'Active' => true, 'TemplateId' => 456, 'Name' => 'DEF' } ] } end it 'gets templates info and converts it to ruby format' do expect(http_client).to receive(:get).with('templates', :offset => 0, :count => 2).and_return(response) count, templates = api_client.get_templates(:count => 2) expect(count).to eq(31) expect(templates.first[:template_id]).to eq(123) expect(templates.first[:name]).to eq('ABC') end end describe '#templates' do it 'returns an Enumerator' do expect(api_client.templates).to be_kind_of(Enumerable) end it 'requests data at /templates' do allow(api_client.http_client).to receive(:get). with('templates', an_instance_of(Hash)). and_return('TotalCount' => 1, 'Templates' => [{}]) expect(api_client.templates.first(5).count).to eq(1) end end describe '#get_template' do let(:response) do { 'Name' => 'Template Name', 'TemplateId' => 123, 'Subject' => 'Subject', 'HtmlBody' => 'Html', 'TextBody' => 'Text', 'AssociatedServerId' => 456, 'Active' => true } end it 'gets single template and converts it to ruby format' do expect(http_client).to receive(:get).with('templates/123').and_return(response) template = api_client.get_template('123') expect(template[:name]).to eq('Template Name') expect(template[:template_id]).to eq(123) expect(template[:html_body]).to eq('Html') end end describe '#create_template' do let(:response) do { 'TemplateId' => 123, 'Name' => 'template name', 'Active' => true } end it 'performs a POST request to /templates with the given attributes' do expect(http_client).to receive(:post). with('templates', json_representation_of('Name' => 'template name')). and_return(response) template = api_client.create_template(:name => 'template name') expect(template[:name]).to eq('template name') expect(template[:template_id]).to eq(123) end end describe '#update_template' do let(:response) do { 'TemplateId' => 123, 'Name' => 'template name', 'Active' => true } end it 'performs a PUT request to /templates with the given attributes' do expect(http_client).to receive(:put). with('templates/123', json_representation_of('Name' => 'template name')). and_return(response) template = api_client.update_template(123, :name => 'template name') expect(template[:name]).to eq('template name') expect(template[:template_id]).to eq(123) end end describe '#delete_template' do let(:response) do { 'ErrorCode' => 0, 'Message' => 'Template 123 removed.' } end it 'performs a DELETE request to /templates/:id' do expect(http_client).to receive(:delete).with('templates/123').and_return(response) resp = api_client.delete_template(123) expect(resp[:error_code]).to eq(0) end end describe '#validate_template' do context 'when template is valid' do let(:response) do { 'AllContentIsValid' => true, 'HtmlBody' => { 'ContentIsValid' => true, 'ValidationErrors' => [], 'RenderedContent' => 'MyName_Value' }, 'TextBody' => { 'ContentIsValid' => true, 'ValidationErrors' => [], 'RenderedContent' => 'MyName_Value' }, 'Subject' => { 'ContentIsValid' => true, 'ValidationErrors' => [], 'RenderedContent' => 'MyName_Value' }, 'SuggestedTemplateModel' => { 'MyName' => 'MyName_Value' } } end it 'performs a POST request and returns unmodified suggested template model' do expect(http_client).to receive(:post). with('templates/validate', json_representation_of('HtmlBody' => '{{MyName}}', 'TextBody' => '{{MyName}}', 'Subject' => '{{MyName}}')). and_return(response) resp = api_client.validate_template(:html_body => '{{MyName}}', :text_body => '{{MyName}}', :subject => '{{MyName}}') expect(resp[:all_content_is_valid]).to be true expect(resp[:html_body][:content_is_valid]).to be true expect(resp[:html_body][:validation_errors]).to be_empty expect(resp[:suggested_template_model]['MyName']).to eq('MyName_Value') end end context 'when template is invalid' do let(:response) do { 'AllContentIsValid' => false, 'HtmlBody' => { 'ContentIsValid' => false, 'ValidationErrors' => [ { 'Message' => 'The \'each\' block being opened requires a model path to be specified in the form \'{#each }\'.', 'Line' => 1, 'CharacterPosition' => 1 } ], 'RenderedContent' => nil }, 'TextBody' => { 'ContentIsValid' => true, 'ValidationErrors' => [], 'RenderedContent' => 'MyName_Value' }, 'Subject' => { 'ContentIsValid' => true, 'ValidationErrors' => [], 'RenderedContent' => 'MyName_Value' }, 'SuggestedTemplateModel' => nil } end it 'performs a POST request and returns validation errors' do expect(http_client). to receive(:post).with('templates/validate', json_representation_of('HtmlBody' => '{{#each}}', 'TextBody' => '{{MyName}}', 'Subject' => '{{MyName}}')).and_return(response) resp = api_client.validate_template(:html_body => '{{#each}}', :text_body => '{{MyName}}', :subject => '{{MyName}}') expect(resp[:all_content_is_valid]).to be false expect(resp[:text_body][:content_is_valid]).to be true expect(resp[:html_body][:content_is_valid]).to be false expect(resp[:html_body][:validation_errors].first[:character_position]).to eq(1) expect(resp[:html_body][:validation_errors].first[:message]).to eq('The \'each\' block being opened requires a model path to be specified in the form \'{#each }\'.') end end end describe "#deliver_with_template" do subject {api_client.deliver_with_template(message_hash)} let(:email) {Postmark::MessageHelper.to_postmark(message_hash)} let(:response) {{"MessageID" => 42}} it 'converts message hash to Postmark format and posts it to /email/withTemplate' do expect(http_client).to receive(:post).with('email/withTemplate', json_representation_of(email)) {response} subject end it 'converts response to ruby format' do expect(http_client).to receive(:post).with('email/withTemplate', json_representation_of(email)) {response} expect(subject).to have_key(:message_id) end it_should_behave_like "retryable" end describe '#deliver_in_batches_with_templates' do let(:max_batch_size) {50} let(:factor) {3.5} let(:postmark_response) do { 'ErrorCode' => 0, 'Message' => 'OK', 'SubmittedAt' => '2018-03-14T09:56:50.4288265-04:00', 'To' => 'recipient@example.org' } end let(:message_hashes) do Array.new((factor * max_batch_size).to_i) do { :template_id => 42, :alias => 'alias', :template_model => {:Foo => 'attr_value'}, :from => 'sender@example.org', :to => 'recipient@example.org' } end end before {api_client.max_batch_size = max_batch_size} it 'performs a total of (bath_size / max_batch_size) requests' do expect(http_client). to receive(:post).with('email/batchWithTemplates', a_postmark_json). at_most(factor.to_i).times do Array.new(max_batch_size) {postmark_response} end expect(http_client). to receive(:post).with('email/batchWithTemplates', a_postmark_json). exactly((factor - factor.to_i).ceil).times do response = Array.new(((factor - factor.to_i) * max_batch_size).to_i) do postmark_response end response end response = api_client.deliver_in_batches_with_templates(message_hashes) expect(response).to be_an Array expect(response.size).to eq message_hashes.size response.each do |message_status| expect(message_status).to have_key(:error_code) expect(message_status).to have_key(:message) expect(message_status).to have_key(:to) expect(message_status).to have_key(:submitted_at) end end end describe '#get_stats_totals' do let(:response) do { "Sent" => 615, "BounceRate" => 10.406, } end it 'converts response to ruby format' do expect(http_client).to receive(:get).with('stats/outbound', {:tag => 'foo'}) {response} response = api_client.get_stats_totals(:tag => 'foo') expect(response).to have_key(:sent) expect(response).to have_key(:bounce_rate) end end describe '#get_stats_counts' do let(:response) do { "Days" => [ { "Date" => "2014-01-01", "Sent" => 140 }, { "Date" => "2014-01-02", "Sent" => 160 }, { "Date" => "2014-01-04", "Sent" => 50 }, { "Date" => "2014-01-05", "Sent" => 115 } ], "Sent" => 615 } end it 'converts response to ruby format' do expect(http_client).to receive(:get).with('stats/outbound/sends', {:tag => 'foo'}) {response} response = api_client.get_stats_counts(:sends, :tag => 'foo') expect(response).to have_key(:days) expect(response).to have_key(:sent) first_day = response[:days].first expect(first_day).to have_key(:date) expect(first_day).to have_key(:sent) end it 'uses fromdate that is passed in' do expect(http_client).to receive(:get).with('stats/outbound/sends', {:tag => 'foo', :fromdate => '2015-01-01'}) {response} response = api_client.get_stats_counts(:sends, :tag => 'foo', :fromdate => '2015-01-01') expect(response).to have_key(:days) expect(response).to have_key(:sent) first_day = response[:days].first expect(first_day).to have_key(:date) expect(first_day).to have_key(:sent) end it 'uses stats type that is passed in' do expect(http_client).to receive(:get).with('stats/outbound/opens/readtimes', {:tag => 'foo', :type => :readtimes}) {response} response = api_client.get_stats_counts(:opens, :type => :readtimes, :tag => 'foo') expect(response).to have_key(:days) expect(response).to have_key(:sent) first_day = response[:days].first expect(first_day).to have_key(:date) expect(first_day).to have_key(:sent) end end describe '#get_message_streams' do subject(:result) { api_client.get_message_streams(:offset => 22, :count => 33) } before do allow(http_client).to receive(:get). with('message-streams', :offset => 22, :count => 33). and_return({ 'TotalCount' => 1, 'MessageStreams' => [{'Name' => 'abc'}]}) end it { is_expected.to be_an(Array) } describe 'returned item' do subject { result.first } it { is_expected.to match(:name => 'abc') } end end describe '#message_streams' do subject { api_client.message_streams } it { is_expected.to be_kind_of(Enumerable) } it 'requests data at /message-streams' do allow(http_client).to receive(:get). with('message-streams', anything). and_return('TotalCount' => 1, 'MessageStreams' => [{}]) expect(subject.first(5).count).to eq(1) end end describe '#get_message_stream' do subject(:result) { api_client.get_message_stream(123) } before do allow(http_client).to receive(:get). with('message-streams/123'). and_return({ 'Id' => 'xxx', 'Name' => 'My Stream', 'ServerID' => 321, 'MessageStreamType' => 'Transactional' }) end it { is_expected.to match( :id => 'xxx', :name => 'My Stream', :server_id => 321, :message_stream_type => 'Transactional' ) } end describe '#create_message_stream' do subject { api_client.create_message_stream(attrs) } let(:attrs) do { :name => 'My Stream', :id => 'my-stream', :message_stream_type => 'Broadcasts', :subscription_management_configuration => { :unsubscribe_handling_type => 'Custom' } } end let(:response) do { 'Name' => 'My Stream', 'Id' => 'my-stream', 'MessageStreamType' => 'Broadcasts', 'ServerId' => 222, 'CreatedAt' => '2020-04-01T03:33:33.333-03:00', 'SubscriptionManagementConfiguration' => { 'UnsubscribeHandlingType' => 'Custom' } } end before do allow(http_client).to receive(:post) { response } end specify do expect(http_client).to receive(:post). with('message-streams', json_representation_of({ 'Name' => 'My Stream', 'Id' => 'my-stream', 'MessageStreamType' => 'Broadcasts', 'SubscriptionManagementConfiguration' => { 'UnsubscribeHandlingType' => 'Custom' } })) subject end it { is_expected.to match( :id => 'my-stream', :name => 'My Stream', :server_id => 222, :message_stream_type => 'Broadcasts', :created_at => '2020-04-01T03:33:33.333-03:00', :subscription_management_configuration => { :unsubscribe_handling_type => 'Custom' } ) } end describe '#update_message_stream' do subject { api_client.update_message_stream('xxx', attrs) } let(:attrs) do { :name => 'My Stream XXX', :subscription_management_configuration => { :unsubscribe_handling_type => 'Custom' } } end let(:response) do { 'Name' => 'My Stream XXX', 'Id' => 'xxx', 'MessageStreamType' => 'Broadcasts', 'ServerId' => 222, 'CreatedAt' => '2020-04-01T03:33:33.333-03:00', 'SubscriptionManagementConfiguration' => { 'UnsubscribeHandlingType' => 'Custom' } } end before do allow(http_client).to receive(:patch) { response } end specify do expect(http_client).to receive(:patch). with('message-streams/xxx', match_json({ 'Name' => 'My Stream XXX', 'SubscriptionManagementConfiguration' => { 'UnsubscribeHandlingType' => 'Custom' } })) subject end it { is_expected.to match( :id => 'xxx', :name => 'My Stream XXX', :server_id => 222, :message_stream_type => 'Broadcasts', :created_at => '2020-04-01T03:33:33.333-03:00', :subscription_management_configuration => { :unsubscribe_handling_type => 'Custom' } ) } end describe '#archive_message_stream' do subject { api_client.archive_message_stream(stream_id) } let(:stream_id) { 'my-stream'} let(:server_id) { 123 } let(:purge_date) { '2030-08-30T12:30:00.00-04:00' } let(:api_endpoint) { "message-streams/#{stream_id}/archive" } let(:api_response) {{ 'ID' => stream_id, 'ServerID' => server_id, 'ExpectedPurgeDate' => purge_date }} before do allow(http_client).to receive(:post).with(api_endpoint) { api_response } end it 'calls the API endpoint' do expect(http_client).to receive(:post).with(api_endpoint) subject end it 'transforms the API response' do expect(subject).to eq({ :id => stream_id, :server_id => server_id, :expected_purge_date => purge_date }) end end describe '#unarchive_message_stream' do subject { api_client.unarchive_message_stream(stream_id) } let(:stream_id) { 'my-stream'} let(:server_id) { 123 } let(:api_endpoint) { "message-streams/#{stream_id}/unarchive" } let(:api_response) { { 'ID' => stream_id, 'ServerID' => server_id, 'Name' => 'My Stream', 'Description' => 'My test stream.', 'MessageStreamType' => 'Transactional', 'CreatedAt' => '2030-08-30T12:30:00.00-04:00', 'UpdatedAt' => '2030-09-30T12:30:00.00-04:00', 'ArchivedAt'=> nil, 'ExpectedPurgeDate' => nil, 'SubscriptionManagementConfiguration' => { 'UnsubscribeHandlingType' => 'None' } } } before do allow(http_client).to receive(:post).with(api_endpoint) { api_response } end it 'calls the API endpoint' do expect(http_client).to receive(:post).with(api_endpoint) subject end it 'transforms the API response' do expect(subject).to eq({ :id => stream_id, :server_id => server_id, :name => 'My Stream', :description => 'My test stream.', :message_stream_type => 'Transactional', :created_at => '2030-08-30T12:30:00.00-04:00', :updated_at => '2030-09-30T12:30:00.00-04:00', :archived_at => nil , :expected_purge_date => nil , :subscription_management_configuration => { 'UnsubscribeHandlingType' => 'None' }}) end end describe '#create_suppressions' do let(:email_addresses) { nil } let(:message_stream_id) { 'outbound' } subject { api_client.create_suppressions(message_stream_id, email_addresses) } context '1 email address as string' do let(:email_addresses) { 'A@example.com' } specify do expect(http_client).to receive(:post). with('message-streams/outbound/suppressions', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' } ]})) subject end end context '1 email address as string & non-default stream' do let(:email_addresses) { 'A@example.com' } let(:message_stream_id) { 'xxxx' } specify do expect(http_client).to receive(:post). with('message-streams/xxxx/suppressions', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' } ]})) subject end end context '1 email address as array of strings' do let(:email_addresses) { ['A@example.com'] } specify do expect(http_client).to receive(:post). with('message-streams/outbound/suppressions', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' } ]})) subject end end context 'many email addresses as array of strings' do let(:email_addresses) { ['A@example.com', 'B@example.com'] } specify do expect(http_client).to receive(:post). with('message-streams/outbound/suppressions', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' }, { :EmailAddress => 'B@example.com' } ]})) subject end end end describe '#delete_suppressions' do let(:email_addresses) { nil } let(:message_stream_id) { 'outbound' } subject { api_client.delete_suppressions(message_stream_id, email_addresses) } context '1 email address as string' do let(:email_addresses) { 'A@example.com' } specify do expect(http_client).to receive(:post). with('message-streams/outbound/suppressions/delete', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' }, ]})) subject end end context '1 email address as string & non-default stream' do let(:email_addresses) { 'A@example.com' } let(:message_stream_id) { 'xxxx' } specify do expect(http_client).to receive(:post). with('message-streams/xxxx/suppressions/delete', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' } ]})) subject end end context '1 email address as array of strings' do let(:email_addresses) { ['A@example.com'] } specify do expect(http_client).to receive(:post). with('message-streams/outbound/suppressions/delete', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' } ]})) subject end end context 'many email addresses as array of strings' do let(:email_addresses) { ['A@example.com', 'B@example.com'] } specify do expect(http_client).to receive(:post). with('message-streams/outbound/suppressions/delete', match_json({ :Suppressions => [ { :EmailAddress => 'A@example.com' }, { :EmailAddress => 'B@example.com' } ]})) subject end end end describe '#dump_suppressions' do let(:message_stream_id) { 'xxxx' } subject { api_client.dump_suppressions(message_stream_id, :count => 123) } before do allow(http_client).to receive(:get).and_return({'TotalCount' => 0, 'Suppressions' => []}) end specify do expect(http_client).to receive(:get). with('message-streams/xxxx/suppressions/dump', { :count => 123, :offset => 0 }) subject end end end postmark-1.22.0/spec/unit/postmark/account_api_client_spec.rb0000644000175100017510000006042514123042137024555 0ustar vivekdebvivekdebrequire 'spec_helper' describe Postmark::AccountApiClient do let(:api_token) { 'abcd-efgh' } subject { Postmark::AccountApiClient} it 'can be created with an API token' do expect { subject.new(api_token) }.not_to raise_error end it 'can be created with an API token and options hash' do expect { subject.new(api_token, :http_read_timeout => 5) }.not_to raise_error end context 'instance' do subject { Postmark::AccountApiClient.new(api_token) } it 'uses the auth header specific for Account API' do auth_header = subject.http_client.auth_header_name expect(auth_header).to eq('X-Postmark-Account-Token') end describe '#senders' do let(:response) { { 'TotalCount' => 10, 'SenderSignatures' => [{}, {}] } } it 'is aliased as #signatures' do expect(subject).to respond_to(:signatures) expect(subject).to respond_to(:signatures).with(1).argument end it 'returns an enumerator' do expect(subject.senders).to be_kind_of(Enumerable) end it 'lazily loads senders' do allow(subject.http_client).to receive(:get). with('senders', an_instance_of(Hash)).and_return(response) subject.senders.take(1000) end end describe '#get_senders' do let(:response) { { "TotalCount" => 1, "SenderSignatures" => [{ "Domain" => "example.com", "EmailAddress" => "someone@example.com", "ReplyToEmailAddress" => "info@example.com", "Name" => "Example User", "Confirmed" => true, "ID" => 8139 }] } } it 'is aliased as #get_signatures' do expect(subject).to respond_to(:get_signatures).with(1).argument end it 'performs a GET request to /senders endpoint' do allow(subject.http_client).to receive(:get). with('senders', :offset => 0, :count => 30). and_return(response) subject.get_senders end it 'formats the keys of returned list of senders' do allow(subject.http_client).to receive(:get).and_return(response) keys = subject.get_senders.map { |s| s.keys }.flatten expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end it 'accepts offset and count options' do allow(subject.http_client).to receive(:get). with('senders', :offset => 10, :count => 42). and_return(response) subject.get_senders(:offset => 10, :count => 42) end end describe '#get_senders_count' do let(:response) { {'TotalCount' => 42} } it 'is aliased as #get_signatures_count' do expect(subject).to respond_to(:get_signatures_count) expect(subject).to respond_to(:get_signatures_count).with(1).argument end it 'returns a total number of senders' do allow(subject.http_client).to receive(:get). with('senders', an_instance_of(Hash)).and_return(response) expect(subject.get_senders_count).to eq(42) end end describe '#get_sender' do let(:response) { { "Domain" => "example.com", "EmailAddress" => "someone@example.com", "ReplyToEmailAddress" => "info@example.com", "Name" => "Example User", "Confirmed" => true, "ID" => 8139 } } it 'is aliased as #get_signature' do expect(subject).to respond_to(:get_signature).with(1).argument end it 'performs a GET request to /senders/:id endpoint' do allow(subject.http_client).to receive(:get).with("senders/42"). and_return(response) subject.get_sender(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:get).and_return(response) keys = subject.get_sender(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#create_sender' do let(:response) { { "Domain" => "example.com", "EmailAddress" => "someone@example.com", "ReplyToEmailAddress" => "info@example.com", "Name" => "Example User", "Confirmed" => true, "ID" => 8139 } } it 'is aliased as #create_signature' do expect(subject).to respond_to(:create_signature).with(1).argument end it 'performs a POST request to /senders endpoint' do allow(subject.http_client).to receive(:post). with("senders", an_instance_of(String)).and_return(response) subject.create_sender(:name => 'Chris Nagele') end it 'converts the sender attributes names to camel case' do allow(subject.http_client).to receive(:post). with("senders", {'FooBar' => 'bar'}.to_json).and_return(response) subject.create_sender(:foo_bar => 'bar') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:post).and_return(response) keys = subject.create_sender(:foo => 'bar').keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#update_sender' do let(:response) { { "Domain" => "example.com", "EmailAddress" => "someone@example.com", "ReplyToEmailAddress" => "info@example.com", "Name" => "Example User", "Confirmed" => true, "ID" => 8139 } } it 'is aliased as #update_signature' do expect(subject).to respond_to(:update_signature).with(1).argument expect(subject).to respond_to(:update_signature).with(2).arguments end it 'performs a PUT request to /senders/:id endpoint' do allow(subject.http_client).to receive(:put). with('senders/42', an_instance_of(String)).and_return(response) subject.update_sender(42, :name => 'Chris Nagele') end it 'converts the sender attributes names to camel case' do allow(subject.http_client).to receive(:put). with('senders/42', {'FooBar' => 'bar'}.to_json).and_return(response) subject.update_sender(42, :foo_bar => 'bar') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:put).and_return(response) keys = subject.update_sender(42, :foo => 'bar').keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#resend_sender_confirmation' do let(:response) { { "ErrorCode" => 0, "Message" => "Confirmation email for Sender Signature someone@example.com was re-sent." } } it 'is aliased as #resend_signature_confirmation' do expect(subject).to respond_to(:resend_signature_confirmation). with(1).argument end it 'performs a POST request to /senders/:id/resend endpoint' do allow(subject.http_client).to receive(:post). with('senders/42/resend').and_return(response) subject.resend_sender_confirmation(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:post).and_return(response) keys = subject.resend_sender_confirmation(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#verified_sender_spf?' do let(:response) { {"SPFVerified" => true} } let(:false_response) { {"SPFVerified" => false} } it 'is aliased as #verified_signature_spf?' do expect(subject).to respond_to(:verified_signature_spf?).with(1).argument end it 'performs a POST request to /senders/:id/verifyspf endpoint' do allow(subject.http_client).to receive(:post). with('senders/42/verifyspf').and_return(response) subject.verified_sender_spf?(42) end it 'returns false when SPFVerified field of the response is false' do allow(subject.http_client).to receive(:post).and_return(false_response) expect(subject.verified_sender_spf?(42)).to be false end it 'returns true when SPFVerified field of the response is true' do allow(subject.http_client).to receive(:post).and_return(response) expect(subject.verified_sender_spf?(42)).to be true end end describe '#request_new_sender_dkim' do let(:response) { { "Domain" => "example.com", "EmailAddress" => "someone@example.com", "ReplyToEmailAddress" => "info@example.com", "Name" => "Example User", "Confirmed" => true, "ID" => 8139 } } it 'is aliased as #request_new_signature_dkim' do expect(subject).to respond_to(:request_new_signature_dkim). with(1).argument end it 'performs a POST request to /senders/:id/requestnewdkim endpoint' do allow(subject.http_client).to receive(:post). with('senders/42/requestnewdkim').and_return(response) subject.request_new_sender_dkim(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:post).and_return(response) keys = subject.request_new_sender_dkim(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#delete_sender' do let(:response) { { "ErrorCode" => 0, "Message" => "Signature someone@example.com removed." } } it 'is aliased as #delete_signature' do expect(subject).to respond_to(:delete_signature).with(1).argument end it 'performs a DELETE request to /senders/:id endpoint' do allow(subject.http_client).to receive(:delete). with('senders/42').and_return(response) subject.delete_sender(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:delete).and_return(response) keys = subject.delete_sender(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#domains' do let(:response) {{'TotalCount' => 10, 'Domains' => [{}, {}]}} it 'returns an enumerator' do expect(subject.domains).to be_kind_of(Enumerable) end it 'lazily loads domains' do allow(subject.http_client).to receive(:get). with('domains', an_instance_of(Hash)).and_return(response) subject.domains.take(1000) end end describe '#get_domains' do let(:response) { { "TotalCount" => 1, "Domains" => [{ "Name" => "example.com", "ID" => 8139 }] } } it 'performs a GET request to /domains endpoint' do allow(subject.http_client).to receive(:get). with('domains', :offset => 0, :count => 30). and_return(response) subject.get_domains end it 'formats the keys of returned list of domains' do allow(subject.http_client).to receive(:get).and_return(response) keys = subject.get_domains.map { |s| s.keys }.flatten expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end it 'accepts offset and count options' do allow(subject.http_client).to receive(:get). with('domains', :offset => 10, :count => 42). and_return(response) subject.get_domains(:offset => 10, :count => 42) end end describe '#get_domains_count' do let(:response) { {'TotalCount' => 42} } it 'returns a total number of domains' do allow(subject.http_client).to receive(:get). with('domains', an_instance_of(Hash)).and_return(response) expect(subject.get_domains_count).to eq(42) end end describe '#get_domain' do let(:response) { { "Name" => "example.com", "ID" => 8139 } } it 'performs a GET request to /domains/:id endpoint' do allow(subject.http_client).to receive(:get).with("domains/42"). and_return(response) subject.get_domain(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:get).and_return(response) keys = subject.get_domain(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#create_domain' do let(:response) { { "Name" => "example.com", "ID" => 8139 } } it 'performs a POST request to /domains endpoint' do allow(subject.http_client).to receive(:post). with("domains", an_instance_of(String)).and_return(response) subject.create_domain(:name => 'example.com') end it 'converts the domain attributes names to camel case' do allow(subject.http_client).to receive(:post). with("domains", {'FooBar' => 'bar'}.to_json).and_return(response) subject.create_domain(:foo_bar => 'bar') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:post).and_return(response) keys = subject.create_domain(:foo => 'bar').keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#update_domain' do let(:response) { { "Name" => "example.com", "ReturnPathDomain" => "return.example.com", "ID" => 8139 } } it 'performs a PUT request to /domains/:id endpoint' do allow(subject.http_client).to receive(:put). with('domains/42', an_instance_of(String)).and_return(response) subject.update_domain(42, :return_path_domain => 'updated-return.example.com') end it 'converts the domain attributes names to camel case' do allow(subject.http_client).to receive(:put). with('domains/42', {'FooBar' => 'bar'}.to_json).and_return(response) subject.update_domain(42, :foo_bar => 'bar') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:put).and_return(response) keys = subject.update_domain(42, :foo => 'bar').keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#verified_domain_spf?' do let(:response) { {"SPFVerified" => true} } let(:false_response) { {"SPFVerified" => false} } it 'performs a POST request to /domains/:id/verifyspf endpoint' do allow(subject.http_client).to receive(:post). with('domains/42/verifyspf').and_return(response) subject.verified_domain_spf?(42) end it 'returns false when SPFVerified field of the response is false' do allow(subject.http_client).to receive(:post).and_return(false_response) expect(subject.verified_domain_spf?(42)).to be false end it 'returns true when SPFVerified field of the response is true' do allow(subject.http_client).to receive(:post).and_return(response) expect(subject.verified_domain_spf?(42)).to be true end end describe '#rotate_domain_dkim' do let(:response) { { "Name" => "example.com", "ID" => 8139 } } it 'performs a POST request to /domains/:id/rotatedkim endpoint' do allow(subject.http_client).to receive(:post). with('domains/42/rotatedkim').and_return(response) subject.rotate_domain_dkim(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:post).and_return(response) keys = subject.rotate_domain_dkim(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#delete_domain' do let(:response) { { "ErrorCode" => 0, "Message" => "Domain example.com removed." } } it 'performs a DELETE request to /domains/:id endpoint' do allow(subject.http_client).to receive(:delete). with('domains/42').and_return(response) subject.delete_domain(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:delete).and_return(response) keys = subject.delete_sender(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#servers' do let(:response) { {'TotalCount' => 10, 'Servers' => [{}, {}]} } it 'returns an enumerator' do expect(subject.servers).to be_kind_of(Enumerable) end it 'lazily loads servers' do allow(subject.http_client).to receive(:get). with('servers', an_instance_of(Hash)).and_return(response) subject.servers.take(100) end end describe '#get_servers' do let(:response) { { 'TotalCount' => 1, 'Servers' => [ { "ID" => 11635, "Name" => "Production01", "ApiTokens" => [ "fe6ec0cf-ff06-787a-b5e9-e77a41c61ce3" ], "ServerLink" => "https://postmarkapp.com/servers/11635/overview", "Color" => "red", "SmtpApiActivated" => true, "RawEmailEnabled" => false, "InboundAddress" => "7373de3ebd66acea228fjkdkf88dd7d5@inbound.postmarkapp.com", "InboundHookUrl" => "http://inboundhook.example.com/inbound", "BounceHookUrl" => "http://bouncehook.example.com/bounce", "InboundDomain" => "", "InboundHash" => "7373de3ebd66acea228fjkdkf88dd7d5" } ] } } it 'performs a GET request to /servers endpoint' do allow(subject.http_client).to receive(:get). with('servers', an_instance_of(Hash)).and_return(response) subject.get_servers end it 'formats the keys of returned list of servers' do allow(subject.http_client).to receive(:get).and_return(response) keys = subject.get_servers.map { |s| s.keys }.flatten expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end it 'accepts offset and count options' do allow(subject.http_client).to receive(:get). with('servers', :offset => 30, :count => 50). and_return(response) subject.get_servers(:offset => 30, :count => 50) end end describe '#get_server' do let(:response) { { "ID" => 7438, "Name" => "Staging Testing", "ApiTokens" => [ "fe6ec0cf-ff06-44aa-jf88-e77a41c61ce3" ], "ServerLink" => "https://postmarkapp.com/servers/7438/overview", "Color" => "red", "SmtpApiActivated" => true, "RawEmailEnabled" => false, "InboundAddress" => "7373de3ebd66acea22812731fb1dd7d5@inbound.postmarkapp.com", "InboundHookUrl" => "", "BounceHookUrl" => "", "InboundDomain" => "", "InboundHash" => "7373de3ebd66acea22812731fb1dd7d5" } } it 'performs a GET request to /servers/:id endpoint' do allow(subject.http_client).to receive(:get). with('servers/42').and_return(response) subject.get_server(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:get).and_return(response) keys = subject.get_server(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#get_servers_count' do let(:response) { {'TotalCount' => 42} } it 'returns a total number of servers' do allow(subject.http_client).to receive(:get). with('servers', an_instance_of(Hash)).and_return(response) expect(subject.get_servers_count).to eq(42) end end describe '#create_server' do let(:response) { { "Name" => "Staging Testing", "Color" => "red", "SmtpApiActivated" => true, "RawEmailEnabled" => false, "InboundHookUrl" => "http://hooks.example.com/inbound", "BounceHookUrl" => "http://hooks.example.com/bounce", "InboundDomain" => "" } } it 'performs a POST request to /servers endpoint' do allow(subject.http_client).to receive(:post). with('servers', an_instance_of(String)).and_return(response) subject.create_server(:foo => 'bar') end it 'converts the server attribute names to camel case' do allow(subject.http_client).to receive(:post). with('servers', {'FooBar' => 'foo_bar'}.to_json). and_return(response) subject.create_server(:foo_bar => 'foo_bar') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:post).and_return(response) keys = subject.create_server(:foo => 'bar').keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#update_server' do let(:response) { { "ID" => 7450, "Name" => "Production Testing", "ApiTokens" => [ "fe6ec0cf-ff06-44aa-jf88-e77a41c61ce3" ], "ServerLink" => "https://postmarkapp.com/servers/7438/overview", "Color" => "blue", "SmtpApiActivated" => false, "RawEmailEnabled" => false, "InboundAddress" => "7373de3ebd66acea22812731fb1dd7d5@inbound.postmarkapp.com", "InboundHookUrl" => "http://hooks.example.com/inbound", "BounceHookUrl" => "http://hooks.example.com/bounce", "InboundDomain" => "", "InboundHash" => "7373de3ebd66acea22812731fb1dd7d5" } } it 'converts the server attribute names to camel case' do allow(subject.http_client).to receive(:put). with(an_instance_of(String), {'FooBar' => 'foo_bar'}.to_json). and_return(response) subject.update_server(42, :foo_bar => 'foo_bar') end it 'performs a PUT request to /servers/:id endpoint' do allow(subject.http_client).to receive(:put). with('servers/42', an_instance_of(String)). and_return(response) subject.update_server(42, :foo => 'bar') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:put).and_return(response) keys = subject.update_server(42, :foo => 'bar').keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#delete_server' do let(:response) { { "ErrorCode" => "0", "Message" => "Server Production Testing removed." } } it 'performs a DELETE request to /servers/:id endpoint' do allow(subject.http_client).to receive(:delete). with('servers/42').and_return(response) subject.delete_server(42) end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:delete).and_return(response) keys = subject.delete_server(42).keys expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end describe '#push_templates' do let(:response) { {"TotalCount"=>5, "Templates"=> [{"Action"=>"Create", "TemplateId"=>nil, "Alias"=>"alias1", "Name"=>"Comment notification"}, {"Action"=>"Create", "TemplateId"=>nil, "Alias"=>"alias2", "Name"=>"Password reset"}]} } let(:request_data) {{:source_server_id => 1, :destination_server_id => 2, :perform_changes => false}} it 'gets templates info and converts it to ruby format' do allow(subject.http_client).to receive(:put).and_return(response) templates = subject.push_templates({:source_server_id => 1, :destination_server_id => 2, :perform_changes => false} ) expect(templates.size).to eq(2) expect(templates.first[:action]).to eq('Create') expect(templates.first[:alias]).to eq('alias1') end it 'formats the keys of returned response' do allow(subject.http_client).to receive(:put).and_return(response) templates = subject.push_templates({:source_server_id => 1, :destination_server_id => 2, :perform_changes => false} ) keys = templates.map { |template| template.keys }.flatten expect(keys.all? { |k| k.is_a?(Symbol) }).to be true end end end end postmark-1.22.0/spec/support/0000755000175100017510000000000014123042137016261 5ustar vivekdebvivekdebpostmark-1.22.0/spec/support/shared_examples.rb0000644000175100017510000000403514123042137021754 0ustar vivekdebvivekdebshared_examples :mail do it "set text body for plain message" do expect(Postmark.send(:convert_message_to_options_hash, subject)['TextBody']).not_to be_nil end it "encode from properly when name is used" do subject.from = "Sheldon Lee Cooper " expect(subject).to be_serialized_to %q[{"Subject":"Hello!", "From":"Sheldon Lee Cooper ", "To":"lenard@bigbangtheory.com", "TextBody":"Hello Sheldon!"}] end it "encode reply to" do subject.reply_to = ['a@a.com', 'b@b.com'] expect(subject).to be_serialized_to %q[{"Subject":"Hello!", "From":"sheldon@bigbangtheory.com", "ReplyTo":"a@a.com, b@b.com", "To":"lenard@bigbangtheory.com", "TextBody":"Hello Sheldon!"}] end it "encode tag" do subject.tag = "invite" expect(subject).to be_serialized_to %q[{"Subject":"Hello!", "From":"sheldon@bigbangtheory.com", "Tag":"invite", "To":"lenard@bigbangtheory.com", "TextBody":"Hello Sheldon!"}] end it "encode multiple recepients (TO)" do subject.to = ['a@a.com', 'b@b.com'] expect(subject).to be_serialized_to %q[{"Subject":"Hello!", "From":"sheldon@bigbangtheory.com", "To":"a@a.com, b@b.com", "TextBody":"Hello Sheldon!"}] end it "encode multiple recepients (CC)" do subject.cc = ['a@a.com', 'b@b.com'] expect(subject).to be_serialized_to %q[{"Cc":"a@a.com, b@b.com", "Subject":"Hello!", "From":"sheldon@bigbangtheory.com", "To":"lenard@bigbangtheory.com", "TextBody":"Hello Sheldon!"}] end it "encode multiple recepients (BCC)" do subject.bcc = ['a@a.com', 'b@b.com'] expect(subject).to be_serialized_to %q[{"Bcc":"a@a.com, b@b.com", "Subject":"Hello!", "From":"sheldon@bigbangtheory.com", "To":"lenard@bigbangtheory.com", "TextBody":"Hello Sheldon!"}] end it "accept string as reply_to field" do subject.reply_to = ['Anton Astashov '] expect(subject).to be_serialized_to %q[{"From": "sheldon@bigbangtheory.com", "ReplyTo": "b@b.com", "To": "lenard@bigbangtheory.com", "Subject": "Hello!", "TextBody": "Hello Sheldon!"}] end end postmark-1.22.0/spec/support/helpers.rb0000644000175100017510000000040314123042137020245 0ustar vivekdebvivekdebmodule Postmark module RSpecHelpers def empty_gif_path File.join(File.dirname(__FILE__), '..', 'data', 'empty.gif') end def encoded_empty_gif_data Postmark::MessageHelper.encode_in_base64(File.read(empty_gif_path)) end end endpostmark-1.22.0/spec/support/custom_matchers.rb0000644000175100017510000000200514123042137022003 0ustar vivekdebvivekdebRSpec::Matchers.define :a_postmark_json do |string| def postmark_key?(key) key == ::Postmark::Inflector.to_postmark(key) end def postmark_object?(obj) case obj when Hash return false unless obj.keys.all? { |k| postmark_key?(k) } return false unless obj.values.all? { |v| postmark_object?(v) } when Array return false unless obj.all? { |v| postmark_object?(v) } end true end def postmark_json?(str) return false unless str.is_a?(String) json = Postmark::Json.decode(str) postmark_object?(json) rescue false end match do |actual| postmark_json?(actual) end end RSpec::Matchers.define :json_representation_of do |x| match { |actual| Postmark::Json.decode(actual) == x } end RSpec::Matchers.define :match_json do |x| match { |actual| Postmark::Json.encode(x) == actual } end RSpec::Matchers.define :be_serialized_to do |json| match do |mail_message| Postmark.convert_message_to_options_hash(mail_message) == JSON.parse(json) end end postmark-1.22.0/spec/spec_helper.rb0000644000175100017510000000331114123042137017361 0ustar vivekdebvivekdeb$LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'rubygems' require 'bundler' Bundler.setup(:development) require 'mail' require 'postmark' require 'active_support' require 'json' require 'fakeweb' require 'fakeweb_matcher' require 'rspec' require 'rspec/its' require File.join(File.expand_path(File.dirname(__FILE__)), 'support', 'shared_examples.rb') require File.join(File.expand_path(File.dirname(__FILE__)), 'support', 'custom_matchers.rb') require File.join(File.expand_path(File.dirname(__FILE__)), 'support', 'helpers.rb') if ENV['JSONGEM'] # `JSONGEM=Yajl rake spec` Postmark.response_parser_class = ENV['JSONGEM'].to_sym puts "Setting ResponseParser class to #{Postmark::ResponseParsers.const_get Postmark.response_parser_class}" end RSpec.configure do |config| include Postmark::RSpecHelpers config.expect_with(:rspec) { |c| c.syntax = :expect } config.filter_run_excluding :skip_for_platform => lambda { |platform| RUBY_PLATFORM.to_s =~ /^#{platform.to_s}/ } config.filter_run_excluding :skip_ruby_version => lambda { |version| versions = [*version] versions.any? { |v| RUBY_VERSION.to_s =~ /^#{v.to_s}/ } } config.filter_run_excluding :exclusive_for_ruby_version => lambda { |version| versions = [*version] versions.all? { |v| !(RUBY_VERSION.to_s =~ /^#{v.to_s}/) } } config.before(:each) do %w(api_client response_parser_class secure api_token proxy_host proxy_port proxy_user proxy_pass host port path_prefix http_open_timeout http_read_timeout max_retries).each do |var| Postmark.instance_variable_set(:"@#{var}", nil) end Postmark.response_parser_class = nil end end postmark-1.22.0/spec/integration/0000755000175100017510000000000014123042137017070 5ustar vivekdebvivekdebpostmark-1.22.0/spec/integration/mail_delivery_method_spec.rb0000644000175100017510000000546514123042137024626 0ustar vivekdebvivekdebrequire 'spec_helper' describe "Sending Mail::Messages with delivery_method Mail::Postmark" do let(:postmark_message_id_format) { /\w{8}\-\w{4}-\w{4}-\w{4}-\w{12}/ } let(:message) { Mail.new do from "sender@postmarkapp.com" to "recipient@postmarkapp.com" subject "Mail::Message object" body "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " "eiusmod tempor incididunt ut labore et dolore magna aliqua." delivery_method Mail::Postmark, :api_token => "POSTMARK_API_TEST", :http_open_timeout => 15, :http_read_timeout => 15 end } let(:tagged_message) { message.tap { |m| m.tag "postmark-gem" } } let(:message_with_no_body) { Mail.new do from "sender@postmarkapp.com" to "recipient@postmarkapp.com" delivery_method Mail::Postmark, :api_token => "POSTMARK_API_TEST", :http_open_timeout => 15, :http_read_timeout => 15 end } let(:message_with_attachment) { message.tap do |msg| msg.attachments["test.gif"] = File.read(File.join(File.dirname(__FILE__), '..', 'data', 'empty.gif')) end } let(:message_with_invalid_to) { Mail.new do from "sender@postmarkapp.com" to "@postmarkapp.com" delivery_method Mail::Postmark, :api_token => "POSTMARK_API_TEST", :http_open_timeout => 15, :http_read_timeout => 15 end } it 'delivers a plain text message' do expect { message.deliver }.to change{message.delivered?}.to(true) end it 'updates a message object with X-PM-Message-Id' do expect { message.deliver }.to change{message['X-PM-Message-Id'].to_s}.to(postmark_message_id_format) end it 'updates a message object with full postmark response' do expect { message.deliver }.to change{message.postmark_response}.from(nil) end it 'delivers a tagged message' do expect { tagged_message.deliver }.to change{message.delivered?}.to(true) end it 'delivers a message with attachment' do expect { message_with_attachment.deliver }.to change{message_with_attachment.delivered?}.to(true) end context 'fails to deliver a message' do it ' without body - raise error' do expect { message_with_no_body.deliver! }.to raise_error(Postmark::InvalidMessageError) end it 'without body - do not deliver' do expect(message_with_no_body).not_to be_delivered end it 'with invalid To address - raise error' do expect { message_with_invalid_to.deliver! }.to raise_error(Postmark::InvalidMessageError) end it 'with invalid To address - do not deliver' do expect(message_with_invalid_to).not_to be_delivered end end endpostmark-1.22.0/spec/integration/api_client_resources_spec.rb0000644000175100017510000000364014123042137024633 0ustar vivekdebvivekdebrequire 'spec_helper' describe 'Accessing server resources using the API' do let(:api_client) {Postmark::ApiClient.new(ENV['POSTMARK_API_KEY'], :http_open_timeout => 15)} let(:recipient) {ENV['POSTMARK_CI_RECIPIENT']} let(:message) { { :from => ENV['POSTMARK_CI_SENDER'], :to => recipient, :subject => "Mail::Message object", :text_body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, " \ "sed do eiusmod tempor incididunt ut labore et dolore " \ "magna aliqua." } } context 'Messages API' do def with_retries(max_retries = 20, wait_seconds = 3) yield rescue => e retries = retries ? retries + 1 : 1 if retries < max_retries sleep wait_seconds retry else raise e end end it 'is possible to send a message and access its details via the Messages API' do response = api_client.deliver(message) message = with_retries {api_client.get_message(response[:message_id])} expect(message[:recipients]).to include(recipient) end it 'is possible to send a message and dump it via the Messages API' do response = api_client.deliver(message) dump = with_retries {api_client.dump_message(response[:message_id])} expect(dump[:body]).to include('Mail::Message object') end it 'is possible to send a message and find it via the Messages API' do response = api_client.deliver(message) expect { with_retries { messages = api_client.get_messages(:recipient => recipient, :fromemail => message[:from], :subject => message[:subject]) unless messages.map {|m| m[:message_id]}.include?(response[:message_id]) raise 'Message not found' end } }.not_to raise_error end end end postmark-1.22.0/spec/integration/api_client_messages_spec.rb0000644000175100017510000001011014123042137024416 0ustar vivekdebvivekdebrequire 'spec_helper' describe "Sending Mail::Messages with Postmark::ApiClient" do let(:postmark_message_id_format) { /\w{8}\-\w{4}-\w{4}-\w{4}-\w{12}/ } let(:api_client) { Postmark::ApiClient.new('POSTMARK_API_TEST', :http_open_timeout => 15, :http_read_timeout => 15) } let(:message) { Mail.new do from "sender@postmarkapp.com" to "recipient@postmarkapp.com" subject "Mail::Message object" body "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " "eiusmod tempor incididunt ut labore et dolore magna aliqua." end } let(:message_with_no_body) { Mail.new do from "sender@postmarkapp.com" to "recipient@postmarkapp.com" end } let(:message_with_attachment) { message.tap { |msg| msg.attachments["test.gif"] = File.read(empty_gif_path) } } let(:message_with_invalid_to) { Mail.new do from "sender@postmarkapp.com" to "@postmarkapp.com" end } let(:valid_messages) { [message, message.dup] } let(:partially_valid_messages) { [message, message.dup, message_with_no_body] } let(:invalid_messages) { [message_with_no_body, message_with_no_body.dup] } context 'invalid API code' do it "doesn't deliver messages" do expect { Postmark::ApiClient.new('INVALID').deliver_message(message) rescue Postmark::InvalidApiKeyError }.to change{message.delivered?}.to(false) end end context "single message" do it 'plain text message' do expect(api_client.deliver_message(message)).to have_key(:message_id) end it 'message with attachment' do expect(api_client.deliver_message(message_with_attachment)).to have_key(:message_id) end it 'response Message-ID' do expect(api_client.deliver_message(message)[:message_id]).to be =~ postmark_message_id_format end it 'response is Hash' do expect(api_client.deliver_message(message)).to be_a Hash end it 'fails to deliver a message without body' do expect { api_client.deliver_message(message_with_no_body) }.to raise_error(Postmark::InvalidMessageError) end it 'fails to deliver a message with invalid To address' do expect { api_client.deliver_message(message_with_invalid_to) }.to raise_error(Postmark::InvalidMessageError) end end context "batch message" do it 'response - valid Mail::Message objects' do expect { api_client.deliver_messages(valid_messages) }. to change{valid_messages.all? { |m| m.delivered? }}.to true end it 'response - valid X-PM-Message-Ids' do api_client.deliver_messages(valid_messages) expect(valid_messages.all? { |m| m['X-PM-Message-Id'].to_s =~ postmark_message_id_format }).to be true end it 'response - valid response objects' do api_client.deliver_messages(valid_messages) expect(valid_messages.all? { |m| m.postmark_response["To"] == m.to[0] }).to be true end it 'response - message responses count' do expect(api_client.deliver_messages(valid_messages).count).to eq valid_messages.count end context "custom max_batch_size" do before do api_client.max_batch_size = 1 end it 'response - valid response objects' do api_client.deliver_messages(valid_messages) expect(valid_messages.all? { |m| m.postmark_response["To"] == m.to[0] }).to be true end it 'response - message responses count' do expect(api_client.deliver_messages(valid_messages).count).to eq valid_messages.count end end it 'partially delivers a batch of partially valid Mail::Message objects' do expect { api_client.deliver_messages(partially_valid_messages) }. to change{partially_valid_messages.select { |m| m.delivered? }.count}.to 2 end it "doesn't deliver a batch of invalid Mail::Message objects" do aggregate_failures do expect { api_client.deliver_messages(invalid_messages) }. to change{invalid_messages.all? { |m| m.delivered? == false }}.to true expect(invalid_messages).to satisfy { |ms| ms.all? { |m| !!m.postmark_response }} end end end endpostmark-1.22.0/spec/integration/api_client_hashes_spec.rb0000644000175100017510000000564014123042137024076 0ustar vivekdebvivekdebrequire 'spec_helper' describe "Sending messages as Ruby hashes with Postmark::ApiClient" do let(:message_id_format) {/<.+@.+>/} let(:postmark_message_id_format) {/\w{8}\-\w{4}-\w{4}-\w{4}-\w{12}/} let(:api_client) {Postmark::ApiClient.new('POSTMARK_API_TEST', :http_open_timeout => 15, :http_read_timeout => 15)} let(:message) { { :from => "sender@postmarkapp.com", :to => "recipient@postmarkapp.com", :subject => "Mail::Message object", :text_body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, " \ "sed do eiusmod tempor incididunt ut labore et dolore " \ "magna aliqua." } } let(:message_with_no_body) { { :from => "sender@postmarkapp.com", :to => "recipient@postmarkapp.com", } } let(:message_with_attachment) { message.tap do |m| m[:attachments] = [File.open(empty_gif_path)] end } let(:message_with_invalid_to) {{:from => "sender@postmarkapp.com", :to => "@postmarkapp.com"}} let(:valid_messages) {[message, message.dup]} let(:partially_valid_messages) {[message, message.dup, message_with_no_body]} let(:invalid_messages) {[message_with_no_body, message_with_no_body.dup]} context "single message" do it 'plain text message' do expect(api_client.deliver(message)).to have_key(:message_id) end it 'message with attachment' do expect(api_client.deliver(message_with_attachment)).to have_key(:message_id) end it 'response Message-ID' do expect(api_client.deliver(message)[:message_id]).to be =~ postmark_message_id_format end it 'response is Hash' do expect(api_client.deliver(message)).to be_a Hash end it 'fails to deliver a message without body' do expect {api_client.deliver(message_with_no_body)}.to raise_error(Postmark::InvalidMessageError) end it 'fails to deliver a message with invalid To address' do expect {api_client.deliver(message_with_invalid_to)}.to raise_error(Postmark::InvalidMessageError) end end context "batch message" do it 'response messages count' do expect(api_client.deliver_in_batches(valid_messages).count).to eq valid_messages.count end context "custom max_batch_size" do before do api_client.max_batch_size = 1 end it 'response message count' do expect(api_client.deliver_in_batches(valid_messages).count).to eq valid_messages.count end end it 'partially delivers a batch of partially valid Mail::Message objects' do response = api_client.deliver_in_batches(partially_valid_messages) expect(response).to satisfy {|r| r.count {|mr| mr[:error_code].to_i.zero?} == 2} end it "doesn't deliver a batch of invalid Mail::Message objects" do response = api_client.deliver_in_batches(invalid_messages) expect(response).to satisfy {|r| r.all? {|mr| !!mr[:error_code]}} end end endpostmark-1.22.0/spec/integration/account_api_client_spec.rb0000644000175100017510000000721314123042137024255 0ustar vivekdebvivekdebrequire 'spec_helper' describe 'Account API client usage' do subject { Postmark::AccountApiClient.new(ENV['POSTMARK_ACCOUNT_API_KEY'], :http_open_timeout => 15, :http_read_timeout => 15) } let(:unique_token) { rand(36**32).to_s(36) } let(:unique_from_email) { ENV['POSTMARK_CI_SENDER'].gsub(/(\+.+)?@/, "+#{unique_token}@") } it 'manage senders' do # create & count new_sender = subject.create_sender(:name => 'Integration Test', :from_email => unique_from_email) expect(subject.get_senders_count).to be > 0 # get expect(subject.get_sender(new_sender[:id])[:id]).to eq(new_sender[:id]) # list senders = subject.get_senders(:count => 50) expect(senders.map { |s| s[:id] }).to include(new_sender[:id]) # collection expect(subject.senders.map { |s| s[:id] }).to include(new_sender[:id]) # update updated_sender = subject.update_sender(new_sender[:id], :name => 'New Name') expect(updated_sender[:name]).to eq('New Name') expect(updated_sender[:id]).to eq(new_sender[:id]) # spf expect(subject.verified_sender_spf?(new_sender[:id])).to be true # resend expect { subject.resend_sender_confirmation(new_sender[:id]) }.not_to raise_error # dkim expect { subject.request_new_sender_dkim(new_sender[:id]) }. to raise_error(Postmark::InvalidMessageError, 'This DKIM is already being renewed.') # delete expect { subject.delete_sender(new_sender[:id]) }.not_to raise_error end it 'manage domains' do domain_name = "#{unique_token}-gem-integration.test" return_path = "return.#{domain_name}" updated_return_path = "updated-return.#{domain_name}" # create & count new_domain = subject.create_domain(:name => domain_name, :return_path_domain => return_path) expect(subject.get_domains_count).to be > 0 # get expect(subject.get_domain(new_domain[:id])[:id]).to eq(new_domain[:id]) # list domains = subject.get_domains(:count => 50) expect(domains.map { |d| d[:id] }).to include(new_domain[:id]) # collection expect(subject.domains.map { |d| d[:id] }).to include(new_domain[:id]) # update updated_domain = subject.update_domain(new_domain[:id], :return_path_domain => updated_return_path) expect(updated_domain[:return_path_domain]).to eq(updated_return_path) expect(updated_domain[:id]).to eq(new_domain[:id]) # spf expect(subject.verified_domain_spf?(new_domain[:id])).to be true # dkim expect { subject.rotate_domain_dkim(new_domain[:id]) }. to raise_error(Postmark::InvalidMessageError, 'This DKIM is already being renewed.') # delete expect { subject.delete_domain(new_domain[:id]) }.not_to raise_error end it 'manage servers' do # create & count new_server = subject.create_server(:name => "server-#{unique_token}", :color => 'red') expect(subject.get_servers_count).to be > 0 # get expect(subject.get_server(new_server[:id])[:id]).to eq(new_server[:id]) # list servers = subject.get_servers(:count => 50) expect(servers.map { |s| s[:id] }).to include(new_server[:id]) # collection expect(subject.servers.map { |s| s[:id] }).to include(new_server[:id]) # update updated_server = subject.update_server(new_server[:id], :color => 'blue') expect(updated_server[:color]).to eq('blue') expect(updated_server[:id]).to eq(new_server[:id]) # delete expect { subject.delete_server(new_server[:id]) }.not_to raise_error end end postmark-1.22.0/spec/data/0000755000175100017510000000000014123042137015456 5ustar vivekdebvivekdebpostmark-1.22.0/spec/data/empty.gif0000644000175100017510000000005314123042137017301 0ustar vivekdebvivekdebGIF89a! ,D;postmark-1.22.0/postmark.png0000644000175100017510000000605714123042137016171 0ustar vivekdebvivekdebPNG  IHDR SPLTEx#WLRG ޽gY-'}mؼдsd70u>6sF=Ī] GtRNSȟA4^ּ!cƴtkVPJ=&ɁQ DIDATxǒ@E_+K(s3䜳ëJih䝫=M_A8ʬms2pܳ~ q݃g[=r>;%pxBpFpv`d"KN L쩟pTr3H-ϟ矫Z-]C%$NLv{~w/@K0B.,@Q٢a.k\'!Oj}s.R\YK@[ƾ:Hz|cXs^{dt-YfA2%}|)-ͷ/ {[SRK`H(0@l QXB}?’S>Ba9*w Yb tHmAa}R|U䇐}cx5 cxb}PL`;Z)|[1~|O 67P`,/ɛ"y+ EFy?tI(v0J[Od=eO0L(~0b!-#x&x ^v6MG03 iKT`*Mb PzDzqO 6LܝL]B:9q),_67>R7d!VӚP_ݽn0Sni]w Ū"Ɋ~˖mpJ,H2/b >(\]x޽wطt2Q۔gi뷥D9j}[{QzV.`)ö/rZi YKn`0-g`l|˕o`ofNQǾ*{<amfrTCy`kxZ˛cs}nzʭZ˛￞TӺ qW մP/,KCE<H;.z,wv{XxixM_͝i<o`sgtp>ÿ?K `]b[0q.>X0t\V3),NZC:S9|:xK~9jR )rtaR>" xNӖg<^:kt4r:οS֒%4 pO&Ɵ>|RgΗn|<^~ xQ xQH حtvjLN pD/Ӕ zg@85[4,ET'~;ڼKCޖQ$< e6`6%\m+QVupCr\J[@ނ <LG:G0aj?Ԣmqȓyl`lI\: 0H"CLh@4w&AVˬ&xB(RH>r rWE9T>uU?̦j˟ʟa徥U@Zῴzt1~P :u:L>0ja@E 2 R r @OLo0i4}M~Ri>?%50d$ a1%+bi_߅  .*$t`ҁK~߻Lܖ5F4,CnvSj @vW+v|+ c&V/7v7>hqM Agnveau1˟ 9f p!rovkPzF'wZ;FSMIH) C޽z"b5oxsX{/d[bvFr#FՇ]LED-djmՐ[[3"n ihLE̴@Ǡv (ު T!%RíQ#"?i3K6ywo6K=M<zIENDB`postmark-1.22.0/postmark.gemspec0000644000175100017510000000261514123042137017024 0ustar vivekdebvivekdeb# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "postmark/version" Gem::Specification.new do |s| s.name = "postmark" s.version = Postmark::VERSION s.homepage = "http://postmarkapp.com" s.platform = Gem::Platform::RUBY s.license = 'MIT' s.authors = ["Tomek Maszkowski", "Igor Balos", "Artem Chistyakov", "Nick Hammond", "Petyo Ivanov", "Ilya Sabanin"] s.email = "tomek@wildbit.com" s.extra_rdoc_files = ["LICENSE", "README.md"] s.rdoc_options = ["--charset=UTF-8"] s.summary = "Official Postmark API wrapper." s.description = "Use this gem to send emails through Postmark HTTP API and retrieve info about bounces." s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.post_install_message = %q{ ================== Thanks for installing the postmark gem. If you don't have an account, please sign up at http://postmarkapp.com/. Review the README.md for implementation details and examples. ================== } s.required_rubygems_version = ">= 1.3.7" s.add_dependency "json" s.add_development_dependency "mail" s.add_development_dependency "rake" end postmark-1.22.0/lib/0000755000175100017510000000000014123042137014361 5ustar vivekdebvivekdebpostmark-1.22.0/lib/postmark/0000755000175100017510000000000014123042137016221 5ustar vivekdebvivekdebpostmark-1.22.0/lib/postmark/version.rb0000644000175100017510000000005114123042137020227 0ustar vivekdebvivekdebmodule Postmark VERSION = '1.22.0' end postmark-1.22.0/lib/postmark/response_parsers/0000755000175100017510000000000014123042137021616 5ustar vivekdebvivekdebpostmark-1.22.0/lib/postmark/response_parsers/yajl.rb0000644000175100017510000000031614123042137023102 0ustar vivekdebvivekdebrequire 'yajl' Yajl::Encoder.enable_json_gem_compatability module Postmark module ResponseParsers module Yajl def self.decode(data) ::Yajl::Parser.parse(data) end end end endpostmark-1.22.0/lib/postmark/response_parsers/json.rb0000644000175100017510000000023114123042137023110 0ustar vivekdebvivekdebrequire 'json' module Postmark module ResponseParsers module Json def self.decode(data) JSON.parse(data) end end end end postmark-1.22.0/lib/postmark/response_parsers/active_support.rb0000644000175100017510000000031614123042137025212 0ustar vivekdebvivekdeb# assume activesupport is already loaded module Postmark module ResponseParsers module ActiveSupport def self.decode(data) ::ActiveSupport::JSON.decode(data) end end end end postmark-1.22.0/lib/postmark/message_extensions/0000755000175100017510000000000014123042137022124 5ustar vivekdebvivekdebpostmark-1.22.0/lib/postmark/message_extensions/mail.rb0000644000175100017510000001135714123042137023402 0ustar vivekdebvivekdebmodule Mail class Message attr_accessor :delivered, :postmark_response def delivered? self.delivered end def tag(val = nil) default 'TAG', val end def tag=(val) header['TAG'] = val end def track_links(val = nil) self.track_links=(val) unless val.nil? header['TRACK-LINKS'].to_s end def track_links=(val) header['TRACK-LINKS'] = ::Postmark::Inflector.to_postmark(val) end def track_opens(val = nil) self.track_opens=(val) unless val.nil? header['TRACK-OPENS'].to_s end def track_opens=(val) header['TRACK-OPENS'] = (!!val).to_s end def metadata(val = nil) if val @metadata = val else @metadata ||= {} end end def metadata=(val) @metadata = val end def postmark_attachments=(value) Kernel.warn("Mail::Message#postmark_attachments= is deprecated and will " \ "be removed in the future. Please consider using the native " \ "attachments API provided by Mail library.") @_attachments = value end def postmark_attachments return [] if @_attachments.nil? Kernel.warn("Mail::Message#postmark_attachments is deprecated and will " \ "be removed in the future. Please consider using the native " \ "attachments API provided by Mail library.") ::Postmark::MessageHelper.attachments_to_postmark(@_attachments) end def template_alias(val = nil) return self[:postmark_template_alias] && self[:postmark_template_alias].to_s if val.nil? self[:postmark_template_alias] = val end attr_writer :template_model def template_model(model = nil) return @template_model if model.nil? @template_model = model end def message_stream(val = nil) self.message_stream = val unless val.nil? header['MESSAGE-STREAM'].to_s end def message_stream=(val) header['MESSAGE-STREAM'] = val end def templated? !!template_alias end def prerender raise ::Postmark::Error, 'Cannot prerender a message without an associated template alias' unless templated? unless delivery_method.is_a?(::Mail::Postmark) raise ::Postmark::MailAdapterError, "Cannot render templates via #{delivery_method.class} adapter." end client = delivery_method.api_client template = client.get_template(template_alias) response = client.validate_template(template.merge(:test_render_model => template_model || {})) raise ::Postmark::InvalidTemplateError, response unless response[:all_content_is_valid] self.body = nil subject response[:subject][:rendered_content] text_part do body response[:text_body][:rendered_content] end html_part do content_type 'text/html; charset=UTF-8' body response[:html_body][:rendered_content] end self end def text? if defined?(super) super else has_content_type? ? !!(main_type =~ /^text$/i) : false end end def html? text? && !!(sub_type =~ /^html$/i) end def body_html if multipart? && html_part html_part.decoded elsif html? decoded end end def body_text if multipart? && text_part text_part.decoded elsif text? && !html? decoded elsif !html? body.decoded end end def export_attachments export_native_attachments + postmark_attachments end def export_headers [].tap do |headers| self.header.fields.each do |field| key, value = field.name, field.value next if reserved_headers.include? key.downcase headers << { "Name" => key, "Value" => value } end end end def to_postmark_hash ready_to_send! ::Postmark::MailMessageConverter.new(self).run end protected def pack_attachment_data(data) ::Postmark::MessageHelper.encode_in_base64(data) end def export_native_attachments attachments.map do |attachment| basics = {"Name" => attachment.filename, "Content" => pack_attachment_data(attachment.body.decoded), "ContentType" => attachment.mime_type} specials = attachment.inline? ? {'ContentID' => attachment.url} : {} basics.update(specials) end end def reserved_headers %q[ return-path x-pm-rcpt from reply-to sender received date content-type cc bcc subject tag attachment to track-opens track-links postmark-template-alias message-stream ] end end end postmark-1.22.0/lib/postmark/mail_message_converter.rb0000644000175100017510000000354214123042137023267 0ustar vivekdebvivekdebmodule Postmark class MailMessageConverter def initialize(message) @message = message end def run delete_blank_fields(pick_fields(convert, @message.templated?)) end private def convert headers_part.merge(content_part) end def delete_blank_fields(message_hash) message_hash.delete_if { |k, v| v.nil? || (v.respond_to?(:empty?) && v.empty?) } end def headers_part { 'From' => @message['from'].to_s, 'To' => @message['to'].to_s, 'ReplyTo' => @message['reply_to'].to_s, 'Cc' => @message['cc'].to_s, 'Bcc' => @message['bcc'].to_s, 'Subject' => @message.subject, 'Headers' => @message.export_headers, 'Tag' => @message.tag.to_s, 'TrackOpens' => (cast_to_bool(@message.track_opens) unless @message.track_opens.empty?), 'TrackLinks' => (::Postmark::Inflector.to_postmark(@message.track_links) unless @message.track_links.empty?), 'Metadata' => @message.metadata, 'TemplateAlias' => @message.template_alias, 'TemplateModel' => @message.template_model, 'MessageStream' => @message.message_stream } end def pick_fields(message_hash, templated = false) fields = if templated %w(Subject HtmlBody TextBody) else %w(TemplateAlias TemplateModel) end fields.each { |key| message_hash.delete(key) } message_hash end def content_part { 'Attachments' => @message.export_attachments, 'HtmlBody' => @message.body_html, 'TextBody' => @message.body_text } end def cast_to_bool(val) if val.is_a?(TrueClass) || val.is_a?(FalseClass) val elsif val.is_a?(String) && val.downcase == "true" true else false end end end endpostmark-1.22.0/lib/postmark/json.rb0000644000175100017510000000050314123042137017515 0ustar vivekdebvivekdebmodule Postmark module Json class << self def encode(data) json_parser data.to_json end def decode(data) json_parser.decode(data) end private def json_parser ResponseParsers.const_get(Postmark.response_parser_class) end end end end postmark-1.22.0/lib/postmark/inflector.rb0000644000175100017510000000104014123042137020526 0ustar vivekdebvivekdebmodule Postmark module Inflector extend self def to_postmark(name) name.to_s.split('_').map { |part| capitalize_first_letter(part) }.join('') end def to_ruby(name) name.to_s.scan(camel_case_regexp).join('_').downcase.to_sym end def camel_case_regexp /(?:[A-Z](?:(?:[A-Z]+(?![a-z\d]))|[a-z\d]*))|[a-z\d\_]+/ end protected def capitalize_first_letter(str) if str.length > 0 str.slice(0..0).capitalize + str.slice(1..-1) else str end end end endpostmark-1.22.0/lib/postmark/inbound.rb0000644000175100017510000000127614123042137020212 0ustar vivekdebvivekdebmodule Postmark module Inbound extend self def to_ruby_hash(inbound) inbound = Json.decode(inbound) if inbound.is_a?(String) ret = HashHelper.to_ruby(inbound) ret[:from_full] ||= {} ret[:to_full] ||= [] ret[:cc_full] ||= [] ret[:headers] ||= [] ret[:attachments] ||= [] ret[:from_full] = HashHelper.to_ruby(ret[:from_full]) ret[:to_full] = ret[:to_full].map { |to| HashHelper.to_ruby(to) } ret[:cc_full] = ret[:cc_full].map { |cc| HashHelper.to_ruby(cc) } ret[:headers] = ret[:headers].map { |h| HashHelper.to_ruby(h) } ret[:attachments] = ret[:attachments].map { |a| HashHelper.to_ruby(a) } ret end end endpostmark-1.22.0/lib/postmark/http_client.rb0000644000175100017510000000620614123042137021067 0ustar vivekdebvivekdebrequire 'thread' unless defined? Mutex # For Ruby 1.8.7 require 'cgi' module Postmark class HttpClient attr_accessor :api_token attr_reader :http, :secure, :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :host, :port, :path_prefix, :http_open_timeout, :http_read_timeout, :http_ssl_version, :auth_header_name alias_method :api_key, :api_token alias_method :api_key=, :api_token= DEFAULTS = { :auth_header_name => 'X-Postmark-Server-Token', :host => 'api.postmarkapp.com', :secure => true, :path_prefix => '/', :http_read_timeout => 15, :http_open_timeout => 5 } def initialize(api_token, options = {}) @api_token = api_token @request_mutex = Mutex.new apply_options(options) @http = build_http end def post(path, data = '') do_request { |client| client.post(url_path(path), data, headers) } end def put(path, data = '') do_request { |client| client.put(url_path(path), data, headers) } end def patch(path, data = '') do_request { |client| client.patch(url_path(path), data, headers) } end def get(path, query = {}) do_request { |client| client.get(url_path(path + to_query_string(query)), headers) } end def delete(path, query = {}) do_request { |client| client.delete(url_path(path + to_query_string(query)), headers) } end def protocol self.secure ? 'https' : 'http' end protected def apply_options(options = {}) options = Hash[*options.select { |_, v| !v.nil? }.flatten] DEFAULTS.merge(options).each_pair do |name, value| instance_variable_set(:"@#{name}", value) end @port = options[:port] || (@secure ? 443 : 80) end def to_query_string(hash) return "" if hash.empty? "?" + hash.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&") end def url URI.parse("#{protocol}://#{self.host}:#{self.port}/") end def handle_response(response) if response.code.to_i == 200 Postmark::Json.decode(response.body) else raise HttpServerError.build(response.code, response.body) end end def headers HEADERS.merge(self.auth_header_name => self.api_token.to_s) end def url_path(path) self.path_prefix + path end def do_request @request_mutex.synchronize do handle_response(yield(http)) end rescue Timeout::Error => e raise TimeoutError.new(e) rescue Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e raise HttpClientError.new(e.message) end def build_http http = Net::HTTP::Proxy(self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_pass).new(url.host, url.port) http.read_timeout = self.http_read_timeout http.open_timeout = self.http_open_timeout http.use_ssl = !!self.secure http.ssl_version = self.http_ssl_version if self.http_ssl_version && http.respond_to?(:ssl_version=) http end end end postmark-1.22.0/lib/postmark/helpers/0000755000175100017510000000000014123042137017663 5ustar vivekdebvivekdebpostmark-1.22.0/lib/postmark/helpers/message_helper.rb0000644000175100017510000000276014123042137023200 0ustar vivekdebvivekdebmodule Postmark module MessageHelper extend self def to_postmark(message = {}) message = message.dup %w(to reply_to cc bcc).each do |field| message[field.to_sym] = Array[*message[field.to_sym]].join(", ") end if message[:headers] message[:headers] = headers_to_postmark(message[:headers]) end if message[:attachments] message[:attachments] = attachments_to_postmark(message[:attachments]) end if message[:track_links] message[:track_links] = ::Postmark::Inflector.to_postmark(message[:track_links]) end HashHelper.to_postmark(message) end def headers_to_postmark(headers) wrap_in_array(headers).map do |item| HashHelper.to_postmark(item) end end def attachments_to_postmark(attachments) wrap_in_array(attachments).map do |item| if item.is_a?(Hash) HashHelper.to_postmark(item) elsif item.is_a?(File) { "Name" => item.path.split("/")[-1], "Content" => encode_in_base64(IO.read(item.path)), "ContentType" => "application/octet-stream" } end end end def encode_in_base64(data) [data].pack('m') end protected # From ActiveSupport (Array#wrap) def wrap_in_array(object) if object.nil? [] elsif object.respond_to?(:to_ary) object.to_ary || [object] else [object] end end end endpostmark-1.22.0/lib/postmark/helpers/hash_helper.rb0000644000175100017510000000266414123042137022502 0ustar vivekdebvivekdebmodule Postmark module HashHelper extend self def to_postmark(object, options = {}) deep = options.fetch(:deep, false) case object when Hash object.reduce({}) do |m, (k, v)| m.tap do |h| h[Inflector.to_postmark(k)] = deep ? to_postmark(v, options) : v end end when Array deep ? object.map { |v| to_postmark(v, options) } : object else object end end def to_ruby(object, options = {}) compatible = options.fetch(:compatible, false) deep = options.fetch(:deep, false) case object when Hash object.reduce({}) do |m, (k, v)| m.tap do |h| h[Inflector.to_ruby(k)] = deep ? to_ruby(v, options) : v end end.tap do |result| if compatible result.merge!(object) enhance_with_compatibility_warning(result) end end when Array deep ? object.map { |v| to_ruby(v, options) } : object else object end end private def enhance_with_compatibility_warning(hash) def hash.[](key) if key.is_a? String Kernel.warn("Postmark: the CamelCased String keys of response are " \ "deprecated in favor of underscored symbols. The " \ "support will be dropped in the future.") end super end end end endpostmark-1.22.0/lib/postmark/handlers/0000755000175100017510000000000014123042137020021 5ustar vivekdebvivekdebpostmark-1.22.0/lib/postmark/handlers/mail.rb0000644000175100017510000000126314123042137021272 0ustar vivekdebvivekdebmodule Mail class Postmark attr_accessor :settings def initialize(values) self.settings = { :api_token => ENV['POSTMARK_API_TOKEN'] }.merge(values) end def deliver!(mail) response = if mail.templated? api_client.deliver_message_with_template(mail) else api_client.deliver_message(mail) end if settings[:return_response] response else self end end def api_client settings = self.settings.dup api_token = settings.delete(:api_token) || settings.delete(:api_key) ::Postmark::ApiClient.new(api_token, settings) end end endpostmark-1.22.0/lib/postmark/error.rb0000644000175100017510000000631414123042137017703 0ustar vivekdebvivekdebmodule Postmark class Error < ::StandardError; end class HttpClientError < Error def retry? true end end class HttpServerError < Error attr_accessor :status_code, :parsed_body, :body alias_method :full_response, :parsed_body def self.build(status_code, body) parsed_body = Postmark::Json.decode(body) rescue {} case status_code when '401' InvalidApiKeyError.new(401, body, parsed_body) when '422' ApiInputError.build(body, parsed_body) when '500' InternalServerError.new(500, body, parsed_body) else UnexpectedHttpResponseError.new(status_code, body, parsed_body) end end def initialize(status_code = 500, body = '', parsed_body = {}) self.parsed_body = parsed_body self.status_code = status_code.to_i self.body = body message = parsed_body.fetch('Message', "The Postmark API responded with HTTP status #{status_code}.") super(message) end def retry? 5 == status_code / 100 end end class ApiInputError < HttpServerError INACTIVE_RECIPIENT = 406 INVALID_EMAIL_ADDRESS = 300 attr_accessor :error_code def self.build(body, parsed_body) error_code = parsed_body['ErrorCode'].to_i case error_code when INACTIVE_RECIPIENT InactiveRecipientError.new(error_code, body, parsed_body) when INVALID_EMAIL_ADDRESS InvalidEmailAddressError.new(error_code, body, parsed_body) else new(error_code, body, parsed_body) end end def initialize(error_code = nil, body = '', parsed_body = {}) self.error_code = error_code.to_i super(422, body, parsed_body) end def retry? false end end class InvalidEmailAddressError < ApiInputError; end class InactiveRecipientError < ApiInputError attr_reader :recipients PATTERNS = [/^Found inactive addresses: (.+?)\.$/.freeze, /these inactive addresses: (.+?)\. Inactive/.freeze, /these inactive addresses: (.+?)\.?$/].freeze def self.parse_recipients(message) PATTERNS.each do |p| _, recipients = p.match(message).to_a next unless recipients return recipients.split(', ') end [] end def initialize(*args) super @recipients = parse_recipients || [] end private def parse_recipients return unless parsed_body && !parsed_body.empty? self.class.parse_recipients(parsed_body['Message']) end end class InvalidTemplateError < Error attr_reader :postmark_response def initialize(response) @postmark_response = response super('Failed to render the template. Please check #postmark_response on this error for details.') end end class TimeoutError < Error def retry? true end end class MailAdapterError < Postmark::Error; end class UnknownMessageType < Error; end class InvalidApiKeyError < HttpServerError; end class InternalServerError < HttpServerError; end class UnexpectedHttpResponseError < HttpServerError; end # Backwards compatible aliases DeliveryError = Error InvalidMessageError = ApiInputError UnknownError = UnexpectedHttpResponseError end postmark-1.22.0/lib/postmark/client.rb0000644000175100017510000000471214123042137020030 0ustar vivekdebvivekdebrequire 'enumerator' module Postmark class Client attr_reader :http_client, :max_retries def initialize(api_token, options = {}) options = options.dup @max_retries = options.delete(:max_retries) || 0 @http_client = HttpClient.new(api_token, options) end def api_token=(api_token) http_client.api_token = api_token end alias_method :api_key=, :api_token= def find_each(path, name, options = {}) if block_given? options = options.dup i, total_count = [0, 1] while i < total_count options[:offset] = i total_count, collection = load_batch(path, name, options) collection.each { |e| yield e } i += collection.size end else enum_for(:find_each, path, name, options) do get_resource_count(path, options) end end end protected def with_retries yield rescue HttpServerError, HttpClientError, TimeoutError, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Net::ProtocolError, SocketError => e retries = retries ? retries + 1 : 1 retriable = !e.respond_to?(:retry?) || e.retry? if retriable && retries < self.max_retries retry else raise e end end def serialize(data) Postmark::Json.encode(data) end def take_response_of [yield, nil] rescue HttpServerError => e [e.full_response || {}, e] end def format_response(response, options = {}) return {} unless response compatible = options.fetch(:compatible, false) deep = options.fetch(:deep, false) if response.kind_of? Array response.map { |entry| Postmark::HashHelper.to_ruby(entry, :compatible => compatible, :deep => deep) } else Postmark::HashHelper.to_ruby(response, :compatible => compatible, :deep => deep) end end def get_resource_count(path, options = {}) # At this point Postmark API returns 0 as total if you request 0 documents total_count, _ = load_batch(path, nil, options.merge(:count => 1)) total_count end def load_batch(path, name, options) options[:offset] ||= 0 options[:count] ||= 30 response = http_client.get(path, options) format_batch_response(response, name) end def format_batch_response(response, name) [response['TotalCount'], format_response(response[name])] end end endpostmark-1.22.0/lib/postmark/bounce.rb0000644000175100017510000000254114123042137020023 0ustar vivekdebvivekdebrequire 'time' module Postmark class Bounce attr_reader :email, :bounced_at, :type, :description, :details, :name, :id, :server_id, :tag, :message_id, :subject def initialize(values = {}) values = Postmark::HashHelper.to_ruby(values) @id = values[:id] @email = values[:email] @bounced_at = Time.parse(values[:bounced_at]) @type = values[:type] @name = values[:name] @description = values[:description] @details = values[:details] @tag = values[:tag] @dump_available = values[:dump_available] @inactive = values[:inactive] @can_activate = values[:can_activate] @message_id = values[:message_id] @subject = values[:subject] end def inactive? !!@inactive end def can_activate? !!@can_activate end def dump Postmark.api_client.dump_bounce(id)[:body] end def activate Bounce.new(Postmark.api_client.activate_bounce(id)) end def dump_available? !!@dump_available end class << self def find(id) Bounce.new(Postmark.api_client.get_bounce(id)) end def all(options = {}) options[:count] ||= 30 options[:offset] ||= 0 Postmark.api_client.get_bounces(options).map do |bounce_json| Bounce.new(bounce_json) end end end end end postmark-1.22.0/lib/postmark/api_client.rb0000644000175100017510000002772714123042137020674 0ustar vivekdebvivekdebmodule Postmark class ApiClient < Client attr_accessor :max_batch_size def initialize(api_token, options = {}) options = options.dup @max_batch_size = options.delete(:max_batch_size) || 500 super end def deliver(message_hash = {}) data = serialize(MessageHelper.to_postmark(message_hash)) with_retries do format_response http_client.post("email", data) end end def deliver_in_batches(message_hashes) in_batches(message_hashes) do |batch, offset| data = serialize(batch.map { |h| MessageHelper.to_postmark(h) }) with_retries do http_client.post("email/batch", data) end end end def deliver_message(message) if message.templated? raise ArgumentError, "Please use #{self.class}#deliver_message_with_template to deliver messages with templates." end data = serialize(message.to_postmark_hash) with_retries do response, error = take_response_of { http_client.post("email", data) } update_message(message, response) raise error if error format_response(response, :compatible => true) end end def deliver_message_with_template(message) raise ArgumentError, 'Templated delivery requested, but the template is missing.' unless message.templated? data = serialize(message.to_postmark_hash) with_retries do response, error = take_response_of { http_client.post("email/withTemplate", data) } update_message(message, response) raise error if error format_response(response, :compatible => true) end end def deliver_messages(messages) if messages.any? { |m| m.templated? } raise ArgumentError, "Some of the provided messages have templates. Please use " \ "#{self.class}#deliver_messages_with_templates to deliver those." end in_batches(messages) do |batch, offset| data = serialize(batch.map { |m| m.to_postmark_hash }) with_retries do http_client.post("email/batch", data).tap do |response| response.each_with_index do |r, i| update_message(messages[offset + i], r) end end end end end def deliver_messages_with_templates(messages) unless messages.all? { |m| m.templated? } raise ArgumentError, 'Templated delivery requested, but one or more messages lack templates.' end in_batches(messages) do |batch, offset| mapped = batch.map { |m| m.to_postmark_hash } data = serialize(:Messages => mapped) with_retries do http_client.post("email/batchWithTemplates", data).tap do |response| response.each_with_index do |r, i| update_message(messages[offset + i], r) end end end end end def delivery_stats response = format_response(http_client.get("deliverystats"), :compatible => true) if response[:bounces] response[:bounces] = format_response(response[:bounces]) end response end def messages(options = {}) path, name, params = extract_messages_path_and_params(options) find_each(path, name, params) end def get_messages(options = {}) path, name, params = extract_messages_path_and_params(options) load_batch(path, name, params).last end def get_messages_count(options = {}) path, _, params = extract_messages_path_and_params(options) get_resource_count(path, params) end def get_message(id, options = {}) get_for_message('details', id, options) end def dump_message(id, options = {}) get_for_message('dump', id, options) end def bounces(options = {}) find_each('bounces', 'Bounces', options) end def get_bounces(options = {}) _, batch = load_batch('bounces', 'Bounces', options) batch end def get_bounce(id) format_response http_client.get("bounces/#{id}") end def dump_bounce(id) format_response http_client.get("bounces/#{id}/dump") end def activate_bounce(id) format_response http_client.put("bounces/#{id}/activate")["Bounce"] end def opens(options = {}) find_each('messages/outbound/opens', 'Opens', options) end def clicks(options = {}) find_each('messages/outbound/clicks', 'Clicks', options) end def get_opens(options = {}) _, batch = load_batch('messages/outbound/opens', 'Opens', options) batch end def get_clicks(options = {}) _, batch = load_batch('messages/outbound/clicks', 'Clicks', options) batch end def get_opens_by_message_id(message_id, options = {}) _, batch = load_batch("messages/outbound/opens/#{message_id}", 'Opens', options) batch end def get_clicks_by_message_id(message_id, options = {}) _, batch = load_batch("messages/outbound/clicks/#{message_id}", 'Clicks', options) batch end def opens_by_message_id(message_id, options = {}) find_each("messages/outbound/opens/#{message_id}", 'Opens', options) end def clicks_by_message_id(message_id, options = {}) find_each("messages/outbound/clicks/#{message_id}", 'Clicks', options) end def create_trigger(type, options) type = Postmark::Inflector.to_postmark(type).downcase data = serialize(HashHelper.to_postmark(options)) format_response http_client.post("triggers/#{type}", data) end def get_trigger(type, id) format_response http_client.get("triggers/#{type}/#{id}") end def delete_trigger(type, id) type = Postmark::Inflector.to_postmark(type).downcase format_response http_client.delete("triggers/#{type}/#{id}") end def get_triggers(type, options = {}) type = Postmark::Inflector.to_postmark(type) _, batch = load_batch("triggers/#{type.downcase}", type, options) batch end def triggers(type, options = {}) type = Postmark::Inflector.to_postmark(type) find_each("triggers/#{type.downcase}", type, options) end def server_info format_response http_client.get("server") end def update_server_info(attributes = {}) data = HashHelper.to_postmark(attributes) format_response http_client.put("server", serialize(data)) end def get_templates(options = {}) load_batch('templates', 'Templates', options) end def templates(options = {}) find_each('templates', 'Templates', options) end def get_template(id) format_response http_client.get("templates/#{id}") end def create_template(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.post('templates', data) end def update_template(id, attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.put("templates/#{id}", data) end def delete_template(id) format_response http_client.delete("templates/#{id}") end def validate_template(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) response = format_response(http_client.post('templates/validate', data)) response.each do |k, v| next unless v.is_a?(Hash) && k != :suggested_template_model response[k] = HashHelper.to_ruby(v) if response[k].has_key?(:validation_errors) ruby_hashes = response[k][:validation_errors].map do |err| HashHelper.to_ruby(err) end response[k][:validation_errors] = ruby_hashes end end response end def deliver_with_template(attributes = {}) data = serialize(MessageHelper.to_postmark(attributes)) with_retries do format_response http_client.post('email/withTemplate', data) end end def deliver_in_batches_with_templates(message_hashes) in_batches(message_hashes) do |batch, offset| mapped = batch.map { |h| MessageHelper.to_postmark(h) } data = serialize(:Messages => mapped) with_retries do http_client.post('email/batchWithTemplates', data) end end end def get_stats_totals(options = {}) format_response(http_client.get('stats/outbound', options)) end def get_stats_counts(stat, options = {}) url = "stats/outbound/#{stat}" url << "/#{options[:type]}" if options.has_key?(:type) response = format_response(http_client.get(url, options)) response[:days].map! { |d| HashHelper.to_ruby(d) } response end def get_webhooks(options = {}) options = HashHelper.to_postmark(options) _, batch = load_batch('webhooks', 'Webhooks', options) batch end def get_webhook(id) format_response http_client.get("webhooks/#{id}") end def create_webhook(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.post('webhooks', data) end def update_webhook(id, attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.put("webhooks/#{id}", data) end def delete_webhook(id) format_response http_client.delete("webhooks/#{id}") end def get_message_streams(options = {}) _, batch = load_batch('message-streams', 'MessageStreams', options) batch end def message_streams(options = {}) find_each('message-streams', 'MessageStreams', options) end def get_message_stream(id) format_response(http_client.get("message-streams/#{id}")) end def create_message_stream(attributes = {}) data = serialize(HashHelper.to_postmark(attributes, :deep => true)) format_response(http_client.post('message-streams', data), :deep => true) end def update_message_stream(id, attributes) data = serialize(HashHelper.to_postmark(attributes, :deep => true)) format_response(http_client.patch("message-streams/#{id}", data), :deep => true) end def archive_message_stream(id) format_response http_client.post("message-streams/#{id}/archive") end def unarchive_message_stream(id) format_response http_client.post("message-streams/#{id}/unarchive") end def dump_suppressions(stream_id, options = {}) _, batch = load_batch("message-streams/#{stream_id}/suppressions/dump", 'Suppressions', options) batch end def create_suppressions(stream_id, email_addresses) data = serialize(:Suppressions => Array(email_addresses).map { |e| HashHelper.to_postmark(:email_address => e) }) format_response(http_client.post("message-streams/#{stream_id}/suppressions", data)) end def delete_suppressions(stream_id, email_addresses) data = serialize(:Suppressions => Array(email_addresses).map { |e| HashHelper.to_postmark(:email_address => e) }) format_response(http_client.post("message-streams/#{stream_id}/suppressions/delete", data)) end protected def in_batches(messages) r = messages.each_slice(max_batch_size).each_with_index.map do |batch, i| yield batch, i * max_batch_size end format_response r.flatten end def update_message(message, response) response ||= {} message['X-PM-Message-Id'] = response['MessageID'] message.delivered = response['ErrorCode'] && response['ErrorCode'].zero? message.postmark_response = response end def get_for_message(action, id, options = {}) path, _, params = extract_messages_path_and_params(options) format_response http_client.get("#{path}/#{id}/#{action}", params) end def extract_messages_path_and_params(options = {}) options = options.dup messages_key = options[:inbound] ? 'InboundMessages' : 'Messages' path = options.delete(:inbound) ? 'messages/inbound' : 'messages/outbound' [path, messages_key, options] end end end postmark-1.22.0/lib/postmark/account_api_client.rb0000644000175100017510000001016314123042137022372 0ustar vivekdebvivekdebmodule Postmark class AccountApiClient < Client def initialize(api_token, options = {}) options[:auth_header_name] = 'X-Postmark-Account-Token' super end def senders(options = {}) find_each('senders', 'SenderSignatures', options) end alias_method :signatures, :senders def get_senders(options = {}) load_batch('senders', 'SenderSignatures', options).last end alias_method :get_signatures, :get_senders def get_senders_count(options = {}) get_resource_count('senders', options) end alias_method :get_signatures_count, :get_senders_count def get_sender(id) format_response http_client.get("senders/#{id.to_i}") end alias_method :get_signature, :get_sender def create_sender(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.post('senders', data) end alias_method :create_signature, :create_sender def update_sender(id, attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.put("senders/#{id.to_i}", data) end alias_method :update_signature, :update_sender def resend_sender_confirmation(id) format_response http_client.post("senders/#{id.to_i}/resend") end alias_method :resend_signature_confirmation, :resend_sender_confirmation def verified_sender_spf?(id) !!http_client.post("senders/#{id.to_i}/verifyspf")['SPFVerified'] end alias_method :verified_signature_spf?, :verified_sender_spf? def request_new_sender_dkim(id) format_response http_client.post("senders/#{id.to_i}/requestnewdkim") end alias_method :request_new_signature_dkim, :request_new_sender_dkim def delete_sender(id) format_response http_client.delete("senders/#{id.to_i}") end alias_method :delete_signature, :delete_sender def domains(options = {}) find_each('domains', 'Domains', options) end def get_domains(options = {}) load_batch('domains', 'Domains', options).last end def get_domains_count(options = {}) get_resource_count('domains', options) end def get_domain(id) format_response http_client.get("domains/#{id.to_i}") end def create_domain(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.post('domains', data) end def update_domain(id, attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.put("domains/#{id.to_i}", data) end def verify_domain_dkim(id) format_response http_client.put("domains/#{id.to_i}/verifydkim") end def verify_domain_return_path(id) format_response http_client.put("domains/#{id.to_i}/verifyreturnpath") end def verified_domain_spf?(id) !!http_client.post("domains/#{id.to_i}/verifyspf")['SPFVerified'] end def rotate_domain_dkim(id) format_response http_client.post("domains/#{id.to_i}/rotatedkim") end def delete_domain(id) format_response http_client.delete("domains/#{id.to_i}") end def servers(options = {}) find_each('servers', 'Servers', options) end def get_servers(options = {}) load_batch('servers', 'Servers', options).last end def get_servers_count(options = {}) get_resource_count('servers', options) end def get_server(id) format_response http_client.get("servers/#{id.to_i}") end def create_server(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.post('servers', data) end def update_server(id, attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) format_response http_client.put("servers/#{id.to_i}", data) end def delete_server(id) format_response http_client.delete("servers/#{id.to_i}") end def push_templates(attributes = {}) data = serialize(HashHelper.to_postmark(attributes)) _, batch = format_batch_response(http_client.put('templates/push', data), "Templates") batch end end end postmark-1.22.0/lib/postmark.rb0000644000175100017510000000474314123042137016556 0ustar vivekdebvivekdebrequire 'net/http' require 'net/https' require 'thread' unless defined? Mutex # For Ruby 1.8.7 require 'postmark/version' require 'postmark/inflector' require 'postmark/helpers/hash_helper' require 'postmark/helpers/message_helper' require 'postmark/mail_message_converter' require 'postmark/bounce' require 'postmark/inbound' require 'postmark/json' require 'postmark/error' require 'postmark/http_client' require 'postmark/client' require 'postmark/api_client' require 'postmark/account_api_client' require 'postmark/message_extensions/mail' require 'postmark/handlers/mail' module Postmark module ResponseParsers autoload :Json, 'postmark/response_parsers/json' autoload :ActiveSupport, 'postmark/response_parsers/active_support' autoload :Yajl, 'postmark/response_parsers/yajl' end HEADERS = { 'User-Agent' => "Postmark Ruby Gem v#{VERSION}", 'Content-type' => 'application/json', 'Accept' => 'application/json' } extend self @@api_client_mutex = Mutex.new attr_accessor :secure, :api_token, :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :host, :port, :path_prefix, :http_open_timeout, :http_read_timeout, :max_retries alias_method :api_key, :api_token alias_method :api_key=, :api_token= attr_writer :response_parser_class, :api_client def response_parser_class @response_parser_class ||= defined?(ActiveSupport::JSON) ? :ActiveSupport : :Json end def configure yield self end def api_client return @api_client if @api_client @@api_client_mutex.synchronize do @api_client ||= Postmark::ApiClient.new( self.api_token, :secure => self.secure, :proxy_host => self.proxy_host, :proxy_port => self.proxy_port, :proxy_user => self.proxy_user, :proxy_pass => self.proxy_pass, :host => self.host, :port => self.port, :path_prefix => self.path_prefix, :max_retries => self.max_retries ) end end def deliver_message(*args) api_client.deliver_message(*args) end alias_method :send_through_postmark, :deliver_message def deliver_messages(*args) api_client.deliver_messages(*args) end def delivery_stats(*args) api_client.delivery_stats(*args) end end Postmark.response_parser_class = nilpostmark-1.22.0/init.rb0000644000175100017510000000002214123042137015075 0ustar vivekdebvivekdebrequire 'postmark'postmark-1.22.0/gemfiles/0000755000175100017510000000000014123042137015406 5ustar vivekdebvivekdebpostmark-1.22.0/gemfiles/Gemfile.legacy0000644000175100017510000000065514123042137020152 0ustar vivekdebvivekdebsource "http://rubygems.org" gemspec :path => '../' gem 'rake', '< 11.0.0' gem 'json', '< 2.0.0' group :test do gem 'rspec', '~> 3.7' gem 'rspec-its', '~> 1.2' gem 'fakeweb', :git => 'https://github.com/chrisk/fakeweb.git' gem 'fakeweb-matcher' gem 'mime-types', '~> 1.25.1' gem 'activesupport', '~> 3.2.0' gem 'i18n', '~> 0.6.0' gem 'yajl-ruby', '~> 1.0', '< 1.4.0', :platforms => [:mingw, :mswin, :ruby] end postmark-1.22.0/VERSION0000644000175100017510000000000714123042137014660 0ustar vivekdebvivekdeb1.22.0 postmark-1.22.0/Rakefile0000644000175100017510000000012414123042137015255 0ustar vivekdebvivekdebrequire "bundler/gem_tasks" require 'rspec/core/rake_task' RSpec::Core::RakeTask.newpostmark-1.22.0/RELEASE.md0000644000175100017510000000226214123042137015217 0ustar vivekdebvivekdebNew versions of the gem are cut by the Postmark team, this is a quick guide to ensuring a smooth release. 1. Determine the next version of the gem by following the [SemVer](https://semver.org/) guidelines. 1. Verify all builds are passing on CircleCI for your branch. 1. Merge in your branch to master. 1. Update VERSION and lib/postmark/version.rb with the new version. 1. Update CHANGELOG.rdoc with a brief description of the changes. 1. Commit to git with a comment of "Bump version to x.y.z". 1. run `rake release` - This will push to github(with the version tag) and rubygems with the version in lib/postmark/version.rb. *Note that if you're on Bundler 1.17 there's a bug that hides the prompt for your OTP. If it hangs after adding the tag then it's asking for your OTP, enter your OTP and press Enter. Bundler 2.x and beyond resolved this issue. * 1. Verify the new version is on [github](https://github.com/wildbit/postmark-gem) and [rubygems](https://rubygems.org/gems/postmark). 1. Create a new release for the version on [Github releases](https://github.com/wildbit/postmark-gem/releases). 1. Add or update any related content to the [wiki](https://github.com/wildbit/postmark-gem/wiki). postmark-1.22.0/README.md0000644000175100017510000000516314123042137015077 0ustar vivekdebvivekdeb Postmark Logo # Postmark Ruby Gem [![Build Status](https://circleci.com/gh/wildbit/postmark-gem.svg?style=shield)](https://circleci.com/gh/wildbit/postmark-gem) [![Code Climate](https://codeclimate.com/github/wildbit/postmark-gem/badges/gpa.svg)](https://codeclimate.com/github/wildbit/postmark-gem) [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://www.opensource.org/licenses/MIT) [![Gem Version](https://badge.fury.io/rb/postmark.svg)](https://badge.fury.io/rb/postmark) Postmark allows you to send your emails with high delivery rates. It also includes detailed statistics. In addition, Postmark can parse incoming emails which are forwarded back to your application. This gem is the official wrapper for the [Postmark HTTP API](http://postmarkapp.com). ## Usage Please see the [wiki](https://github.com/wildbit/postmark-gem/wiki) for detailed instructions about sending email, using the bounce api and other Postmark API features. For details about Postmark API in general, please check out [Postmark developer docs](https://postmarkapp.com/developer). ## Requirements You will need a Postmark account, server and sender signature (or verified domain) set up to use it. For details about setup, check out [wiki pages](https://github.com/wildbit/postmark-gem/wiki/Getting-Started). If you plan using the library in a Rails project, check out the [postmark-rails](https://github.com/wildbit/postmark-rails/) gem, which is meant to integrate with ActionMailer. The plugin will try to use ActiveSupport JSon if it is already included. If not, it will attempt to use the built-in Ruby JSon library. You can also explicitly specify which one to be used, using following code: ``` ruby Postmark.response_parser_class = :Json # :ActiveSupport or :Yajl are also supported. ``` ## Installation You can use the library with or without a Bundler. With Bundler: ``` ruby gem 'postmark' ``` Without Bundler: ``` bash gem install postmark ``` ## Note on Patches/Pull Requests See [CONTRIBUTING.md](CONTRIBUTING.md) file for details. ## Issues & Comments Feel free to contact us if you encounter any issues with the library or Postmark API. Please leave all comments, bugs, requests and issues on the Issues page. ## License The Postmark Ruby library is licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php) license. Refer to the [LICENSE](https://github.com/wildbit/postmark-gem/blob/master/LICENSE) file for more information. ## Copyright Copyright © 2021 Wildbit LLC. postmark-1.22.0/LICENSE0000644000175100017510000000204014123042137014614 0ustar vivekdebvivekdebCopyright (c) 2018 Wildbit LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. postmark-1.22.0/Gemfile0000644000175100017510000000060214123042137015104 0ustar vivekdebvivekdebsource "http://rubygems.org" # Specify your gem's dependencies in postmark.gemspec gemspec group :test do gem 'rspec', '~> 3.7' gem 'rspec-its', '~> 1.2' gem 'fakeweb', :git => 'https://github.com/chrisk/fakeweb.git' gem 'fakeweb-matcher' gem 'mime-types' gem 'activesupport' gem 'i18n', '~> 0.6.0' gem 'yajl-ruby', '~> 1.0', :platforms => [:mingw, :mswin, :ruby] end postmark-1.22.0/CONTRIBUTING.md0000644000175100017510000000147614123042137016054 0ustar vivekdebvivekdeb# Before you report an issue or submit a pull request *If you are blocked or need help with Postmark, please [contact Postmark Support](https://postmarkapp.com/contact)*. For other, non-urgent cases you’re welcome to report a bug and/or contribute to this project. We will make our best effort to review your contributions and triage any bug reports in a timely fashion. If you’d like to submit a pull request: * Fork the project. * Make your feature addition or bug fix. * Add tests for it. This is important to prevent future regressions. * Do not mess with rakefile, version, or history. * Update the CHANGELOG, list your changes under Unreleased. * Update the README if necessary. * Write short, descriptive commit messages, following the format used in therepo. * Send a pull request. Bonus points for topic branches. postmark-1.22.0/CHANGELOG.rdoc0000644000175100017510000001725514123042137015765 0ustar vivekdebvivekdeb= Changelog == 1.22.0 * Disabled automatic retries of failed requests by default. You can enabled it by passing `max_retries` option to the client constructor. == 1.21.8 * Fixed passing and receiving SubscriptionManagementConfiguration when creating/updating message streams (#94). == 1.21.7 * Improved parsing recipients with Postmark::InactiveRecipientError.parse_recipients method == 1.21.6 * Improved error handling for email sending related to invalid email addresses == 1.21.5 * Added support for archiving/unarchiving message streams == 1.21.4 * Fixed Postmark::ApiClient#deliver_messages_with_templates (#104) == 1.21.3 * Remove default SSL version setting and rely on Net::HTTP/OpenSSL default. == 1.21.2 * Ensure sending via message stream uses the correct message stream == 1.21.1 * Fixed Postmark::ApiClient#get_message_streams == 1.21.0 * Added support for message streams and suppressions == 1.20.0 * Removed deprecated trigger endpoints == 1.19.2 Allow possibility to change TLS version for HTTP client. == 1.19.1 Bounce tags endoint removed, since it's no longer supported by API. == 1.19.0 Webhooks management support is added. == 1.18.0 Custom headers with any type of character casing is supported now. == 1.17.0 * Update sent email message properly and not altering it's Message-ID with Postmark unique message id. == 1.16.0 * Added support for template pushes. == 1.15.0 * Extended Mail::Message objects with support for Postmark templates. * Added ApiClient#deliver_message_with_template and ApiClient#deliver_messages_with_templates * Removed Rake from dependencies. == 1.14.0 * Added support for verifying DKIM/Return-Path. * Added support for searching inbound rules. * Updated README. == 1.13.0 * Changed default value returned by Mail::Message#metadata to a mutable hash (makes things easier for postmark-rails). * All message JSON payloads now include an empty metadata object even if metadata is unset. == 1.12.0 * Added support for attaching metadata to messages. == 1.11.0 * New, improved, and backwards-compatible gem errors (see README). * Added support for retrieving message clicks using the Messages API. * Added support for sending templated message in batches. * Added support for assigning link tracking mode via `Mail::Message` headers. == 1.10.0 * Fix a bug when open tracking flag is set to false by default, when open tracking flag is not set by a user. * Added support for link tracking == 1.9.1 * Fix a bug when port setting is not respected. * Made `Postmark::HttpClient#protocol` method public. == 1.9.0 * Added methods to access domains API endoints. == 1.8.1 * Technical release. Fixed gemspec. == 1.8.0 * Added missing `description` attribute to `Postmark::Bounce` #50. * Restricted `rake` dependency to `< 11.0.0` for Ruby < 1.9 via gemspec. * Restricted `json` dependency to `< 2.0.0` for Ruby < 2.0 via gemspec. == 1.7.1 * Explicitly set TLS version used by the client. == 1.7.0 * Add methods to access stats API endpoints. == 1.6.0 * Add methods to access new templates API endpoints. == 1.5.0 * Call API access strings tokens instead of keys. Keep backwards compatibility. == 1.4.3 * Fix a regression when using the gem with older mail gem versions not implementing Mail::Message#text?. == 1.4.2 * Fix a regression when using the gem with older mail gem versions introduced in 1.4.1. Affected mail gem versions are 2.5.3 and below. == 1.4.1 * Fix an exception when sending a Mail::Message containing quoted-printable parts with unicode characters. == 1.4.0 * Add descriptive User-Agent string. * Enable secure HTTP connections by default. == 1.3.1 * Allow track_open header to be String for compatibility with older versions of the mail gem. == 1.3.0 * Add support for TrackOpens flag of the Delivery API. * Add support for the Opens API. * Add support for the Triggers API. == 1.2.1 * Fixed a bug in Postmark::ApiClient causing #get_bounces to return unexpected value. == 1.2.0 * Added support for the Postmark Account API. * Added #bounces and #messages methods to Postmark::ApiClient returning Ruby enumerators. == 1.1.2 * Fixed HTTP verb used to update server info from POST to PUT to support the breaking change in the API. == 1.1.1 * Fixed inbound support for the Postmark Messages API. == 1.1.0 * Added support for inline attachments when using the Mail gem. * Added support for the Postmark Messages API. == 1.0.2 * Removed metaprogramming executed at runtime. [#37] * Fixed invalid check for a blank recipient. [#38] == 1.0.1 * Fixed an issue causing recipient names to disappear from "To", "Cc" and "Reply-To" headers when using with Mail library. == 1.0.0 * Introduced new instance-based architecture (see README for more details). * Removed TMail support. * Added support for sending emails in batches. * Added API to send emails without Mail library. * Introduced lock-free approach for Mail::Postmark delivery method. * Deprecated the Mail::Message#postmark_attachments method * Added Postmark::Inbound module. * Added integration tests. * Added support for the "server" endpoint of the Postmark API. * Improved unit test coverage. * Added more examples to the README file. * Added official JRuby support. * Fixed the inconsistent behaviour of Mail::Message#tag method added by the gem. * Added Mail::Message#delivered property and Mail::Message#delivered? predicate. * Added Mail::Message#postmark_response method. * Removed Postmark::AttachmentsFixForMail class (that hack no longer works). * Added Travis-CI for integration tests. == 0.9.19 * Added support for native attachments API provided by Ruby Mail library. == 0.9.18 * Fixed regression introduced by removing ActiveSupport#wrap in case when a Hash instance is passed. * Fixed broken Ruby 1.8.7 support (uninitialized constant Postmark::HttpClient::Mutex (NameError)). * Added unit tests for attachments handling. * Removed unneeded debug output from shared RSpec examples. == 0.9.17 * Removed date from gemspec. * Removed unneeded debug output when sending attachments. == 0.9.16 * Thread-safe HTTP requests. * Fixed inproper method of ActiveSupport::JSON detection. * Removed unexpected ActiveSupport dependency from Postmark::SharedMessageExtensions#postmark_attachments= method. * Used Markdown to format README. * Updated README. == 0.9.15 * Save a received MessageID in message headers. == 0.9.14 * Parse Subject and MessageID from the Bounce API response. == 0.9.13 * Added error_code to DeliveryError. * Added retries for Timeout::Error. == 0.9.12 * Fixed a problem of attachments processing when using deliver! method on Mail object. * Removed activesupport dependency for Postmark::AttachmentsFixForMail. * Added specs for AttachmentFixForMail. == 0.9.11 * Replaced Jeweler by Bundler. * Updated RSpec to 2.8. * Fixed specs. * Refactored the codebase. == 0.9.10 * Fixed Ruby 1.9 compatibility issue. == 0.9.9 * Added support for non-array reply_to addresses. == 0.9.8 * Fixed bug that caused unexpected multiple email deliveries on Ruby 1.9.2/Rails 3.0.7. == 0.9.7 * All delivery exceptions are now childs of Postmark::DeliveryError. Easier to rescue that way. == 0.9.6 * Fixed exception when content-type wasn't explicitly specified. * Removed tmail from the list of dependencies. == 0.9.5 * Fixed a problem of HTML content detection when using Mail gem. == 0.9.4 * Fixed bug that caused full name to be dropped from From address. == 0.9.3 * Removed all "try" calls from the code. It's not always available and not essential anyway. == 0.9.2 * Fixed "Illegal email address ']'" bug on Ruby 1.9 == 0.9.1 * Fixed TypeError when calling Bounce.all. * Fixed NoMethodError when trying to read bounce info. == 0.9.0 * Added support for attachments. == 0.8.0 * Added support for Rails 3. postmark-1.22.0/.ruby-version0000644000175100017510000000001114123042137016250 0ustar vivekdebvivekdebruby-2.6 postmark-1.22.0/.ruby-gemset0000644000175100017510000000001514123042137016053 0ustar vivekdebvivekdebpostmark-gem postmark-1.22.0/.rspec0000644000175100017510000000004014123042137014722 0ustar vivekdebvivekdeb--colour --format documentation postmark-1.22.0/.rake_tasks0000644000175100017510000000044714123042137015750 0ustar vivekdebvivekdebbuild check_dependencies check_dependencies:development check_dependencies:runtime clobber_rcov clobber_rdoc features gemcutter:release gemspec gemspec:generate gemspec:validate install rcov rdoc release rerdoc spec version version:bump:major version:bump:minor version:bump:patch version:write postmark-1.22.0/.gitignore0000644000175100017510000000011114123042137015574 0ustar vivekdebvivekdeb*.gem .bundle .env Gemfile.lock pkg/* .rvmrc .idea bin/* *.swp .DS_Store postmark-1.22.0/.document0000644000175100017510000000007414123042137015433 0ustar vivekdebvivekdebREADME.rdoc lib/**/*.rb bin/* features/**/*.feature LICENSE postmark-1.22.0/.circleci/0000755000175100017510000000000014123042137015446 5ustar vivekdebvivekdebpostmark-1.22.0/.circleci/config.yml0000644000175100017510000000305514123042137017441 0ustar vivekdebvivekdeb# In order for builds to pass, in CircleCI you must have following environment variables setub: # POSTMARK_API_KEY,POSTMARK_ACCOUNT_API_KEY,POSTMARK_CI_RECIPIENT,POSTMARK_CI_SENDER version: 2.1 workflows: ruby-tests: jobs: - unit-tests: matrix: parameters: version: ["2", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7"] - unit-tests-legacy: matrix: parameters: version: ["kneip/ree-1.8.7-2012.02","ruby:1.9.3","circleci/jruby:9"] orbs: ruby: circleci/ruby@0.1.2 jobs: unit-tests: parallelism: 1 parameters: version: type: string docker: - image: circleci/ruby:<< parameters.version >> steps: - checkout - run: name: Versions command: | echo "ruby: $(ruby --version)" - run: name: Install dependencies command: bundle install - run: name: Run tests command: bundle exec rake spec unit-tests-legacy: parallelism: 1 environment: BUNDLE_GEMFILE: ./gemfiles/Gemfile.legacy parameters: version: type: string docker: - image: << parameters.version >> steps: - checkout - run: name: Versions command: | echo "ruby: $(ruby --version)" - run: name: Install dependencies command: | gem install bundler --version 1.17.3 bundle install - run: name: Run tests command: bundle exec rake spec