Module Netsendmail


module Netsendmail: sig .. end
Functions to compose and send electronic mails

Contents




Composing Messages

The core function is Netsendmail.compose generating a MIME mail. The mail can be sent with Netsendmail.sendmail, written to an object channel with Netmime.write_mime_message, or postprocessed by a user function.

The call to compose can be as easy as

 compose ~from_addr:("me", "me@domain.net") 
            ~to_addr:("you", "you@domain.com")
            ~subject:"I have a message for you"
            "Hello, this is my message!\n"
 

This call generates the message as Netmime.complex_mime_message, and can be directly sent with Netsendmail.sendmail.

The compose function is the simplified interface; alternatively one can also generate the mail by calling Netsendmail.wrap_mail, Netsendmail.wrap_parts, and Netsendmail.wrap_attachment, getting more fine-grained control of certain options.

val compose : ?in_charset:Netconversion.encoding ->
?out_charset:Netconversion.encoding ->
?from_addr:string * string ->
?cc_addrs:(string * string) list ->
?bcc_addrs:(string * string) list ->
?content_type:string * (string * Mimestring.s_param) list ->
?container_type:string * (string * Mimestring.s_param) list ->
?attachments:Netmime.complex_mime_message list ->
to_addrs:(string * string) list ->
subject:string -> string -> Netmime.complex_mime_message
Composes a mail message with a main text, and optionally a number of attachments.

The addresses from_addr, to_addrs, cc_addrs, and bcc_addrs are passed as pairs (human_readable,formal) where human_readable is an arbitrary printable string identifying the sender/receiver, and where formal is the RFC-822 mailbox specification. An example is ("Stolpmann, Gerd", "gerd@gerd-stolpmann.de").

The subject can be any text.

The anonymous string argument is the main text of the mail.

The resulting message is always a correct MIME message.


in_charset : All passed texts (except the formal addresses) must be encoded in in_charset. Default: `Enc_iso88591. As another exception, setting content_type explicitly prevents the main text from being converted, and in_charset does not have a meaning for the main text.
out_charset : The encoded words in the generated header fields, if necessary, and the main text are encoded in out_charset. Default: `Enc_iso88591. It is required that out_charset is ASCII-compatible. As a special rule, setting content_type explicitly prevents the main text from being converted to out_charset.
content_type : The content type of the main text. The list is the list of parameters attached to the type, e.g. ("text/plain", ["charset", mk_param "ISO-8859-1"]) (see Mimestring.mk_param). When this argument is set, the main text is no longer converted to out_charset. By default, when this argument is missing, the main text is converted from in_charset to out_charset, and the content type becomes "text/plain; charset=<out_charset>".
container_type : The content type of the container wrapping the main text and the attachment into one entity (only used if attachments are present). This defaults to ("multipart/mixed", []). This must be either a "multipart" or "message" type.
attachments : An optional list of attachments. Should be generated with wrap_attachment.

Character Set Conversion

The impact of in_charset and out_charset on the generated mail is not very obvious. The charset arguments may have an effect on the mail header and the mail body.

The mail header can only be composed of ASCII characters (7 bit). To circumvent this restriction the MIME standard specifies a special format, the so-called encoded words. These may only be used in some places, and compose knows where: In the subject, and the non-formal part of mail addresses. The out_charset is the character set used in the generated mail. The in_charset is the character set the strings are encoded you pass to compose. It is a good idea to have in_charset = out_charset, or at least choose out_charset as a superset of in_charset, because this ensures that the character set conversion succeeds.

If the mail header does not make use of the additional non-ASCII characters, the encoded words will be avoided.

The mail body is only subject of character set conversion if the content_type is not passed to compose. In this case, the function sets it to text/plain, and converts the message from in_charset to out_charset.

Adding Attachments

To generate the attachments, call Netsendmail.wrap_attachment, e.g.

 compose ...
      ~attachments:[ wrap_attachment  
                       ~content_type:("application/octet-stream", [])
                       (new Netmime.file_mime_body "file.tar.gz") ]
 

There are a number of kinds of attaching files, identified by container_type. The default is multipart/mixed, meaning that the parts of the mail are mixed messages and files. One can give a hint whether to display the parts directly in the mailer program (so-called inline attachments), or whether to suggest that the file is saved to disk ("real" attachments). This hint is contained in the Content-disposition header, see wrap_attachment how to set it.

