The Problem
I have a system which uses SNS to send mail messages to a Lambda for additional processing. The Lambda is written in JavaScript, and it has really good test coverage, but there are message shapes that aren't included.
All of the messages which go through the system get persisted into S3 in SMTP format, but the Lambda receives a JavaScript object in a known SNS shape as its input, not an SMTP message.
The Solution
Write a quick-and-dirty script to generate JSON from the SMTP message which is close enough to what the Lambda's input would be, so that I can add additional messages to the Lambda's unit tests as needed.
The Gist
Here's a gist of the script.
#!/usr/bin/env perl | |
use Modern::Perl '2020'; | |
use Carp qw/croak/; | |
use JSON; | |
use Email::MIME; | |
use IO::File; | |
sub get_smtp { | |
my $filename = shift; | |
# Grab the file name as the only | |
my $fh = IO::File->new($filename, 'r') | |
|| croak "Failed to open file «$filename» for reading: $!"; | |
local $/ = undef | |
; # This enables slurp mode, so we can read the whole file in a single operation. | |
my $smtp_content = <$fh>; | |
my $smtp = Email::MIME->new($smtp_content); | |
# Since this is for test data generation only, we're simply pretending on some of this. | |
my $to_return = { | |
Records => [ | |
{ EventSource => 'aws:sns', | |
EventVersion => '1.0', | |
EventSubscriptionArn => | |
'arn:aws:sns:AWS_REGION:ACCOUNT_ID:TOPID_NAME:UUID', | |
Sns => { | |
Type => 'Notification', | |
MessageId => 'UUID', | |
TopicArn => 'arn:aws:sns:AWS_REGION:ACCOUNT_ID:TOPIC_NAME', | |
Subject => $smtp->header_raw('Subject'), | |
Timestamp => $smtp->header_raw('Date'), | |
SignatureVersion => '1', | |
Signature => $smtp->header_raw('DKIM-Signature') | |
|| 'SOME SIGNATURE', | |
SigningCertUrl => | |
'https://sns.AWS_REGION.amazonaws.com/SimpleNotificationService-KEY_ID.pem', | |
UnsubscribeUrl => | |
'https://sns.AWS_REGION.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:AWS_REGION:ACCOUNT_ID:TOPIC_NAME:UUID', | |
MessageAttributes => {}, | |
Message => encode_json( | |
{ | |
mail => { | |
headers => [ | |
map { | |
{ name => $_, | |
value => join('; ', $smtp->header_raw($_)) | |
} | |
} $smtp->header_names | |
], | |
destination => $smtp->header_raw('To'), | |
content => $smtp_content, | |
}, | |
notificationType => 'Received', | |
'receipt' => { | |
'timestamp' => $smtp->header_raw('Date'), | |
'processingTimeMillis' => 536, | |
'recipients' => [ $smtp->header_raw('To') ], | |
'spamVerdict' => { | |
'status' => $smtp->header_raw('X-SES-Spam-Verdict') | |
|| 'PASS' | |
}, | |
'virusVerdict' => { | |
'status' => $smtp->header_raw('X-SES-Virus-Verdict') | |
|| 'PASS' | |
}, | |
'spfVerdict' => { 'status' => 'PASS' }, | |
'dkimVerdict' => { 'status' => 'PASS' }, | |
'dmarcVerdict' => { 'status' => 'PASS' }, | |
'action' => { | |
'type' => 'SNS', | |
'topicArn' => | |
'arn:aws:sns:AWS_REGION:ACCOUNT_ID:TOPIC_NAME', | |
'encoding' => 'UTF8' | |
}, | |
}, | |
} | |
) | |
} | |
} | |
] | |
}; | |
return $to_return; | |
} | |
sub write_json { | |
my $filename = shift; | |
my $content_hashref = shift; | |
my $out_fh = IO::File->new($filename, 'w') | |
|| croak "Failed to open file «$filename» for writing: $!"; | |
print $out_fh encode_json($content_hashref); | |
return; | |
} | |
my $arg_filename = shift @ARGV || croak 'No file name provided.'; | |
write_json("$arg_filename.json", get_smtp($arg_filename)); | |
say "Wrote «$arg_filename.json»."; |
Conclusion
As I'm sure you noticed, that data isn't super clean. It's missing some key fields, so it's not 100%. However, my additional processing only needs the Message
content, the Subject
and a few fields from the receipt.
Thanks for looking at my quick-and-dirty, I had fun writing it. May your quick-and-dirties bring you joy as well.
(Update: Made a couple code fixes to the Gist)
Top comments (0)