Search the site
BACKEND
Send email, in-app and push notifications through DV.Notifications.
Mail is part of it, at DV.Notifications.mail.
ON THIS PAGE
Pick a mail provider
Send an email
Notify a person on several channels
Push to Android and the browser
Receive push in the app
Topics and data messages
Status
void configureMail() {
DV.Notifications.mail.useProvider(SmtpMailProvider(
host: 'smtp.example.com',
username: 'apikey',
password: DV.Secrets.get('SMTP_PASSWORD'),
));
}Copy code to clipboard
SmtpMailProvider
Sends through: Any SMTP server, port 587 by default
ResendMailProvider
Sends through: Resend
SendGridMailProvider
Sends through: SendGrid
PostmarkMailProvider
Sends through: Postmark
SesMailProvider
Sends through: Amazon SES
MailgunMailProvider
Sends through: Mailgun
DVMemoryMailProvider
Sends through: Nowhere. It keeps messages in .sent for tests
Providers are set in code. Read passwords and API keys with DV.Secrets.get.
await DV.Notifications.mail.send(const DVMailMessage(
from: DVMailAddress('hello@example.com', name: 'Shop'),
to: <DVMailAddress>[DVMailAddress('ada@example.com')],
subject: 'Your order shipped',
text: 'It arrives on Friday.',
));Copy code to clipboard
Add html: for an HTML body next to the text one.
send throws when no mail provider is set.
DV.Notifications.register(DVMemoryNotificationProvider());
final DVNotificationDelivery delivery = await DV.Notifications.send(
'user-1',
const DVNotificationMessage(
title: 'Order shipped',
body: 'It arrives on Friday.',
channels: <DVNotificationChannel>[DVNotificationChannel.inApp],
),
);Copy code to clipboard
Channels are inApp, email, push, sms and webPush.
Register one provider per kind, such as FirebasePushProvider, ApnsPushProvider, WebPushProvider or TwilioSmsProvider.
useRoutes tells the service each person's address and device tokens.
push tries the native providers first and falls back to Web Push when none is registered or the one that is fails.
usePreferences reads each person's muted channels and quiet hours. Push, web push and SMS are not sent during quiet hours, and email is.
send returns a DVNotificationDelivery with the outcome of every channel, so a muted or unrouted channel is told apart from one that failed.
Name the targets under dartvel.push in pubspec.yaml: web takes the public half of your VAPID key (dartvel push vapid-keys makes a pair), and android.fcm takes the four values the Firebase console shows for the app. The build writes everything else, so nothing in android/ or in a service worker is edited by hand.
The credentials that send are Secrets, never pubspec.yaml: FCM_SERVICE_ACCOUNT_JSON for FCM, VAPID_PRIVATE_KEY and VAPID_SUBJECT for Web Push. The config parser refuses a private key written in pubspec.yaml.
FirebasePushProvider.fromSecrets signs the service-account token request itself, keeps the access token until just before it expires and retries once after a 401. The key is redacted from logs and is in no error message.
void configurePush() {
// The service-account key (FCM_SERVICE_ACCOUNT_JSON) and the VAPID private
// key (VAPID_PRIVATE_KEY, VAPID_SUBJECT) are read from DV.Secrets.
DV.Notifications.register(FirebasePushProvider.fromSecrets());
DV.Notifications.register(WebPushProvider.fromSecrets());
}Copy code to clipboard
Future<void> enablePush() async {
// Called from a tap: starting push asks for nothing.
await PushNotifications.requestPermission();
final String? token = await PushNotifications.getToken();
if (token != null) DV.log('push token ready');
PushNotifications.onMessage.listen((PushNotification note) {
DV.log('push: ${note.title}');
});
}Copy code to clipboard
getToken is an FCM registration token on Android and the browser subscription on the web. Send it to your server, where DVNotificationRoutes takes it as pushTokens or webPushSubscriptions.
Starting push asks for no permission. requestPermission is for a tap: a browser stops showing the prompt after a few dismissals, so asking at load spends the only chance.
On Android the build writes the Firebase service, the JNI bridge, the manifest entry, the Gradle dependency and the string resources Firebase reads, so there is no google-services.json. A message that arrives while the app is not running is kept until Dart asks.
On the web the one generated service worker, shared by web and web-server, shows a notification when no window is visible, hands the message to the page when one is, and opens only pages of your own origin when it is clicked.
Fire OS and Horizon OS have no Google Play services, so their builds leave FCM out.
Future<void> announce() async {
final FirebasePushProvider fcm = FirebasePushProvider.fromSecrets();
await fcm.sendTopic(
'news',
const DVNotificationMessage(title: 'New release', body: 'Version 2 is out.'),
);
await fcm.sendData('device-token', <String, String>{'sync': 'orders'});
}Copy code to clipboard
A topic reaches every device subscribed to it. Web Push has no topics, and subscribeToTopic there says so.
A data message shows nothing; the app receives it and decides.
FCM answers a token it no longer knows with UNREGISTERED. That is isUnregisteredToken on the exception: drop the token. isRetryable is true for quota, unavailable and internal errors.
Partial
Spec section: Mail and Notifications
Planned work and implementation limits
Web Push has been run against Chrome's real push service. FCM has not: no Firebase project or Android device was available, so no token has come from a phone and no message has been delivered to one.
A click on a notification is tested in the worker and has not been tried in a browser. Nor have Firefox and Safari.
A tap on a notification while the Android app is already running in the background is not reported as opened.
No attachments, bounce webhooks or List-Unsubscribe header.
Mail is sent directly, and not through the job queue.
Desktop and TV push kinds have no implementation, and iOS has no device registration yet.
FSL-1.1-MIT licensed. Built with Dartvel.
Dartvel is made by
To the bottom