For a discusion of the other container_types see the Tutorial at the end of this document.

val wrap_attachment : ?in_charset:Netconversion.encoding ->
?out_charset:Netconversion.encoding ->
?content_id:string ->
?content_description:string ->
?content_location:string ->
?content_disposition:string * (string * Mimestring.s_param) list ->
content_type:string * (string * Mimestring.s_param) list ->
Netmime.mime_body -> Netmime.complex_mime_message
Generates a header for the mime_body. The returned value is intended to be used as input for the attachments argument of the compose function:

 compose ...
    ~attachments:[ wrap_attachment
                      ~content_type:("audio/wav", [])
                      (new file_mime_body "music.wav") ]
 

The header contains at least the Content-type and the Content-transfer-encoding fields. The latter is currently always "base64", but it is possible that the function is changed in the future to also generate "quoted-printable" when applicable.


in_charset : The encoding of the content_description argument. Default: `Enc_iso88591.
out_charset : The encoding of the generated Content-Description header. Default: `Enc_iso88591.
content_id : Optionally sets the Content-ID header field. The passed string is the ID value without the embracing angle brackets. The Content-ID can be used to refer to the attachment from other parts of the mail, e.g. in multipart/related mails HTML documents can include hyperlinks to attachments using the URL syntax cid:ID where ID is the ID value.
content_description : The Content-Description header
content_location : The Content-Location header. This must be a valid URL, only composed of 7 bit characters, and with escaped unsafe characters
content_disposition : Optionally sets the Content-disposition header. Frequent values are
content_type : Specifies the content type with main type and list of parameters. Example: ("text/plain", ["charset", Mimestring.mk_param "ISO-8859-1" ]) (see Mimestring.mk_param)
val wrap_mail : ?in_charset:Netconversion.encoding ->
?out_charset:Netconversion.encoding ->
?from_addr:string * string ->
?cc_addrs:(string * string) list ->
?bcc_addrs:(string * string) list ->
to_addrs:(string * string) list ->
subject:string ->
Netmime.complex_mime_message -> Netmime.complex_mime_message
Sets the mail-related header fields in the input message, and returns a message ready for delivery. Transfer- and delivery-related header fields are removed from the message first, and the new fields are set to the values passed to this function.

The arguments are like in Netsendmail.compose.

The input message should have at least a Content-type header, but this is not enforced.

Use this function as an alternative to Netsendmail.compose, if the message is already available as complex_mime_message, e.g. to re-send a parsed mail message to a new destination.


Note: Resending Messages

Note that mails generated by wrap_mail always appear as new mails, not as forwarded or replied mails. In order to do the latter a different way of processing the message is needed.

val wrap_parts : ?in_charset:Netconversion.encoding ->
?out_charset:Netconversion.encoding ->
?content_type:string * (string * Mimestring.s_param) list ->
?content_id:string ->
?content_description:string ->
?content_location:string ->
?content_disposition:string * (string * Mimestring.s_param) list ->
Netmime.complex_mime_message list -> Netmime.complex_mime_message
Generates an intermediate container for multipart attachments. Use this if you want to bundle a set of attachments as a single attachment.


in_charset : The encoding of the content_description argument. Default: `Enc_iso88591.
out_charset : The encoding of the generated Content-Description header. Default: `Enc_iso88591.
content_type : The Content-Type header. Default: multipart/mixed
content_id : The Content-ID header, without the angle brackets
content_description : The Content-Description header
content_location : The Content-Location header. This must be a valid URL, only composed of 7 bit characters, and with escaped unsafe characters
content_disposition : The Content-Disposition header

Low-level
val create_address_list_tokens : ?in_charset:Netconversion.encoding ->
?out_charset:Netconversion.encoding ->
(string * string) list -> Mimestring.s_token list
Returns the list of s_tokens representing email addresses as structured value. The addresses are passed as list of pairs (human_readable, formal) as in the compose function above. The returned structured field value can be formatted and filled into a mail header. For example, to set the "To" header to "Stolpmann, Gerd" <gerd@gerd-stolpmann.de> use
 let sval = create_address_list_tokens ["Stolpmann, Gerd",
                                        "gerd@gerd-stolpmann.de"] in
 header # update_field "to" (format_field_value "to" sval)
 
