Envoyer des fax depuis Rails via OVH

Première publication : 2015-05-21

Dans toute ma vie, j’ai du envoyer ou recevoir moins de fax que j’ai de doigts à toutes mes mains (et contrairement à certains, ça ne me fait que 10 doigts). En plus, en bon artisan du web, ce genre de technologie d’avant me fait plutôt rire qu’autre chose.

Mais force est de constater que certains métiers sont encore très attachés à ce genre de communication. Pour RoomRoom (un des sites sur lesquels je travaille ces derniers temps), nous devons communiquer des informations importantes à des hôteliers, pour qui l’e-mail n’est pas vraiment le moyen le plus fiable. Le bon vieux fax qui arrive directement derrière le comptoir de la réception, ça par contre, ça fonctionne bien et ça laisse une trace.

Donc, comment envoyer un fax depuis une application Rails ?

Le transport

Je n’envisage pas de brancher un modem sur mes serveurs, mais les services “en ligne” d’envoi de fax sont légions. Il est possible que certains aient même une API, mais le plus souvent il faut leur transmettre un document (idéalement un PDF), c’est pourquoi ça passe souvent par l’e-mail.

  • envoyer un e-mail : on sait faire ;
  • avec une pièce jointe : c’est très facile aussi ;
  • une pièce jointe en PDF, générée dynamiquement : c’est un peu plus sport mais on va y arriver.

Pour mon essai, j’ai choisi la solution EcoFax d’OVH. c’est du basique, mais ça marche et ça coûte 3 kopeks pour une utilisation occasionnelle (quelques envois par jour).

Alors concrètement, puisque notre appli Rails va envoyer un mail, on va pouvoir s’appuyer sur ActionMailer.

Le contenu de cet e-mail se résume à ceci :

  • le champ From déterminera l’expéditeur du fax ;
  • le champ To, au format 0123456789@ecofax.fr déterminera le destinataire du fax (ici 0123456789) ;
  • le champ Subject doit être notre identifiant (ici 0987654321) ;
  • le corps du message contient d’autres paramètres : au minimum password: AZERTYIUOP, mais en option la qualité, l’heure d’envoi…

Pour plus de détail, voici la FAQ d’OVH sur le sujet.

Voici donc un exemple de mailer :

class FaxMailer < ApplicationMailer
  def hotel_offer_on_sale(offer)
    file_name = "roomroom_offer#{offer.id}_on_sale.pdf"
    file = prepare_on_sale_fax_file(offer)
    attachments[file_name] = file.read

    config = Rails.application.secrets.ecofax

    mail(
      to: config.fetch("to") % { number: offer.hotel.fax_number },
      subject: config.fetch("login"),
      body: "password: #{config.fetch("password")}"
    )
  end
end

Les 3 premières lignes permettent de générer un fichier PDF attaché (détail plus loin), puis on envoie simplement le mail.

Voici l’extrait du fichier config/secrets.yml qui contient les éléments référencés :

production:
  ecofax:
    to: "%{number}@ecofax.fr"
    login: "0987654321"
    password:

Si on fait abstraction de la méthode prepare_on_sale_fax_file() on a quelque chose de fonctionnel, qui est capable d’envoyer un PDF attaché dans un e-mail.

Le contenu du fax

On pourrait utiliser une bibliothèque pour générer du PDF. Il existe en particulier Prawn. Mais c’est assez lourd et si on a déjà créé des vues pour notre application, on voudrait bien les réutiliser.

Dans notre cas, le fax est la copie presque parfaite d’une “mise en page HTML” également envoyée par e-mail. Du coup, on a tout ce qu’il nous faut.

Allons-y étape par étape. Comment générer un fichier HTML spécialement pour le fax ?

Le message en HTML

Dans Rails, les templates sont dans app/views/XXX_mailer/YYY, où XXX est le nom du mailer, et YYY est le nom de la méthode utilisée pour l’envoi du mail.

Puisque nous avons déjà un template, nous allons devoir l’adapter. En effet il n’y a pas de lien cliquable dans un fax, de même la feuille de style peut être différente pour optimiser la lisibilité en noir et blanc.

