kns_email_endpoint 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +71 -0
  4. data/Guardfile +10 -0
  5. data/LICENCE +21 -0
  6. data/README.md +1 -0
  7. data/Rakefile +2 -0
  8. data/kns_email_endpoint.gemspec +39 -0
  9. data/lib/kns_email_endpoint/configuration.rb +87 -0
  10. data/lib/kns_email_endpoint/connection.rb +97 -0
  11. data/lib/kns_email_endpoint/core_extensions/class.rb +44 -0
  12. data/lib/kns_email_endpoint/core_extensions/hash.rb +14 -0
  13. data/lib/kns_email_endpoint/core_extensions/imap.rb +36 -0
  14. data/lib/kns_email_endpoint/core_extensions/message.rb +26 -0
  15. data/lib/kns_email_endpoint/core_extensions/pop3.rb +0 -0
  16. data/lib/kns_email_endpoint/core_extensions/test_retriever.rb +42 -0
  17. data/lib/kns_email_endpoint/email_endpoint.rb +88 -0
  18. data/lib/kns_email_endpoint/message_state.rb +89 -0
  19. data/lib/kns_email_endpoint/process_email.rb +123 -0
  20. data/lib/kns_email_endpoint/storage/abstract_storage.rb +93 -0
  21. data/lib/kns_email_endpoint/storage/file_storage.rb +74 -0
  22. data/lib/kns_email_endpoint/storage/memcache_storage.rb +72 -0
  23. data/lib/kns_email_endpoint/storage/storage.rb +15 -0
  24. data/lib/kns_email_endpoint/version.rb +3 -0
  25. data/lib/kns_email_endpoint.rb +19 -0
  26. data/spec/a18x34/.app +4 -0
  27. data/spec/a18x34/a18x34.krl +91 -0
  28. data/spec/lib/kns_email_endpoint/configuration_spec.rb +44 -0
  29. data/spec/lib/kns_email_endpoint/connection_spec.rb +133 -0
  30. data/spec/lib/kns_email_endpoint/email_endpoint_spec.rb +97 -0
  31. data/spec/lib/kns_email_endpoint/message_state_spec.rb +69 -0
  32. data/spec/lib/kns_email_endpoint/process_email_spec.rb +67 -0
  33. data/spec/lib/kns_email_endpoint/storage/file_storage_spec.rb +16 -0
  34. data/spec/lib/kns_email_endpoint/storage/memcache_storage_spec.rb +17 -0
  35. data/spec/lib/kns_email_endpoint/storage/shared_storage.rb +65 -0
  36. data/spec/lib/kns_email_endpoint/storage/storage_spec.rb +14 -0
  37. data/spec/test_config_file.yml +65 -0
  38. data/spec/test_email.eml +43 -0
  39. metadata +181 -0
