diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98751f9..0ea3afc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 5.0.0
+* minor interface changes. Some are now const / final objects.
+* added a lot of tests
+* names of addresses may contain unicode characters now
+ still no punycode support!
+
## 4.0.0
* null safety and cleanups
Thanks: https://github.com/bsutton
diff --git a/README.md b/README.md
index 0ddb558..75090da 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,9 @@
# mailer
-
**mailer** is an easy to use library for composing and sending emails in Dart.
Mailer supports file attachments and HTML emails.
-
-## mailer2 and mailer3
-
-`mailer2` and `mailer3` on pub.dart are forks of this project.
-
-`mailer` was not well maintained and `mailer2` and `mailer3` had some important fixes.
-
-Currently `mailer` should include all known bug-fixes and AFAIK there is
-no reason to use `mailer2` or `mailer3`.
-
-
-## Dart2 support
-
-Support for dart2 has been added in version ^1.2.0
-
-Version ^2.0.0 is a rewrite (it too supports dart1.x and dart2).
-
-Even though the API for ^2.0.0 has slightly changed, *most* programs will probably
-continue to work with deprecation warnings.
-
## SMTP definitions
Mailer provides configurations for a few common SMTP servers.
@@ -36,7 +15,8 @@ Please create merge requests for missing configurations.
* Export the newly created SMTP server in `lib/smtp_server.dart`
* Create a pull request.
-In a lot of cases you will find a configuration in [legacy.dart](https://github.com/kaisellgren/mailer/blob/v2/lib/legacy.dart)
+In a lot of cases you will find a configuration
+in [legacy.dart](https://github.com/kaisellgren/mailer/blob/v2/lib/legacy.dart)
## Features
@@ -53,8 +33,8 @@ In a lot of cases you will find a configuration in [legacy.dart](https://github.
* Correct encoding of non ASCII mail addresses.
* Reintegrate address validation from version 1.*
* Improve Header types. (see [ir_header.dart](lib/src/smtp/internal_representation/ir_header.dart))
-We should choose the correct header based on the header name.
-Known headers (`list-unsubscribe`,...) should have their own subclass.
+ We should choose the correct header based on the header name.
+ Known headers (`list-unsubscribe`,...) should have their own subclass.
* Improve documentation.
## Examples
@@ -76,7 +56,7 @@ main() async {
// final smtpServer = SmtpServer('smtp.domain.com');
// See the named arguments of SmtpServer for further configuration
// options.
-
+
// Create our message.
final message = Message()
..from = Address(username, 'Your name')
@@ -97,8 +77,8 @@ main() async {
}
}
// DONE
-
-
+
+
// Let's send another message using a slightly different syntax:
//
// Addresses without a name part can be set directly.
@@ -109,32 +89,37 @@ main() async {
// `new Address('destination@example.com')` is equivalent to
// adding the mail address as `String`.
final equivalentMessage = Message()
- ..from = Address(username, 'Your name')
- ..recipients.add(Address('destination@example.com'))
- ..ccRecipients.addAll([Address('destCc1@example.com'), 'destCc2@example.com'])
- ..bccRecipients.add('bccAddress@example.com')
- ..subject = 'Test Dart Mailer library :: ๐ :: ${DateTime.now()}'
- ..text = 'This is the plain text.\nThis is line 2 of the text part.'
- ..html = "
Test
\nHey! Here's some HTML content
";
-
+ ..from = Address(username, 'Your name ๐')
+ ..recipients.add(Address('destination@example.com'))
+ ..ccRecipients.addAll([Address('destCc1@example.com'), 'destCc2@example.com'])
+ ..bccRecipients.add('bccAddress@example.com')
+ ..subject = 'Test Dart Mailer library :: ๐ :: ${DateTime.now()}'
+ ..text = 'This is the plain text.\nThis is line 2 of the text part.'
+ ..html = 'Test
\nHey! Here is some HTML content
'
+ ..attachments = [
+ FileAttachment(File('exploits_of_a_mom.png'))
+ ..location = Location.inline
+ ..cid = ''
+ ];
+
final sendReport2 = await send(equivalentMessage, smtpServer);
-
+
// Sending multiple messages with the same connection
//
// Create a smtp client that will persist the connection
var connection = PersistentConnection(smtpServer);
-
+
// Send the first message
await connection.send(message);
-
+
// send the equivalent message
await connection.send(equivalentMessage);
-
+
// close the connection
await connection.close();
-
}
```
## License
+
This library is licensed under MIT.
diff --git a/lib/src/entities/address.dart b/lib/src/entities/address.dart
index 806c5e9..b5e1f8d 100644
--- a/lib/src/entities/address.dart
+++ b/lib/src/entities/address.dart
@@ -1,16 +1,17 @@
class Address {
- String? name;
- String? mailAddress;
+ final String? name;
+ final String mailAddress;
- Address([this.mailAddress, this.name]);
+ const Address(this.mailAddress, [this.name]);
+
+ /// The name used to output to SMTP server.
+ /// Implementation can override it to pre-process the name before sending.
+ /// For example, providing a default name for certain address, or quoting it.
+ String? get sanitizedName => name;
+ /// The address used to output to SMTP server.
+ /// Implementation can override it to pre-process the address before sending
+ String get sanitizedAddress => mailAddress;
- /// Generates an address that must conform to RFC 5322.
- /// For example, `name `, ``
- /// and `foo.domain.com`.
@override
- String toString() {
- var fromName = name ?? '';
- // ToDo base64 fromName (add _IRMetaInformation as argument)
- return '$fromName <$mailAddress>';
- }
+ String toString() => "${name ?? ''} <$mailAddress>";
}
diff --git a/lib/src/entities/attachment.dart b/lib/src/entities/attachment.dart
index 7b8b675..d376ae6 100644
--- a/lib/src/entities/attachment.dart
+++ b/lib/src/entities/attachment.dart
@@ -16,11 +16,15 @@ enum Location {
/// Represents a single email attachment.
///
/// You may specify a [File], a [Stream] or just a [String] of [data].
-/// [cid] allows you to specify the content id.
+/// [cid] allows you to specify the content id for html inlining.
///
/// When [location] is set to [Location.inline] The attachment (usually image)
/// can be referenced using:
/// `cid:yourCid`. For instance: `
`
+///
+/// [cid] must contain an `@` and be inside `<` and `>`.
+/// The cid: `` can then be referenced inside your html as:
+/// `
`
abstract class Attachment {
String? cid;
Location location = Location.attachment;
diff --git a/lib/src/smtp/capabilities.dart b/lib/src/smtp/capabilities.dart
index a4aad55..d0323b6 100644
--- a/lib/src/smtp/capabilities.dart
+++ b/lib/src/smtp/capabilities.dart
@@ -1,3 +1,17 @@
+import 'package:meta/meta.dart';
+
+@visibleForTesting
+Capabilities capabilitiesForTesting(
+ {bool startTls = false,
+ bool smtpUtf8 = false,
+ bool authPlain = true,
+ bool authLogin = false,
+ bool authXoauth2 = false,
+ List all = const []}) {
+ return Capabilities._values(
+ startTls, smtpUtf8, authPlain, authLogin, authXoauth2, all);
+}
+
class Capabilities {
final bool startTls;
final bool smtpUtf8;
diff --git a/lib/src/smtp/internal_representation/conversion.dart b/lib/src/smtp/internal_representation/conversion.dart
index aed0ecb..5697741 100644
--- a/lib/src/smtp/internal_representation/conversion.dart
+++ b/lib/src/smtp/internal_representation/conversion.dart
@@ -1,6 +1,10 @@
import 'dart:async';
import 'dart:convert' as convert;
+import 'package:logging/logging.dart';
+
+final Logger _logger = Logger('conversion');
+
const String eol = '\r\n';
List to8(String s) => convert.utf8.encode(s);
@@ -71,22 +75,31 @@ Iterable> split(List data, int maxLength,
Stream> _splitS(
Stream> dataS, int splitOver, int maxLength) {
var currentLineLength = 0;
+ var insertEol = false;
var sc = StreamController>();
void processData(List data) {
+ _logger.finest('_splitS: <- ${data.length} bytes currentLineLength: $currentLineLength');
if (data.length + currentLineLength > maxLength) {
var targetLength = maxLength ~/ 2;
if (targetLength + currentLineLength > maxLength) {
targetLength = maxLength - currentLineLength;
}
+ _logger.finest('_splitS: > maxLength ($maxLength) Splitting into $targetLength parts');
split(data, targetLength, avoidUtf8Cut: false).forEach(processData);
} else if (data.length + currentLineLength > splitOver) {
+ _logger.finest('_splitS: inside splitOver ($splitOver) and maxLength ($maxLength) window.');
// We are now over splitOver but not too long. Perfect.
+ if (insertEol) sc.add(eol8);
sc.add(data);
- sc.add(eol8);
currentLineLength = 0;
+ insertEol = true;
} else {
+ _logger.finest('_splitS: below splitOver ($splitOver).');
// We are still below splitOver
+ if (insertEol) sc.add(eol8);
+ insertEol = false;
+
sc.add(data);
currentLineLength += data.length;
}
diff --git a/lib/src/smtp/internal_representation/internal_representation.dart b/lib/src/smtp/internal_representation/internal_representation.dart
index 3779652..c242a6d 100644
--- a/lib/src/smtp/internal_representation/internal_representation.dart
+++ b/lib/src/smtp/internal_representation/internal_representation.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert' as convert;
import 'package:intl/intl.dart';
+import 'package:logging/logging.dart';
import 'package:mailer/src/utils.dart';
import '../../entities/address.dart';
diff --git a/lib/src/smtp/internal_representation/ir_content.dart b/lib/src/smtp/internal_representation/ir_content.dart
index 23b3c90..da710ea 100644
--- a/lib/src/smtp/internal_representation/ir_content.dart
+++ b/lib/src/smtp/internal_representation/ir_content.dart
@@ -41,12 +41,14 @@ abstract class _IRContentPart extends _IRContent {
late Iterable<_IRContent> _content;
List _boundaryStart(String boundary) => to8('--$boundary$eol');
+
List _boundaryEnd(String boundary) => to8('--$boundary--$eol');
// We don't want to expose the number of sent emails.
// Only use the counter, if milliseconds hasn't changed.
static int _counter = 0;
static int? _prevTimestamp;
+
static String _buildBoundary() {
var now = DateTime.now().millisecondsSinceEpoch;
if (now != _prevTimestamp) _counter = 0;
@@ -84,7 +86,7 @@ Iterable _follow(T t, Iterable ts) sync* {
class _IRContentPartMixed extends _IRContentPart {
_IRContentPartMixed(Message message, Iterable<_IRHeader> header) {
- var attachments = message.attachments ;
+ var attachments = message.attachments;
var attached = attachments.where((a) => a.location == Location.attachment);
_active = attached.isNotEmpty;
@@ -155,7 +157,7 @@ class _IRContentAttachment extends _IRContent {
_header.add(_IRHeaderText('content-transfer-encoding', 'base64'));
if ((_attachment.cid ?? '').isNotEmpty) {
- _header.add(_IRHeaderText('content-id', _attachment.cid));
+ _header.add(_IRHeaderText('content-id', _attachment.cid!));
}
var fnSuffix = '';
@@ -173,7 +175,7 @@ class _IRContentAttachment extends _IRContent {
enum _IRTextType { plain, html }
class _IRContentText extends _IRContent {
- String? _text;
+ String _text = '';
_IRContentText(
String? text, _IRTextType textType, Iterable<_IRHeader> header) {
diff --git a/lib/src/smtp/internal_representation/ir_header.dart b/lib/src/smtp/internal_representation/ir_header.dart
index fe41890..fd4b8eb 100644
--- a/lib/src/smtp/internal_representation/ir_header.dart
+++ b/lib/src/smtp/internal_representation/ir_header.dart
@@ -3,17 +3,81 @@ part of 'internal_representation.dart';
abstract class _IRHeader extends _IROutput {
final String _name;
- static final List _b64prefix = convert.utf8.encode(' =?utf-8?B?');
- static final List _b64postfix = convert.utf8.encode('?=$eol');
+ static final _b64prefix = convert.utf8.encode('=?utf-8?B?'),
+ _b64postfix = convert.utf8.encode('?='),
+ _$eol = convert.utf8.encode(eol),
+ _$eolSpace = convert.utf8.encode('$eol '),
+ _$spaceLt = convert.utf8.encode(' <'),
+ _$gt = convert.utf8.encode('>'),
+ _$commaSpace = convert.utf8.encode(', '),
+ _$colonSpace = convert.utf8.encode(': ');
static final int _b64Length = _b64prefix.length + _b64postfix.length;
- Stream> _outValue(String? value) => Stream.fromIterable(
- [_name, ': ', value ?? '', eol].map(convert.utf8.encode));
+ Stream> _outValue(String? value) async* {
+ yield convert.utf8.encode(_name);
+ yield _$colonSpace;
+ if (value != null) yield convert.utf8.encode(value);
+ yield _$eol;
+ }
- // Outputs value encoded as base64.
+ // Outputs this header's name and the given [value] encoded as base64.
// Every chunk starts with ' ' and ends with eol.
// Call _outValueB64 after an eol.
Stream> _outValueB64(String value) async* {
+ yield convert.utf8.encode(_name);
+ yield _$colonSpace;
+ yield* _outB64(value);
+ yield _$eol;
+ }
+
+ /// Outputs the given [addresses].
+ Stream> _outAddressesValue(Iterable addresses,
+ _IRMetaInformation irMetaInformation) async* {
+ yield convert.utf8.encode(_name);
+ yield _$colonSpace;
+
+ var len = 2, //2 = _$commaSpace
+ second = false;
+ for (final address in addresses) {
+ final name = address.sanitizedName, maddr = address.sanitizedAddress;
+ var adrlen = maddr.length;
+ if (name != null) {
+ adrlen += name.length + 3;
+ } //not accurate but good enough
+
+ if (second) {
+ yield _$commaSpace;
+
+ if (len + adrlen > maxEncodedLength) {
+ len = 2;
+ yield _$eolSpace;
+ }
+ } else {
+ second = true;
+ }
+
+ if (name == null) {
+ yield convert.utf8.encode(maddr);
+ } else {
+ if (_shallB64(name, irMetaInformation)) {
+ yield* _outB64(name);
+ } else {
+ yield convert.utf8.encode(name);
+ }
+
+ yield _$spaceLt;
+ yield convert.utf8.encode(maddr);
+ yield _$gt;
+ }
+
+ len += adrlen;
+ }
+
+ yield _$eol;
+ }
+
+ // Outputs the given [value] encoded as base64.
+ static Stream> _outB64(String value) async* {
// Encode with base64.
var availableLengthForBase64 = maxEncodedLength - _b64Length;
@@ -24,16 +88,33 @@ abstract class _IRHeader extends _IROutput {
// At least 10 chars (random length).
if (availableLength < 10) availableLength = 10;
- var splitData = split(convert.utf8.encode(value), availableLength);
+ var second = false;
+ for (var d in split(convert.utf8.encode(value), availableLength)) {
+ if (second) {
+ yield _$eolSpace;
+ } else {
+ second = true;
+ }
- yield convert.utf8.encode('$_name: $eol');
- for (var d in splitData) {
yield _b64prefix;
yield convert.utf8.encode(convert.base64.encode(d));
yield _b64postfix;
}
}
+ static bool _shallB64(String value, _IRMetaInformation irMetaInformation) {
+ // If we have a maxLineLength is it the length of utf8 characters or
+ // the length of utf8 bytes?
+ // Just to be safe we'll count the bytes.
+ var byteLength = convert.utf8.encode(value).length;
+ return (byteLength > maxLineLength ||
+ !isPrintableRegExp.hasMatch(value) ||
+ // Make sure that text which looks like an encoded text is encoded.
+ value.contains('=?') ||
+ (!irMetaInformation.capabilities.smtpUtf8 &&
+ value.contains(RegExp(r'[^\x20-\x7E]'))));
+ }
+
/*
Stream> _outValue8(List value) => Stream.fromIterable(
[_name, ': '].map(utf8.encode).followedBy([value, _eol8]));
@@ -43,28 +124,17 @@ abstract class _IRHeader extends _IROutput {
}
class _IRHeaderText extends _IRHeader {
- final String? _value;
+ final String _value;
_IRHeaderText(String name, this._value) : super(name);
@override
- Stream> out(_IRMetaInformation irMetaInformation) {
- var utf8Allowed = irMetaInformation.capabilities.smtpUtf8;
-
- if ((_value?.length ?? 0) > maxLineLength ||
- !isPrintableRegExp.hasMatch(_value!) ||
- // Make sure that text which looks like an encoded text is encoded.
- _value!.contains('=?') ||
- (!utf8Allowed && _value!.contains(RegExp(r'[^\x20-\x7E]')))) {
- return _outValueB64(_value!);
- }
- return _outValue(_value);
- }
+ Stream> out(_IRMetaInformation irMetaInformation) =>
+ _IRHeader._shallB64(_value, irMetaInformation)
+ ? _outValueB64(_value)
+ : _outValue(_value);
}
-Iterable _addressToString(Iterable addresses) =>
- addresses.map((a) => a.toString());
-
class _IRHeaderAddress extends _IRHeader {
final Address _address;
@@ -72,7 +142,7 @@ class _IRHeaderAddress extends _IRHeader {
@override
Stream> out(_IRMetaInformation irMetaInformation) =>
- _outValue(_addressToString([_address]).first);
+ _outAddressesValue([_address], irMetaInformation);
}
class _IRHeaderAddresses extends _IRHeader {
@@ -82,7 +152,7 @@ class _IRHeaderAddresses extends _IRHeader {
@override
Stream> out(_IRMetaInformation irMetaInformation) =>
- _outValue(_addressToString(_addresses).join(', '));
+ _outAddressesValue(_addresses, irMetaInformation);
}
class _IRHeaderContentType extends _IRHeader {
@@ -142,7 +212,7 @@ Iterable<_IRHeader> _buildHeaders(Message message) {
});
if (!msgHeader.containsKey('subject') && message.subject != null) {
- headers.add(_IRHeaderText('subject', message.subject));
+ headers.add(_IRHeaderText('subject', message.subject!));
}
if (!msgHeader.containsKey('from')) {
diff --git a/lib/src/smtp/internal_representation/ir_message.dart b/lib/src/smtp/internal_representation/ir_message.dart
index 05d04ce..21f159a 100644
--- a/lib/src/smtp/internal_representation/ir_message.dart
+++ b/lib/src/smtp/internal_representation/ir_message.dart
@@ -1,6 +1,7 @@
part of 'internal_representation.dart';
class IRMessage {
+ final Logger _logger = Logger('IRMessage');
final Message? _message;
late _IRContent _content;
@@ -19,19 +20,23 @@ class IRMessage {
..._message!.recipientsAsAddresses,
..._message!.ccsAsAddresses,
..._message!.bccsAsAddresses
- ].where((a) => a.mailAddress != null).map((a) => a.mailAddress);
+ ].map((a) => a.mailAddress);
}
return envelopeTos;
}
String get envelopeFrom =>
- _message!.envelopeFrom ?? _message!.fromAsAddress.mailAddress ?? '';
+ _message!.envelopeFrom ?? _message!.fromAsAddress.mailAddress;
Stream> data(Capabilities capabilities) =>
- _content.out(_IRMetaInformation(capabilities));
+ _content.out(_IRMetaInformation(capabilities)).map((s) {
+ _logger.finest('ยซ${convert.utf8.decoder.convert(s)}ยป');
+ return s;
+ });
}
class InvalidHeaderException implements Exception {
String message;
+
InvalidHeaderException(this.message);
}
diff --git a/lib/src/smtp/validator.dart b/lib/src/smtp/validator.dart
index 3736978..d743442 100644
--- a/lib/src/smtp/validator.dart
+++ b/lib/src/smtp/validator.dart
@@ -15,9 +15,8 @@ bool _validAddress(dynamic addressIn) {
String? address;
if (addressIn is Address) {
- //We can't validate [Address.name] directly, since the implementation
- //of [Address.toString] might sanitize it.
- if (!_printableCharsOnly(addressIn.toString())) return false;
+ //Don't validate [Address.name] here since it will be encoded with base64
+ //if necessary
address = addressIn.mailAddress;
} else {
address = addressIn as String;
@@ -25,10 +24,7 @@ bool _validAddress(dynamic addressIn) {
return _validMailAddress(address);
}
-bool _validMailAddress(String? ma) {
- if (ma == null) {
- return false;
- }
+bool _validMailAddress(String ma) {
var split = ma.split('@');
return split.length == 2 &&
split.every((part) => part.isNotEmpty && _printableCharsOnly(part));
@@ -67,7 +63,7 @@ List validate(Message message) {
a = aIn is String ? Address(aIn) : aIn as Address?;
validate(
- a != null && (a.mailAddress ?? '').isNotEmpty,
+ a != null && (a.mailAddress).isNotEmpty,
'FROM_ADDRESS_EMPTY',
'A recipient address is null or empty. (pos: $counter).');
if (a != null) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 2cf0c8e..e5ee553 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: mailer
-version: 4.0.0
+version: 5.0.0
description: >
Compose and send emails from Dart.
Supports file attachments and HTML emails
@@ -7,7 +7,7 @@ homepage: https://github.com/kaisellgren/mailer
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
- async: '^2.0.0'
+ async: '^2.5.0'
logging: '^1.0.0'
intl: '^0.17.0'
mime: '^1.0.0'
diff --git a/test/message_out_test.dart b/test/message_out_test.dart
new file mode 100644
index 0000000..531ca8c
--- /dev/null
+++ b/test/message_out_test.dart
@@ -0,0 +1,94 @@
+library message_out_test;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:mailer/mailer.dart';
+import 'package:mailer/src/smtp/capabilities.dart';
+import 'package:mailer/src/smtp/internal_representation/internal_representation.dart';
+import 'package:test/test.dart';
+
+part 'messages/message_helpers.dart';
+
+part 'messages/message_simple_utf8.dart';
+
+part 'messages/message_utf8_from_header.dart';
+
+part 'messages/message_utf8_long_subject_long_body.dart';
+
+part 'messages/message_text_html_only.dart';
+
+part 'messages/message_all.dart';
+
+class MessageTest {
+ final String name;
+ final Message message;
+ final String messageRegExpWithUtf8;
+ final String messageRegExpWithoutUtf8;
+ final Map stringReplacements;
+
+ MessageTest(this.name, this.message, this.messageRegExpWithUtf8,
+ this.messageRegExpWithoutUtf8,
+ {this.stringReplacements = const {}});
+}
+
+final testCases = [
+ messageSimpleUtf8,
+ messageUtf8FromHeader,
+ messageUtf8LongSubjectLongBodyBelowLineLength,
+ messageUtf8LongSubjectLongBodyAboveLineLength,
+ messageHtmlOnly,
+ messageTextOnly,
+ messageAll
+];
+
+Future testMessage(Message message, String expectedRegExp,
+ {bool smtpUtf8 = true,
+ Map stringReplacements = const {}}) async {
+ var irContent = IRMessage(message);
+ var capabilities = capabilitiesForTesting(smtpUtf8: smtpUtf8);
+ var data = irContent.data(capabilities);
+ var m = await data.fold>([], (previous, element) {
+ previous.addAll(element);
+ return previous;
+ });
+ var mUtf8 = utf8.decoder.convert(m);
+ stringReplacements.forEach((replaceThis, withThis) {
+ mUtf8 = mUtf8.replaceAll(replaceThis, withThis);
+ });
+ //print('Testing: $mUtf8 against $expectedRegExp');
+ return RegExp(expectedRegExp, multiLine: true).hasMatch(mUtf8);
+}
+
+void main() async {
+ Logger.root.level = Level.ALL;
+ // Logger.root.onRecord.listen((LogRecord rec) =>
+ // print('${rec.level.name}: ${rec.time}: ${rec.message}'));
+
+ testCases.forEach((testCase) {
+ // If we have a StreamAttachment we can't send the same message twice.
+ // In this case the testCase is a function which generates a `MessageTest`
+ var tcUtf8 = (testCase is Function ? testCase() : testCase) as MessageTest;
+ test(
+ 'message is correctly converted ${tcUtf8.name} (utf8)',
+ () async => expect(
+ testMessage(tcUtf8.message, tcUtf8.messageRegExpWithUtf8,
+ smtpUtf8: true, stringReplacements: tcUtf8.stringReplacements),
+ completion(equals(true)),
+ reason: '${tcUtf8.name} (smtpUtf8)'),
+ );
+
+ // Recreate the testCase (for StreamAttachments)
+ final tcWithoutUtf8 =
+ (testCase is Function ? testCase() : testCase) as MessageTest;
+ test(
+ 'message is correctly converted ${tcWithoutUtf8.name} (without utf8)',
+ () async => expect(
+ testMessage(
+ tcWithoutUtf8.message, tcWithoutUtf8.messageRegExpWithoutUtf8,
+ smtpUtf8: false, stringReplacements: tcUtf8.stringReplacements),
+ completion(equals(true)),
+ reason: '${tcWithoutUtf8.name} (smtpUtf8 false)'));
+ });
+}
diff --git a/test/send_test.dart b/test/send_test.dart
index 04c8267..3ec418f 100644
--- a/test/send_test.dart
+++ b/test/send_test.dart
@@ -41,7 +41,7 @@ Future configureCorrectSmtpServer() async {
Message createMessage(SmtpServer smtpServer) {
// Message to myself
return Message()
- ..from = Address(smtpServer.username)
+ ..from = Address(smtpServer.username!)
..recipients.add(smtpServer.username)
..subject = 'Test Dart Mailer library :: ๐ :: ${DateTime.now()}'
..text = 'This is the plain text.\nThis is line 2 of the text part.';