Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: v5.0.0! bug fixes and lib update #173

Merged
merged 1 commit into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
65 changes: 25 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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')
Expand All @@ -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.
Expand All @@ -109,32 +89,37 @@ main() async {
// `new Address('[email protected]')` is equivalent to
// adding the mail address as `String`.
final equivalentMessage = Message()
..from = Address(username, 'Your name')
..recipients.add(Address('[email protected]'))
..ccRecipients.addAll([Address('[email protected]'), '[email protected]'])
..bccRecipients.add('[email protected]')
..subject = 'Test Dart Mailer library :: 😀 :: ${DateTime.now()}'
..text = 'This is the plain text.\nThis is line 2 of the text part.'
..html = "<h1>Test</h1>\n<p>Hey! Here's some HTML content</p>";

..from = Address(username, 'Your name 😀')
..recipients.add(Address('[email protected]'))
..ccRecipients.addAll([Address('[email protected]'), '[email protected]'])
..bccRecipients.add('[email protected]')
..subject = 'Test Dart Mailer library :: 😀 :: ${DateTime.now()}'
..text = 'This is the plain text.\nThis is line 2 of the text part.'
..html = '<h1>Test</h1>\n<p>Hey! Here is some HTML content</p><img src="cid:[email protected]"/>'
..attachments = [
FileAttachment(File('exploits_of_a_mom.png'))
..location = Location.inline
..cid = '<[email protected]>'
];

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.
23 changes: 12 additions & 11 deletions lib/src/entities/address.dart
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>`, `<[email protected]>`
/// and `foo.domain.com`.
@override
String toString() {
var fromName = name ?? '';
// ToDo base64 fromName (add _IRMetaInformation as argument)
return '$fromName <$mailAddress>';
}
String toString() => "${name ?? ''} <$mailAddress>";
}
6 changes: 5 additions & 1 deletion lib/src/entities/attachment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<img src="cid:yourCid" />`
///
/// [cid] must contain an `@` and be inside `<` and `>`.
/// The cid: `<[email protected]>` can then be referenced inside your html as:
/// `<img src="cid:[email protected]">`
abstract class Attachment {
String? cid;
Location location = Location.attachment;
Expand Down
14 changes: 14 additions & 0 deletions lib/src/smtp/capabilities.dart
Original file line number Diff line number Diff line change
@@ -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<String> all = const <String>[]}) {
return Capabilities._values(
startTls, smtpUtf8, authPlain, authLogin, authXoauth2, all);
}

class Capabilities {
final bool startTls;
final bool smtpUtf8;
Expand Down
15 changes: 14 additions & 1 deletion lib/src/smtp/internal_representation/conversion.dart
Original file line number Diff line number Diff line change
@@ -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<int> to8(String s) => convert.utf8.encode(s);
Expand Down Expand Up @@ -71,22 +75,31 @@ Iterable<List<int>> split(List<int> data, int maxLength,
Stream<List<int>> _splitS(
Stream<List<int>> dataS, int splitOver, int maxLength) {
var currentLineLength = 0;
var insertEol = false;

var sc = StreamController<List<int>>();
void processData(List<int> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
8 changes: 5 additions & 3 deletions lib/src/smtp/internal_representation/ir_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ abstract class _IRContentPart extends _IRContent {
late Iterable<_IRContent> _content;

List<int> _boundaryStart(String boundary) => to8('--$boundary$eol');

List<int> _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;
Expand Down Expand Up @@ -84,7 +86,7 @@ Iterable<T> _follow<T>(T t, Iterable<T> 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;
Expand Down Expand Up @@ -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 = '';
Expand All @@ -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) {
Expand Down
Loading