Envoyer des fax depuis Rails via OVH
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 format0123456789@ecofax.fr
déterminera le destinataire du fax (ici0123456789
) ; - le champ
Subject
doit être notre identifiant (ici0987654321
) ; - 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é.