On va utiliser le principe de variante de Rails. En gros, ça permet de définir des variations sur un format, pour un périphérique particulier. Ici, on va utiliser le format “html” et définir une variante “fax”.

Notre template sera alors dans app/views/fax_mailer/hotel_offer_on_sale.html+fax.erb.

Voici un premier jet pour notre méthode de génération du fichier joint

def prepare_on_sale_fax_file(offer)
  @body = OpenStruct.new(
    offer_title: offer.title,
    offer_url: offer_url(slug: offer.slug),
    root_url:  root_url,
  )
  html = render_to_string template: "notification_mailer/hotel_offer_on_sale",
                          format: "html", variant: "fax"
end

Notez que j’ai choisi de ne créer qu’une seule variable d’instance (qui sera visible dans mon template). Une ivar pour chaque valeur marche très bien aussi, mais je trouve ça moins pratique et prévisible.

De plus, je génère les URL depuis le mailer et pas depuis la vue, ça économise des soucis plus tard, et ça simplifie la vue qui se contente d’injecter les valeurs dans le template.

On utilise ensuite la méthode render_to_string qui va renvoyer une chaîne avec le résultat du template généré. Le format “html” et la variante “fax” sont explicitement indiqués pour forcer le rendu en utilisant ces réglages là.

Là ça ne marche pas encore, car la méthode renvoie une longue chaîne de caractères alors que le mailer attends un IO (ou en tous cas un objet qui réponde à read()).

Convertir le HTML en PDF

Il existe une très bonne bibliothèque pour transformer du HTML en PDF (ou en image) : wkhtmltopdf.

Elle est disponible sous forme de paquet pour de nombreuses distributions. Ses sources sont également disponibles.

La version 0.9 disponible (notamment) dans le dépôt officiel Debian nécessite un serveur X (pas cool sur un serveur), mais une version 0.12 du paquet est dispo pour une installation manuelle.

Voilà comment je l’ai installée (sous Debian) :

# aptitude install xfonts-base xfonts-75dpi
# cd /tmp
# wget http://downloads.sourceforge.net/wkhtmltopdf/wkhtmltox-0.12.2.1_linux-wheezy-amd64.deb
# dpkg -i wkhtmltox-0.12.2.1_linux-wheezy-amd64.deb

Nous n’allons pas l’utiliser en direct, mais plutôt via une gem Ruby : WickedPdf. Elle se chargera de faire un appel à wkhtmltopdf avec les bon paramètres.

Voici l’évolution de notre méthode :

def prepare_on_sale_fax_file(offer)
  @body = OpenStruct.new(
    offer_title: offer.title,
    offer_url: offer_url(slug: offer.slug),
    root_url:  root_url,
  )
  html = render_to_string template: "fax_mailer/hotel_offer_on_sale",
                          format: "html", variant: "fax"
  pdf = WickedPdf.new.pdf_from_string(html)

  # On utilise un StringIO plutôt qu'un fichier sauvegardé
  # mais l'interface est la même `#read()` pour accéder au contenu
  StringIO.new(pdf)
end

Là nous avons maintenant une chaîne de caractère qui contient le code PDF, que nous embarquons au final dans un StringIO afin qu’elle se comporte comme un descripteur de fichier.

Et voilà !

Optimiser du rendu

Le fax n’est pas une technologie qui apporte une grande qualité, surtout à l’arrivée sur des vieilles machines.

Pour une meilleure lisibilité, je vous conseille une page sur fond blanc, avec du texte suffisamment gros, écrit en noir.

Si vous avez un logo, utilisez la version monochrome que votre graphiste aura spécialement préparée (si vous travaillez avec STPO, il y pense à chaque fois).

Evitez les ombres portées (de texte ou de bloc), éviter les traits très fins, les dégradés de couleur…

Pour avoir un bon rendu au niveau du PDF, utilisez une feuille de style et une mise en page simple. Dans mon cas, c’était déjà très simple au départ car le template était optimisé pour un rendu dans un client mail, donc je n’ai presque rien modifié.