public void ParseMultipleValidEmails(string email)
        {
            void verify(EmailAddress[] addr, params string[] expectedNames)
            {
                addr.Should().HaveCount(2);
                for (int i = 0; i < addr.Length; i++)
                {
                    addr[i].Email.Should().Be(email);
                    addr[i].Name.Should().Be(expectedNames[i]);
                }
            }

            verify(EmailAddressParser.ParseEmailAddresses($"\"First, middle, lastname\" <{email}>, \"Second\" <{email}>"), "First, middle, lastname", "Second");
            verify(EmailAddressParser.ParseEmailAddresses($"\"First, middle, lastname\" <{email}>, \"First\" <{email}>"), "First, middle, lastname", "First");
            verify(EmailAddressParser.ParseEmailAddresses($"\"First, middle, lastname\" <{email}>, {email}"), "First, middle, lastname", "");
        }
        /// <summary>
        /// Parses a raw multipart form from the stream as a sendgrid email according to https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/#default-parameters
        /// Adapted from https://github.com/KoditkarVedant/sendgrid-inbound
        /// </summary>
        public Email Parse(MemoryStream body)
        {
            var parser = MultipartFormDataParser.Parse(body, Encoding.UTF8);

            var charsets = JObject.Parse(parser.GetParameterValue("charsets", "{}"))
                           .Properties()
                           .ToDictionary(p => p.Name, p => Encoding.GetEncoding(p.Value.ToString()));

            var rawHeaders = parser
                             .GetParameterValue("headers", "")
                             .Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);

            var headers = rawHeaders
                          .Select(header =>
            {
                var splitHeader = header.Split(new[] { ": " }, StringSplitOptions.RemoveEmptyEntries);
                var key         = splitHeader[0];
                var value       = splitHeader.Length > 1 ? splitHeader[1] : null;
                return(new KeyValuePair <string, string>(key, value));
            }).ToArray();

            // Create a dictionary of parsers, one parser for each desired encoding.
            // This is necessary because MultipartFormDataParser can only handle one
            // encoding and SendGrid can use different encodings for parameters such
            // as "from", "to", "text" and "html".
            var encodedParsers = charsets
                                 .Where(c => c.Value != Encoding.UTF8)
                                 .Select(c => c.Value)
                                 .Distinct()
                                 .Select(encoding =>
            {
                body.Position = 0;
                return(new
                {
                    Encoding = encoding,
                    Parser = MultipartFormDataParser.Parse(body, encoding)
                });
            })
                                 .Union(new[]
            {
                new { Encoding = Encoding.UTF8, Parser = parser }
            })
                                 .ToDictionary(ep => ep.Encoding, ep => ep.Parser);

            // convert the raw formats so we can apply the correct encoders. the pre-parsed Sendgrid values (in Envelope) are forced to UTF-8
            var rawFrom = GetEncodedValue("from", charsets, encodedParsers, string.Empty);
            var from    = EmailAddressParser.ParseEmailAddress(rawFrom);

            var rawTo = GetEncodedValue("to", charsets, encodedParsers, string.Empty);
            var to    = EmailAddressParser.ParseEmailAddresses(rawTo);

            var rawCc = GetEncodedValue("cc", charsets, encodedParsers, string.Empty);
            var cc    = EmailAddressParser.ParseEmailAddresses(rawCc);

            // will have attachment1...attachmentX properties depending on attachment count
            // conver to array
            var attachmentInfoAsJObject = JObject.Parse(parser.GetParameterValue("attachment-info", "{}"));
            var attachments             = attachmentInfoAsJObject
                                          .Properties()
                                          .Select(prop =>
            {
                var attachment = prop.Value.ToObject <EmailAttachment>();
                attachment.Id  = prop.Name;

                var file = parser.Files.FirstOrDefault(f => f.Name == prop.Name);
                if (file != null)
                {
                    attachment.Base64Data = file.Data.ConvertToBase64();
                    if (string.IsNullOrEmpty(attachment.ContentType))
                    {
                        attachment.ContentType = file.ContentType;
                    }
                    if (string.IsNullOrEmpty(attachment.FileName))
                    {
                        attachment.FileName = file.FileName;
                    }
                }

                return(attachment);
            }).ToArray();

            return(new Email
            {
                // serializer friendly format
                Charsets = charsets.ToDictionary(p => p.Key, p => p.Value.WebName),
                Headers = headers,
                From = from,
                To = to,
                Cc = cc,
                Subject = GetEncodedValue("subject", charsets, encodedParsers, null),
                Html = GetEncodedValue("html", charsets, encodedParsers, null),
                Text = GetEncodedValue("text", charsets, encodedParsers, null),
                Attachments = attachments,
                SenderIp = GetEncodedValue("sender_ip", charsets, encodedParsers, null),
                SpamReport = GetEncodedValue("spam_report", charsets, encodedParsers, null),
                SpamScore = GetEncodedValue("spam_score", charsets, encodedParsers, null),
                Dkim = GetEncodedValue("dkim", charsets, encodedParsers, null),
                Spf = GetEncodedValue("SPF", charsets, encodedParsers, null)
            });
        }