buildMessage static method

String buildMessage({
  1. required String fromHeader,
  2. required List<String> toHeaders,
  3. required String subject,
  4. required String text,
  5. String? html,
  6. Map<String, String> headers = const <String, String>{},
  7. required DateTime date,
})

Builds the RFC 5322 message, ending with the . terminator.

Any body line starting with . is dot-stuffed, otherwise a line of . inside the body would end the message early.

Implementation

static String buildMessage({
  required String fromHeader,
  required List<String> toHeaders,
  required String subject,
  required String text,
  String? html,
  Map<String, String> headers = const <String, String>{},
  required DateTime date,
}) {
  final buffer = StringBuffer();
  void line(String value) => buffer.write('$value\r\n');

  line('From: ${encodeAddressHeader(fromHeader)}');
  line('To: ${toHeaders.map(encodeAddressHeader).join(', ')}');
  line('Subject: ${encodeHeaderValue(subject)}');
  line('Date: ${formatDate(date)}');
  line('MIME-Version: 1.0');
  for (final entry in headers.entries) {
    line('${entry.key}: ${encodeHeaderValue(entry.value)}');
  }

  // Each part says how it is encoded. Without the header a body defaults
  // to 7bit, which raw UTF-8 is not: a server without 8BITMIME may refuse
  // it or strip the high bit, and the message arrives readable-ish, which
  // is the worst outcome to be handed.
  void part(String content) {
    final bool plain = bodyIsSevenBit(content);
    line('Content-Transfer-Encoding: ${plain ? '7bit' : 'quoted-printable'}');
    line('');
    // Dot-stuffed after encoding, because it is the lines that actually go
    // down the wire that a leading dot would truncate.
    buffer.write(_dotStuff(plain ? content : quotedPrintable(content)));
  }

  if (html == null) {
    line('Content-Type: text/plain; charset=utf-8');
    part(text);
  } else {
    const boundary = '----=_dartvel_alternative';
    line('Content-Type: multipart/alternative; boundary="$boundary"');
    line('');
    line('--$boundary');
    line('Content-Type: text/plain; charset=utf-8');
    part(text);
    line('');
    line('--$boundary');
    line('Content-Type: text/html; charset=utf-8');
    part(html);
    line('');
    line('--$boundary--');
  }

  line('');
  line('.');
  return buffer.toString();
}