@@ -0,0 +1,133 @@
1
+ require 'lib/kns_email_endpoint'
2
+
3
+ RSpec.configure do |c|
4
+ #c.filter_run :filter => true
5
+ end
6
+
7
+
8
+ module KNSEmailEndpoint
9
+ # Select which connection to test against
10
+ # - test: Doesn't actually connect to any mail system so it's fast
11
+ # - gmail: Uses a test gmail account so it's slow
12
+ # All tests should work for either test or gmail.
13
+ $CONN_TO_TEST = "test"
14
+ test_config_file = File.join(File.dirname(__FILE__), '../..', 'test_config_file.yml')
15
+ Configuration.load_from_file test_config_file
16
+
17
+
18
+ describe ConnectionFactory, :filter => true do
19
+
20
+ before :all do
21
+
22
+ class TestConnection
23
+ include ConnectionFactory
24
+
25
+ def initialize
26
+ begin
27
+ setup_mail_connections $CONN_TO_TEST
28
+ rescue => e
29
+ ap e.message
30
+ ap e.backtrace
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ @conn = TestConnection.new
37
+
38
+ end
39
+
40
+ it "should have a valid name" do
41
+ @conn.name.should eql $CONN_TO_TEST
42
+ end
43
+
44
+ it "should have a valid log" do
45
+ @conn.conn_log.class.should eql Logger
46
+ end
47
+
48
+ it "should have a retriever method" do
49
+ @conn.retriever.should_not be_nil
50
+ [Mail::TestRetriever, Mail::IMAP, Mail::POP3].
51
+ should include @conn.retriever.class
52
+ end
53
+
54
+ it "should have a sender method" do
55
+ @conn.sender.should_not be_nil
56
+ [Mail::TestMailer, Mail::SMTP].
57
+ should include @conn.sender.class
58
+ end
59
+
60
+ end
61
+
62
+ describe Connection, :filter => true do
63
+ before :all do
64
+ @conn = Connection.new $CONN_TO_TEST
65
+ end
66
+
67
+ it "should have a valid name" do
68
+ @conn.name.should eql $CONN_TO_TEST
69
+ end
70
+
71
+ describe "sending messages", :filter => true do
72
+ before :all do
73
+ @conn.retriever.delete_all
74
+ @message = Mail.new do
75
+ from 'kynetx.endpoint.test@gmail.com'
76
+ to 'kynetx.endpoint.test@gmail.com'
77
+ subject 'testing 123'
78
+ body 'Testing 123'
79
+ end
80
+ end
81
+
82
+ it "should send an email" do
83
+ begin
84
+ lambda { @conn.sender.deliver!(@message) }.
85
+ should_not raise_error
86
+ rescue => e
87
+ ap e.message
88
+ ap e.backtrace
89
+ end
90
+ end
91
+
92
+ # Copy the sent emails to the retrievers email to simulate
93
+ # emailing one's self. Only needed if using "test"
94
+ after :all do
95
+ Mail::TestRetriever.emails = Mail::TestMailer.deliveries if @conn.name == "test"
96
+ end
97
+
98
+ end
99
+
100
+ describe "receiving messages", :filter => false do
101
+ before :all do
102
+ @message = @conn.retriever.find(:count => 1)
103
+ end
104
+
105
+ it "should get mail from test" do
106
+ messages = @conn.retriever.find :count => 2
107
+ messages.should_not be_empty
108
+ end
109
+
110
+
111
+ it "should be a valid message" do
112
+ @message.to.first.should eql 'kynetx.endpoint.test@gmail.com'
113
+ end
114
+
115
+ it "should allow me to delete the message" do
116
+ # demonstrates how to use find to delete messages
117
+ begin
118
+ @conn.retriever.find(:count => 1, :delete_after_find => true) do |msg|
119
+ msg.mark_for_delete = true
120
+ end
121
+ messages = @conn.retriever.find
122
+ messages.should be_empty
123
+ rescue => e
124
+ ap e.message
125
+ ap e.backtrace
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+
132
+ end
133
+ end
@@ -0,0 +1,97 @@
1
+ require 'lib/kns_email_endpoint'
2
+ require 'ap'
3
+ #$KNS_ENDPOINT_DEBUG = true
4
+
5
+ module KNSEmailEndpoint
6
+ test_config_file = File.join(File.dirname(__FILE__), '../..', 'test_config_file.yml')
7
+ Configuration.load_from_file(test_config_file)
8
+
9
+
10
+ describe EmailEndpoint do
11
+ include ConnectionFactory
12
+
13
+
14
+ before :all do
15
+ setup_mail_connections "test"
16
+
17
+ @mail = EmailEndpoint.new("test", {
18
+ :ruleset => :a18x34,
19
+ :environment => :development,
20
+ :use_session => true,
21
+ :logging => true
22
+ }, sender)
23
+
24
+ MessageState.set_storage :memcache, {}
25
+
26
+ @test_message_file = File.join(File.dirname(__FILE__), '../..', 'test_email.eml')
27
+ @test_message = Mail.new(File.open(@test_message_file, "r").read);
28
+ end
29
+
30
+
31
+ describe "Events" do
32
+ it "should send an mail received event" do
33
+ lambda {@mail.received(:msg => @test_message, :test_rule => "parts", :extract_label => true)}.
34
+ should_not raise_error
35
+ @mail.status.should eql :processed
36
+ end
37
+
38
+ it "should receive a delete directive" do
39
+ @mail.received(:msg => @test_message, :test_rule => "delete_me")
40
+ @mail.status.should eql :deleted
41
+ end
42
+
43
+ describe "reply" do
44
+ before :all do
45
+ Mail::TestMailer.deliveries = []
46
+ @mail.received(:msg => @test_message, :test_rule => "reply")
47
+ end
48
+
49
+ subject { @mail.status }
50
+ it { should eql :replied }
51
+
52
+ it "should be included in deliveries" do
53
+ reply = Mail::TestMailer.deliveries.first
54
+ reply.to.first.should eql "mjf@kynetx.com"
55
+ end
56
+
57
+ end
58
+
59
+ describe "reply and delete" do
60
+ before :all do
61
+ @mail.received(:msg => @test_message, :test_rule => "reply and delete")
62
+ end
63
+
64
+ subject { @mail.status }
65
+ it { should eql :deleted }
66
+ end
67
+
68
+ describe "forward" do
69
+ before :all do
70
+ Mail::TestMailer.deliveries = []
71
+ @mail.received(:msg => @test_message, :test_rule => "forward", :forward_to => "test@example.com")
72
+ end
73
+
74
+ subject { @mail }
75
+ its (:status) { should eql :forwarded }
76
+
77
+ it "should be included in deliveries" do
78
+ forward = Mail::TestMailer.deliveries.first
79
+ forward.to.first.should eql "test@example.com"
80
+ end
81
+ end
82
+
83
+ describe "forward and delete" do
84
+ before :all do
85
+ @mail.received(:msg => @test_message, :test_rule => "forward and delete", :forward_to => "test@example.com")
86
+ end
87
+
88
+ subject { @mail.status }
89
+ it { should eql :deleted }
90
+ end
91
+
92
+ end
93
+
94
+
95
+
96
+ end
97
+ end
@@ -0,0 +1,69 @@
1
+ require 'lib/kns_email_endpoint'
2
+
3
+ module KNSEmailEndpoint
4
+ MessageState.set_storage :memcache, {}
5
+
6
+ describe MessageState do
7
+
8
+ before :all do
9
+ @message = Mail.new do
10
+ to "test@example.com"
11
+ from "joe@example.com"
12
+ subject "This is a test"
13
+ body "This is a test body"
14
+ end
15
+ @message.add_message_id('1234')
16
+
17
+ @message_state = MessageState.new("test", @message)
18
+ @message_state.reset_state
19
+ end
20
+
21
+ it "should have an initial state of :unprocessed" do
22
+ @message_state.state.should eql :unprocessed
23
+ end
24
+
25
+ it "should have a message_id" do
26
+ @message.has_message_id?.should eql true
27
+ end
28
+
29
+ it "should have a unique_id" do
30
+ @message_state.unique_id.should_not be_nil
31
+ end
32
+
33
+ it "should have a retry count of 0" do
34
+ @message_state.retry_count.should eql 0
35
+ end
36
+
37
+ it "should have a message_id" do
38
+ @message_state.message_id.should eql "1234"
39
+ end
40
+
41
+ it "should not allow a message without a message_id" do
42
+ lambda { MessageState.new('test', Mail.new) }.
43
+ should raise_error
44
+ end
45
+
46
+ it "should allow me to update the state" do
47
+ @message_state.state = :new_state
48
+ @message_state.state.should eql :new_state
49
+ end
50
+
51
+ it "should allow me to increment the retry count" do
52
+ rc = @message_state.retry_count
53
+ @message_state.retry
54
+ @message_state.retry_count.should eql rc + 1
55
+ end
56
+
57
+ it "should allow me to reset the retry count" do
58
+ @message_state.retry
59
+ @message_state.reset
60
+ @message_state.retry_count.should eql 0
61
+ end
62
+
63
+ it "should allow me to delete the message" do
64
+ @message_state.delete
65
+ @message_state.state.should == :deleted
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,67 @@
1
+ require 'lib/kns_email_endpoint'
2
+
3
+ $CONN_TO_TEST = "test"
4
+ $NUM_TEST_EMAIL = 10 # make sure you tone this down if testing on a connection other than "test"
5
+
6
+ module KNSEmailEndpoint
7
+ test_config_file = File.join(File.dirname(__FILE__), '../..', 'test_config_file.yml')
8
+ Configuration.load_from_file(test_config_file)
9
+
10
+ describe ProcessEmail do
11
+
12
+
13
+
14
+ before :all do
15
+ # Setup some email to test against
16
+ @conn = Connection.new($CONN_TO_TEST)
17
+ @conn.retriever.delete_all
18
+ Mail::TestMailer.deliveries = []
19
+ $NUM_TEST_EMAIL.times do |x|
20
+ @conn.sender.deliver!(Mail.new do
21
+ to "kynetx.endpoint.test@gmail.com"
22
+ from "kynetx.endpoint.test@gmail.com"
23
+ subject "Testing #{x}"
24
+ body "Testing body #{x}"
25
+ message_id "TESTEMAIL::#{x}"
26
+ end)
27
+ end
28
+
29
+ Mail::TestRetriever.emails = Mail::TestMailer.deliveries if @conn.name == "test"
30
+
31
+ end
32
+
33
+ it "should have 3 email messages ready for testing" do
34
+ @conn.retriever.find.size.should eql $NUM_TEST_EMAIL
35
+ end
36
+
37
+
38
+ describe "go" do
39
+ before :all do
40
+
41
+ end
42
+
43
+ it "should go" do
44
+ #lambda { ProcessEmail.go(@conn) }.should_not raise_error
45
+ begin
46
+ ProcessEmail.go(@conn)
47
+ rescue => e
48
+ ap e.message
49
+ ap e.backtrace
50
+ end
51
+ Mail::TestRetriever.emails.should be_empty
52
+ end
53
+
54
+ end
55
+
56
+ describe "flush" do
57
+ it "should flush" do
58
+ ProcessEmail.flush.should eql true
59
+ end
60
+ end
61
+
62
+
63
+
64
+
65
+
66
+ end
67
+ end
@@ -0,0 +1,16 @@
1
+ require 'lib/kns_email_endpoint'
2
+ require 'spec/lib/kns_email_endpoint/storage/shared_storage'
3
+
4
+
5
+ describe KNSEmailEndpoint::Storage::FileStorage do
6
+ storage_dir = "/tmp/kns_email_endpoint/test_file_storage"
7
+ FileUtils.mkdir_p storage_dir
8
+ settings = {
9
+ :file_location => storage_dir
10
+ }
11
+ storage = KNSEmailEndpoint::Storage.get_storage(:file, settings)
12
+
13
+ it_should_behave_like "a storage engine", storage
14
+
15
+
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'lib/kns_email_endpoint'
2
+ require 'spec/lib/kns_email_endpoint/storage/shared_storage'
3
+
4
+
5
+ describe KNSEmailEndpoint::Storage::MemcacheStorage do
6
+ storage_dir = "/tmp/kns_email_endpoint/test_file_storage"
7
+ FileUtils.mkdir_p storage_dir
8
+ settings = {
9
+ :file_location => storage_dir
10
+ }
11
+ storage = KNSEmailEndpoint::Storage.get_storage(:memcache, settings)
12
+ storage.delete_all
13
+
14
+ it_should_behave_like "a storage engine", storage
15
+
16
+
17
+ end
@@ -0,0 +1,65 @@
1
+ shared_examples_for "a storage engine" do |storage|
2
+
3
+ test_unique_id = "a1235456789"
4
+
5
+ describe "CRUD" do
6
+
7
+ it 'should delete a record' do
8
+ storage.find(test_unique_id)
9
+ if storage.unique_id
10
+ storage.delete.should eql true
11
+ end
12
+ end
13
+
14
+ it 'should create a new file' do
15
+ storage.create({
16
+ :unique_id => test_unique_id,
17
+ :message_id => "abcd"
18
+ })
19
+
20
+ storage.unique_id.should eql test_unique_id
21
+ end
22
+
23
+ it "should find a unique_id" do
24
+ s = storage.find(test_unique_id)
25
+ s.unique_id.should eql test_unique_id
26
+ end
27
+
28
+
29
+ it "should return a valid message_id" do
30
+ storage.message_id.should eql 'abcd'
31
+ end
32
+
33
+ it "should increment the retry count" do
34
+ storage.retry_count = 1
35
+ storage.retry_count.should eql 1
36
+ end
37
+
38
+ it "should be able to set the state" do
39
+ storage.state = :finished
40
+ storage.state.should eql :finished
41
+ end
42
+
43
+ it "should use find_or_create method to create a row" do
44
+ alt_unique_id = 'b123456789'
45
+ s = storage.find_or_create({:unique_id => alt_unique_id, :message_id => 'abcd'})
46
+ s.unique_id.should eql alt_unique_id
47
+ end
48
+
49
+ it "should raise an error if unique_id is not provided" do
50
+ lambda {storage.find_or_create({:message_id => ""})}.
51
+ should raise_error
52
+ end
53
+
54
+ it "should raise an error if message_id is not provided" do
55
+ lambda {storage.find_or_create({:unique_id => "abc"})}.
56
+ should raise_error
57
+ end
58
+
59
+ it "should return nil if not found" do
60
+ s = storage.find('mombojombo')
61
+ s.should be_nil
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,14 @@
1
+ require 'lib/kns_email_endpoint'
2
+
3
+
4
+ describe KNSEmailEndpoint::Storage do
5
+ it "should get a FileStorage class" do
6
+ KNSEmailEndpoint::Storage.get_storage(:file, {:file_location => "/tmp"}).class.
7
+ should eql KNSEmailEndpoint::Storage::FileStorage
8
+ end
9
+
10
+ it "should get a MemcacheStorage class" do
11
+ KNSEmailEndpoint::Storage.get_storage(:memcache, {}).class.
12
+ should eql KNSEmailEndpoint::Storage::MemcacheStorage
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ logdir: /tmp/email_endpoint # Set to log directory
2
+ logginglevel: debug # can be: debug, info, warn, error, fatal
3
+ storage:
4
+ engine: memcache
5
+ host: localhost
6
+ port: 11211
7
+ ttl: nil # Time to live in seconds, nil = forever
8
+ # Other storage example
9
+ # engine: file
10
+ # file_location: # location to store the message state, should be a directory. It will be created if it doesn't exist
11
+ workthreads: 5 # Set to number of work threads based on observed performance of Kynetx application. *Warning* incorrectly setting this value to low/high can significantly impact performance of the application
12
+ polldelayinseconds: 30 # Recommended setting is '30'
13
+ connections:
14
+ - name: gmail # Connection name. Should be set to human readable name
15
+ appid: a18x34 # Appid of Kynetx application called to process email
16
+ appversion: dev #prod or dev - defaults to prod
17
+ processmode: repeat #repeat or single - defaults to single
18
+ specialgeneral: false #puts this connection into general mode - not for general use.
19
+ args: #optional arguments to publish with each mail recieved event
20
+ test_rule: parts
21
+ #arg2: value2
22
+ incoming:
23
+ method: imap # imap or pop3
24
+ host: imap.gmail.com #Hostname of the IMAP server (e.g. hostname.domain.tld)
25
+ username: kynetx.endpoint.test@gmail.com # Username for IMAP server
26
+ password: kynetx3mail # Password for IMAP server user
27
+ mailbox: INBOX # Name of mailbox being watched (e.g. INBOX)
28
+ port: 993
29
+ ssl: true
30
+ smtp:
31
+ method: smtp
32
+ host: smtp.gmail.com # Hostname of SMTP mail server (e.g. hostname.domain.tld)
33
+ username: kynetx.endpoint.test@gmail.com # Username for SMTP. *Note* only needed if SMTP authentication is turned on at SMTP - Check with email provider
34
+ password: kynetx3mail # Password for SMTP user
35
+ port: 587 # SMTP port number *Note* usually this is set to port 25, but could be any port depending on email provider - Check with email provider
36
+ helo_domain: cpe.cableone.net # Domain name of sending domain *Note* this domain must match the domain of the sender and should be resolvable via DNS (i.e. don't make it up)
37
+ authentication: plain # can be login, plain, cram_md5, or it can be commented out if server doesn't require auth
38
+ tls: true
39
+ logfile: gmail.log # Name of logfile for this connection
40
+ - name: test
41
+ appid: a18x34
42
+ appversion: dev
43
+ processmode: repeat
44
+ max_retry_count: 3
45
+ args:
46
+ test_rule: delete_me
47
+ #arg2: value2
48
+ incoming:
49
+ method: test
50
+ host: localhost
51
+ username: doesntmatter
52
+ password: doesntmatter
53
+ mailbox: INBOX
54
+ port: 0
55
+ ssl: true
56
+ smtp:
57
+ method: test
58
+ host: localhost
59
+ username: doesntmatter
60
+ password: doesntmatter
61
+ port: 0
62
+ helo_domain: kynetx.com
63
+ authentication: plain
64
+ tls: true
65
+ logfile: test.log
@@ -0,0 +1,43 @@
1
+ Delivered-To: mike.farmer.imap@gmail.com
2
+ Received: by 10.220.178.141 with SMTP id bm13cs253841vcb;
3
+ Thu, 6 Jan 2011 13:54:52 -0800 (PST)
4
+ Received: from mr.google.com ([10.227.146.149])
5
+ by 10.227.146.149 with SMTP id h21mr18229971wbv.43.1294350892087 (num_hops = 1);
6
+ Thu, 06 Jan 2011 13:54:52 -0800 (PST)
7
+ Received: by 10.227.146.149 with SMTP id h21mr14739590wbv.43.1294350892052;
8
+ Thu, 06 Jan 2011 13:54:52 -0800 (PST)
9
+ X-Forwarded-To: mike.farmer.imap@gmail.com
10
+ X-Forwarded-For: mike.farmer@gmail.com mike.farmer.imap@gmail.com
11
+ Delivered-To: mike.farmer+test@gmail.com
12
+ Received: by 10.227.133.78 with SMTP id e14cs66419wbt;
13
+ Thu, 6 Jan 2011 13:54:51 -0800 (PST)
14
+ Received: by 10.42.164.135 with SMTP id g7mr80494icy.504.1294350890863;
15
+ Thu, 06 Jan 2011 13:54:50 -0800 (PST)
16
+ Return-Path: <mjf@kynetx.com>
17
+ Received: from mail.kynetx.com (mail.kynetx.com [209.41.75.70])
18
+ by mx.google.com with ESMTP id r10si181340ict.24.2011.01.06.13.54.50;
19
+ Thu, 06 Jan 2011 13:54:50 -0800 (PST)
20
+ Received-SPF: pass (google.com: domain of mjf@kynetx.com designates 209.41.75.70 as permitted sender) client-ip=209.41.75.70;
21
+ Authentication-Results: mx.google.com; spf=pass (google.com: domain of mjf@kynetx.com designates 209.41.75.70 as permitted sender) smtp.mail=mjf@kynetx.com
22
+ Received: from localhost (localhost.localdomain [127.0.0.1])
23
+ by mail.kynetx.com (Postfix) with ESMTP id E67511D7A26;
24
+ Thu, 6 Jan 2011 14:54:49 -0700 (MST)
25
+ X-Virus-Scanned: amavisd-new at corpserver.kobj.net
26
+ Received: from mail.kynetx.com ([127.0.0.1])
27
+ by localhost (mail.kynetx.com [127.0.0.1]) (amavisd-new, port 10024)
28
+ with ESMTP id KdeiV1dT7FrP; Thu, 6 Jan 2011 14:54:49 -0700 (MST)
29
+ Received: from [192.168.1.133] (24-119-21-78.cpe.cableone.net [24.119.21.78])
30
+ by mail.kynetx.com (Postfix) with ESMTPSA id 707281D7A23;
31
+ Thu, 6 Jan 2011 14:54:47 -0700 (MST)
32
+ From: Michael Farmer <mjf@kynetx.com>
33
+ Content-Type: text/plain; charset=us-ascii
34
+ Content-Transfer-Encoding: 7bit
35
+ Subject: Please ignore this
36
+ Date: Thu, 6 Jan 2011 14:54:45 -0700
37
+ Message-Id: <76B78C79-F47D-4083-974B-6823F1CB86F4@kynetx.com>
38
+ To: Sara Farmer <sarafarmer@gmail.com>,
39
+ mike.farmer+test@gmail.com
40
+ Mime-Version: 1.0 (Apple Message framework v1082)
41
+ X-Mailer: Apple Mail (2.1082)
42
+
43
+ Just testing out my email endpoint. Just ignore this please :)