This ensures that the field is correctly quoted, that appropriate encodings are applied and that long values are folded into several lines.


in_charset : The character encoding used for human_readable. Defaults to `Enc_iso88591.
out_charset : The character encoding used in the generated encoded word. This encoding must be ASCII-compatible. Defaults to `Enc_iso88591.
val create_text_tokens : ?in_charset:Netconversion.encoding ->
?out_charset:Netconversion.encoding -> string -> Mimestring.s_token list
Returns the list of s_tokens representing an informal text as structured value. The text is passed as simple string. The returned structured field value can be formatted and filled into a mail header. For example, to set the "Subject" header to "I have to say something", use
 let sval = create_text_tokens "I have to say something" in
 header # update_field "subject" (format_field_value "subject" sval)
 
This ensures that the field is correctly quoted, that appropriate encodings are applied and that long values are folded into several lines.


in_charset : The character encoding used for the input string. Defaults to `Enc_iso88591.
out_charset : The character encoding used in the generated encoded words. This encoding must be ASCII-compatible. Defaults to `Enc_iso88591.
val format_field_value : string -> Mimestring.s_token list -> string
To put sval, an s_token list, into the header field name, call

header # update_field name (format_field_value name sval)

The field value is folded into several lines, if necessary.


Sending Messages


val sendmail : ?mailer:string -> ?crlf:bool -> Netmime.complex_mime_message -> unit
Sends the passed message. The mailer program must be sendmail-compatible (this can be assumed on all Unix systems, even if a non-sendmail mailer is installed).

The mailer program is the command passed as mailer, which is by default a reasonable compile-time setting.

With crlf one can determine the EOL convention for the message piped to the mailer program: If crlf, CR/LF is used, if not crlf, only LF is used. The default is false for Unix systems.


Tutorial

Generating mail messages is a very complicated procedure. Netsendmail provides a comparatively simple interface to accomplish this task without knowing too much about the details of the mail format. Here is a kind of cookbook:

Generate an ASCII Mail

In the simplest case, the mail is an ASCII text. Generate the mail with

 
 compose ~from_addr ~to_addrs ~subject main_text
 

Here, from_addr is the sender's address as pair (name,formal_address), and to_addrs is the list of recipients in the same format. The variable subject contains the subject of the message as string. Finally, main_text is the ASCII text.

Generate an Internationalised Mail

When addresses or the main text contain non-ASCII characters, you should care about the in_charset and out_charset parameters. In general, the strings you pass to compose are encoded as in_charset, and the strings in the generated mail are encoded as out_charset. Usually, it is a good idea to have in_charset = out_charset, or out_charset as a superset of in_charset (otherwise you might get conversion errors).

