Skip to content

Commit b26bccd

Browse files
authored
Merge pull request #271 from dart-mailer/CL/v7
mailer version 7
2 parents b880cdf + b9914ab commit b26bccd

File tree

86 files changed

+8924
-1090
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+8924
-1090
lines changed

.github/workflows/publish.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish to pub.dev
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]+.[0-9]+.[0-9]+*' # Triggers on tags like v1.2.3 or v1.2.3+1
7+
workflow_dispatch: # Allows you to manually trigger a retry from the Actions tab
8+
9+
jobs:
10+
publish:
11+
permissions:
12+
id-token: write # Required for OIDC authentication
13+
contents: read
14+
15+
runs-on: ubuntu-latest
16+
17+
environment: pub.dev
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v4
22+
23+
- name: Setup Dart
24+
uses: dart-lang/setup-dart@v1
25+
26+
- name: Install dependencies
27+
run: dart pub get
28+
29+
- name: Publish to pub.dev
30+
# The '--force' is safe here because the tag and
31+
# environment approval act as your confirmation.
32+
run: dart pub publish --force

.github/workflows/test.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Dart CI
2+
3+
on:
4+
push:
5+
branches: [ '**' ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- uses: dart-lang/setup-dart@v1
17+
with:
18+
sdk: stable
19+
20+
- name: Install dependencies
21+
run: dart pub get
22+
23+
- name: Verify formatting
24+
run: dart format --output=none --set-exit-if-changed .
25+
26+
- name: Analyze project source
27+
# Adding --fatal-infos ensures the highest code quality standards
28+
run: dart analyze --fatal-infos
29+
30+
- name: Run tests
31+
run: dart test
32+
33+
- name: Publish Dry Run
34+
# This ensures every PR is actually "publishable" to pub.dev
35+
run: dart pub publish --dry-run

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ packages
88
*.iml
99
.dart_tool
1010
/test/smtpserver.json
11-
.DS_Store
11+
.DS_Store
12+
secrets.json

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
## 7.0.0
2+
* Major Refactoring: Simplified library structure and standardized exports.
3+
- Renamed internal `IR` classes to `Mime`.
4+
- Moved files into `core`, `mime`, and `smtp` directories.
5+
- Standardized on relative imports within the package.
6+
- Moved to community repository (github.com/dart-mailer/mailer)
7+
* Feature: Added support for RFC 3030 (CHUNKING and BINARYMIME).
8+
* Feature: Implemented Custom Address Validation API.
9+
- Added `PracticalAddressValidator` (recommended for input validation).
10+
- Added `StrictAddressValidator` (RFC 5322 compliance).
11+
- Added `PermissiveAddressValidator` and `SimpleAddressValidator`.
12+
* Feature: Correct IDNA encoding for domains.
13+
- Proper NFC Unicode normalization using `unorm_dart`.
14+
- Case folding and label validation (RFC 5890).
15+
* Breaking: Removed `lib/src/core/entities.dart`. Import `package:mailer/mailer.dart` instead.
16+
* Update: Bumped SDK constraint to Dart 3.
17+
118
## 6.6.0
219
* Add Amazon simple Email Service stmp server
320
Thanks https://github.com/karelklic

README.md

Lines changed: 133 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -2,158 +2,176 @@
22

33
**mailer** is an easy-to-use library for composing and sending emails in Dart.
44

5-
Mailer supports file attachments and HTML emails.
5+
> [!WARNING]
6+
> This is an **SMTP client** library. It is designed to send emails by connecting to an **existing SMTP server** (like Gmail, SendGrid, Mailgun, or your own Postfix/Exim server).
7+
>
8+
> It is **not** an SMTP server and cannot receive emails or accept incoming connections from other mail clients.
69
7-
## FLUTTER developers
10+
It supports:
11+
* **Plaintext and HTML** emails.
12+
* **Attachments** (files, streams, etc.).
13+
* **Inline images** (using CID).
14+
* **Unicode** support.
15+
* **RFC 3030** (CHUNKING) for efficient large file transfer.
16+
* **Secure connections** (TLS/SSL) with context-aware sanitization.
17+
* **Custom Address Validation** strategies (strict, permissive, etc.).
18+
* **Pre-configured services** (Gmail, Yahoo, Hotmail, etc.) and generic SMTP support.
819

9-
**This library does not work with flutter web.** Sending mails using the SMTP is technically not possible over HTTP.
20+
## Usage
1021

22+
### Simple Example (Gmail)
1123

12-
Please do NOT use mailer together with your credentials. Extracting them is very easy and anybody could then send
13-
mails using your account. If you use your gmail credentials it's even worse as an attacker could read your mails as
14-
well.
24+
```dart
25+
import 'package:mailer/mailer.dart';
26+
import 'package:mailer/smtp_server.dart';
1527
28+
void main() async {
29+
String username = 'username@gmail.com';
30+
String password = 'password'; // Use an App Password if 2FA is enabled
1631
17-
[Johannes Milke](https://github.com/JohannesMilke) has created an excellent tutorial on how to use `mailer` without
18-
needing to embed your credentials in the flutter app.
32+
final smtpServer = gmail(username, password);
33+
// Use the SmtpServer class to configure any other SMTP server:
34+
// final smtpServer = SmtpServer('smtp.domain.com');
1935
20-
[Flutter Tutorial - How To Send Email In Background \[2021\] Without Backend](https://www.youtube.com/watch?v=RDwst9icjAY)
36+
final message = Message()
37+
..from = Address(username, 'Your Name')
38+
..recipients.add('destination@example.com')
39+
..ccRecipients.addAll(['destCc1@example.com', 'destCc2@example.com'])
40+
..bccRecipients.add(Address('bccAddress@example.com'))
41+
..subject = 'Test Dart Mailer library :: 😀 :: ${DateTime.now()}'
42+
..text = 'This is the plain text.\nThis is line 2 of the text part.'
43+
..html = "<h1>Test</h1>\n<p>Hey! Here's some HTML content</p>";
2144
22-
The tutorial will use firebase and ask for the credentials of the android user: ![flutter-screenshot](doc/flutter_user.png)
23-
By using the account of the android user it avoids storing your credentials in the app.
45+
try {
46+
final sendReport = await send(message, smtpServer);
47+
print('Message sent: $sendReport');
48+
} on MailerException catch (e) {
49+
print('Message not sent.');
50+
for (var p in e.problems) {
51+
print('Problem: ${p.code}: ${p.msg}');
52+
}
53+
}
54+
}
55+
```
2456

25-
## Server developers
57+
### Advanced Usage
2658

27-
[Suragch](https://suragch.medium.com/) has written an excellent tutorial on
28-
[How to send yourself email notifications from a Dart server](https://suragch.medium.com/how-to-send-yourself-email-notifications-from-a-dart-server-a7c16a1900d6)
59+
#### Attachments and Inline Images
2960

30-
Thanks to Suragch for bringing those tutorials to my attention.
31-
If you have created a tutorial yourself (or know of one) please open an issue, so that I can add it to this "list".
61+
```dart
62+
final message = Message()
63+
..from = Address(username, 'Your Name')
64+
..recipients.add('destination@example.com')
65+
..subject = 'Inline Image Test'
66+
..html = '<h1>Test</h1><p>Here is an image:</p><img src="cid:myimg@3.141"/>'
67+
..attachments = [
68+
FileAttachment(File('image.png'))
69+
..location = Location.inline
70+
..cid = '<myimg@3.141>'
71+
];
72+
```
3273

33-
Note that those tutorial don't need to be in English!
74+
#### Custom Address Validation
3475

35-
## SMTP definitions
76+
The library validates addresses before sending. By default, it uses a simple check (contains `@` with non-empty parts). You can customize this behavior:
3677

37-
Mailer provides configurations for a few common SMTP servers.
78+
**Available validators:**
79+
| Validator | Use Case |
80+
|:----------|:---------|
81+
| `PracticalAddressValidator` | **Recommended for input validation.** Rejects IP literals, quoted strings, and domains without dots. |
82+
| `StrictAddressValidator` | Full RFC 5322 compliance. Accepts technically valid but uncommon formats. |
83+
| `PermissiveAddressValidator` | Accepts any non-empty string. |
3884

39-
Please create merge requests for missing configurations.
85+
**Validating user input (recommended):**
86+
```dart
87+
import 'package:mailer/src/core/address_validator.dart';
4088
41-
* Copy `lib/smtp_server/gmail.dart` to `lib/smtp_server/xxx.dart`
42-
* Adapt the code. (See `lib/smtp_server.dart` for possible arguments)
43-
* Export the newly created SMTP server in `lib/smtp_server.dart`
44-
* Create a pull request.
89+
final validator = PracticalAddressValidator();
90+
final address = Address('test@example.com');
4591
46-
In a lot of cases you will find a configuration
47-
in [legacy.dart](https://github.com/kaisellgren/mailer/blob/v2/lib/legacy.dart)
92+
if (validator.validate(address)) {
93+
print('Address is valid');
94+
} else {
95+
print('Address is invalid - may not be deliverable');
96+
}
97+
```
4898

49-
## Features
99+
**Using a validator for sending:**
100+
```dart
101+
final message = Message()
102+
..validator = StrictAddressValidator() // or PracticalAddressValidator()
103+
..from = 'valid@example.com';
104+
```
50105

51-
* Plaintext and HTML emails
52-
* Unicode support
53-
* Attachments
54-
* Secure (filters and sanitizes all fields context-wise)
55-
* Use any SMTP server like Gmail, Live, SendGrid, Amazon SES
56-
* SSL/TLS support
57-
* Pre-configured services (Gmail, Yahoo, Hotmail, etc.). Just fill in your username and password.
106+
#### Persistent Connection
58107

59-
## TODO *HELP WANTED*
108+
For sending multiple messages efficiently, use `PersistentConnection`.
60109

61-
* Correct encoding of non ASCII mail addresses.
62-
* Reintegrate address validation from version 1.*
63-
* Improve Header types. (see [ir_header.dart](lib/src/smtp/internal_representation/ir_header.dart))
64-
We should choose the correct header based on the header name.
65-
Known headers (`list-unsubscribe`,...) should have their own subclass.
66-
* Improve documentation.
110+
```dart
111+
var connection = PersistentConnection(smtpServer);
67112
68-
## Examples
113+
await connection.send(message1);
114+
await connection.send(message2);
69115
70-
### Sending an email with SMTP
116+
await connection.close();
117+
```
71118

72-
See [gmail example](example/send_gmail.dart).
73119

74-
We also have an example which uses [gmail oauth](example/gmail_xoauth2/).
120+
## Documentation
75121

76-
```dart
77-
import 'package:mailer/mailer.dart';
78-
import 'package:mailer/smtp_server.dart';
122+
* [Using Inline Images (doc/inline_image_guide.md)](doc/inline_image_guide.md)
123+
* [Gmail XOAUTH2 Guide (doc/gmail_xoauth2/README.md)](doc/gmail_xoauth2/README.md)
79124

80-
main() async {
81-
// Note that using a username and password for gmail only works if
82-
// you have two-factor authentication enabled and created an App password.
83-
// Search for "gmail app password 2fa"
84-
// The alternative is to use oauth.
85-
String username = 'username@gmail.com';
86-
String password = 'password';
125+
## Tutorials
87126

88-
final smtpServer = gmail(username, password);
89-
// Use the SmtpServer class to configure an SMTP server:
90-
// final smtpServer = SmtpServer('smtp.domain.com');
91-
// See the named arguments of SmtpServer for further configuration
92-
// options.
127+
### Flutter Developers
93128

94-
// Create our message.
95-
final message = Message()
96-
..from = Address(username, 'Your name')
97-
..recipients.add('destination@example.com')
98-
..ccRecipients.addAll(['destCc1@example.com', 'destCc2@example.com'])
99-
..bccRecipients.add(Address('bccAddress@example.com'))
100-
..subject = 'Test Dart Mailer library :: 😀 :: ${DateTime.now()}'
101-
..text = 'This is the plain text.\nThis is line 2 of the text part.'
102-
..html = "<h1>Test</h1>\n<p>Hey! Here's some HTML content</p>";
129+
**Flutter Web is NOT supported.** Sending emails via SMTP directly from a browser is technically impossible due to security restrictions (CORS, lack of raw socket access). You must use a backend server or a proxy.
103130

104-
try {
105-
final sendReport = await send(message, smtpServer);
106-
print('Message sent: ' + sendReport.toString());
107-
} on MailerException catch (e) {
108-
print('Message not sent.');
109-
for (var p in e.problems) {
110-
print('Problem: ${p.code}: ${p.msg}');
111-
}
112-
}
113-
// DONE
114-
115-
116-
// Let's send another message using a slightly different syntax:
117-
//
118-
// Addresses without a name part can be set directly.
119-
// For instance `..recipients.add('destination@example.com')`
120-
// If you want to display a name part you have to create an
121-
// Address object: `new Address('destination@example.com', 'Display name part')`
122-
// Creating and adding an Address object without a name part
123-
// `new Address('destination@example.com')` is equivalent to
124-
// adding the mail address as `String`.
125-
final equivalentMessage = Message()
126-
..from = Address(username, 'Your name 😀')
127-
..recipients.add(Address('destination@example.com'))
128-
..ccRecipients.addAll([Address('destCc1@example.com'), 'destCc2@example.com'])
129-
..bccRecipients.add('bccAddress@example.com')
130-
..subject = 'Test Dart Mailer library :: 😀 :: ${DateTime.now()}'
131-
..text = 'This is the plain text.\nThis is line 2 of the text part.'
132-
..html = '<h1>Test</h1>\n<p>Hey! Here is some HTML content</p><img src="cid:myimg@3.141"/>'
133-
..attachments = [
134-
FileAttachment(File('exploits_of_a_mom.png'))
135-
..location = Location.inline
136-
..cid = '<myimg@3.141>'
137-
];
131+
**Security Warning:** Do NOT embed your SMTP credentials (username/password) directly in your client-side Flutter code. If you do, anyone can extract them and use your account to send spam.
132+
133+
[Johannes Milke](https://github.com/JohannesMilke) has created an excellent tutorial on how to use `mailer` without needing to embed your credentials in the Flutter app:
134+
135+
* [Flutter Tutorial - How To Send Email In Background [2021] Without Backend](https://www.youtube.com/watch?v=RDwst9icjAY)
136+
137+
The tutorial uses Firebase and requests the credentials of the Android user, avoiding storing your credentials in the app.
138+
139+
### Server Developers
138140

139-
final sendReport2 = await send(equivalentMessage, smtpServer);
141+
[Suragch](https://suragch.medium.com/) has written an excellent tutorial on [How to send yourself email notifications from a Dart server](https://suragch.medium.com/how-to-send-yourself-email-notifications-from-a-dart-server-a7c16a1900d6).
140142

141-
// Sending multiple messages with the same connection
142-
//
143-
// Create a smtp client that will persist the connection
144-
var connection = PersistentConnection(smtpServer);
143+
---
145144

146-
// Send the first message
147-
await connection.send(message);
145+
If you have created a tutorial yourself (or know of one) please open an issue, so it can be added to this list. Note that tutorials don't need to be in English!
148146

149-
// send the equivalent message
150-
await connection.send(equivalentMessage);
147+
## Logging
151148

152-
// close the connection
153-
await connection.close();
149+
The library uses the `logging` package. By default, no logs are output.
150+
To see the SMTP communication (which is helpful for debugging), configure a logger listener:
151+
152+
```dart
153+
import 'package:logging/logging.dart';
154+
155+
void main() {
156+
Logger.root.level = Level.ALL; // defaults to Level.INFO
157+
Logger.root.onRecord.listen((LogRecord rec) {
158+
print('${rec.level.name}: ${rec.time}: ${rec.message}');
159+
});
160+
161+
// ... rest of your code
154162
}
155163
```
156164

165+
**Security Note:** Credentials (passwords, tokens) in `AUTH` commands are masked (`*******`) in the logs to prevent accidental leakage.
166+
167+
## FAQ
168+
169+
**Q: How do I handle large attachments?**
170+
A: `mailer` supports RFC 3030 (CHUNKING). If the server supports it, `mailer` will automatically stream the data using `BDAT` commands, which is more efficient than the standard `DATA` command.
171+
172+
**Q: How do I use Gmail with 2FA?**
173+
A: You must generate an **App Password** in your Google Account settings and use that instead of your standard password.
174+
157175
## License
158176

159-
This library is licensed under MIT.
177+
MIT

0 commit comments

Comments
 (0)