LogoZonai

Side Effects: email

Sending transactional email from extensions and cron jobs.

email.send provides helpers for sending transactional emails from extension hooks and cron jobs. SMTP must be configured in AppConfig before any email will be delivered. See SMTP Setup.

Built-in Helpers#

Each method sends one of the built-in templates. The first argument is an EmailAddress; table identifies which auth table the user belongs to:

email.send.verifyEmail(
  EmailAddress(address: user.email),
  table: 'users',
);

email.send.loginNotice(
  EmailAddress(address: user.email),
  table: 'users',
);

email.send.passwordReset(
  EmailAddress(address: user.email),
  table: 'users',
);

email.send.magicLink(
  EmailAddress(address: user.email),
  table: 'users',
);

email.send.otpCode(
  EmailAddress(address: user.email),
  table: 'users',
);

email.send.confirmEmailChange(
  EmailAddress(address: user.email),
  table: 'users',
);

All helpers accept an optional variables map to pass extra data to the template.

Sending Custom Emails#

Use email.send(Email(...)) to send a custom template:

email.send(Email(
  to: EmailAddress(address: user.email),
  subject: 'Your order is confirmed',
  template: 'order_confirmation',
  variables: {
    'orderId': order.id,
    'total': order.total,
    'items': order.lineItems,
  },
));

The template field is the filename (without .html) from emailTemplatesPath. See Custom Templates.

Threading#

Pass thread on multiple related emails (e.g. OTP resends) to group them in the user's inbox:

email.send(Email(
  to: ...,
  subject: 'New sign-in code',
  template: 'otp_code',
  variables: {'otp': newCode},
  thread: Email.createThread('otp:users:${user.email}', continueThread: true),
));

When Email Sends#

email.send.* is fire-and-forget — it does not await delivery and does not block the HTTP response. If SMTP delivery fails, the error is logged but the original request still succeeds. There is no built-in retry.

Example#

class UserExtensions extends Extension<User>
    with AuthExtension<UserTable, User> {
  UserExtensions() : super(users);

  @override
  Future<void> onSignUp(User user) async {
    email.send.verifyEmail(
      EmailAddress(address: user.email),
      table: 'users',
    );
  }

  @override
  Future<void> onSignIn(User user) async {
    email.send.loginNotice(
      EmailAddress(address: user.email),
      table: 'users',
    );
  }
}

UserExtensions main() => UserExtensions();