The default for both parameters is `Enc_iso88591.

Not everything can be internationalised. In particular, the subject, the informal names of mail addresses, the content_description, and the main text can be encoded in a non-ASCII character set. Especially, the formal mail addresses cannot be internationalised. Example:

 compose 
   ~in_charset:`Enc_iso885915
   ~out_charset:`Enc_iso885915
   ~from_addr:("Heinz Dräger", "heinz@draeger.de") 
   ~to_addr:("Marion Schröder", "marion@irgendwo.de")
   ~subject:"Geschäftlich"
   "Verkaufe Teddy-Bären für 100¤"
 

Note that when you also pass content_type, the main text is no longer converted according to in_charset and out_charset. It is expected that the main text has already the right encoding, and that the encoding is indicated by the content_type. Example:

 compose 
   ~in_charset:`Enc_iso885915
   ~out_charset:`Enc_iso885915
   ~from_addr:("Heinz Dräger", "heinz@draeger.de") 
   ~to_addr:("Marion Schröder", "marion@irgendwo.de")
   ~content_type:("text/html", ["charset", Mimestring.mk_param "ISO-8859-1"])
   ~subject:"Geschäftlich"
   "<html><body>Verkaufe Teddy-Bären für 100&euro;</body></html>"
 
Here, the header fields are encoded in ISO-8859-15, but the main text uses ISO-8859-1.

The function Mimestring.mk_param encapsulates parameter values for several kinds of structured values that may occur in mail headers, here for Content-type. This function takes care of the appropriate representation of the parameter value (e.g. for parameters like "title" that can be internationalised).

Generate a Mail with Attachments

An attachment can simply be passed to compose. For example, to add a file "foo.gif":

 compose
   ...
   attachments:[ wrap_attachment
                   ~content_type:("image/gif", [])
                   (new Netmime.file_mime_body "foo.gif") ]
   ...
 

This creates a multipart/mixed mail. The class Netmime.file_mime_body encapsulates a file as a MIME body that can be attached to the mail (note: The file is not read as a whole into memory, but only chunk by chunk, so you can even attach large files without exhausting memory).

The type multipart/mixed has the special feature that the attached parts can either by displayed "inline" with the other contents, or suggested for saving in a file. This hint is indicated by the Content-disposition header. For example, to have the first attachment "inline", and the second as a file with name "foo.gif", use:

 compose
   ...
   attachments:[ wrap_attachment
                   ~content_type:("image/gif", [])
                   ~content_disposition:("inline", [])
                   (new Netmime.file_mime_body "foo.gif");
                 wrap_attachment
                   ~content_type:("image/gif", [])
                   ~content_disposition:("attachment", ["filename",
                                                        Mimestring.mk_param "foo.gif"])
                   (new Netmime.file_mime_body "foo.gif") ]
   ...
 

Generate a Multi-Format Mail

It is possible to generate messages where the main part is available in several formats, e.g. in text/plain and text/html. The mail reader program can select which format can be presented best to the user.

The compose function is not the right means to produce such a mail. It is better to use the more capable functions wrap_parts and wrap_mail for this purpose.

For example, to get a message with the text/plain version s_plain and the text/html version s_html, use:

 wrap_mail
    ~from_addr ~to_addrs ~subject
    (wrap_parts
       ~content_type:("multipart/alternative", [])
       [ wrap_attachment
           ~content_type:("text/plain", [])
           (new Netmime.memory_mime_body s_plain);
         wrap_attachment
           ~content_type:("text/html", [])
           (new Netmime.memory_mime_body s_html)
       ])
 

Here, wrap_attachment is used to encapsulate the two versions of the main text. This works because there is no difference between the format of an attachment and the format of a text part. (Actually, wrap_attachment should be better called wrap_body.) The class Netmime.memory_mime_body encapsulates a string as MIME body.

The function wrap_parts bundles the two versions to the main message, and wrap_mail adds the mail headers necessary to deliver the mail.

Note that the simplest version of the message should be added first, and the fanciest version of the message should be added last.

As a variant, one can also add file attachments. To do so, insert a multipart/mixed container around the multipart/alternative message:

 wrap_mail
    ~from_addr ~to_addrs ~subject
    (wrap_parts
       ~content_type:("multipart/mixed", [])
       [ wrap_parts
           ~content_type:("multipart/alternative", [])
           [ wrap_attachment
               ~content_type:("text/plain", [])
               (new Netmime.memory_mime_body s_plain);
             wrap_attachment
               ~content_type:("text/html", [])
               (new Netmime.memory_mime_body s_html)
           ];
         wrap_attachment
           ~content_type:("audio/wav", [])
           (new Netmime.file_mime_body "music.wav")
       ])
 

Generate an MHTML Message

MHTML is an HTML document with attached resource files like images or style sheets. For example, to have the HTML text s_html bundled with an image and a style sheet, use:

 wrap_mail
    ~from_addr ~to_addrs ~subject
    (wrap_parts
       ~content_type:("multipart/related",
                      [ "type", Mimestring.mk_param "text/html" ])
       [ wrap_attachment
           ~content_type:("text/html", [])
           (new Netmime.memory_mime_body s_html);
         wrap_attachment
           ~content_type:("image/gif", [])
           ~content_id:"img1"
           (new Netmime.file_mime_body "my.gif")
         wrap_attachment
           ~content_type:("text/css", [])
           ~content_id:"style1"
           (new Netmime.file_mime_body "style.css")
       ])
 

Note the content_id arguments that assign names to the individual parts. One can now refer to the parts from the HTML document by cid URLs, e.g. cid:img1 points to the image in the second part.

There is another mechanism using the Content-Location header to resolve hyperlinks to message parts. See RFC 2557 for details.