Hi,
this is a simple application to easily send CVs when the application is identical (except for the company). A form is displayed with information to fill in (which can be hard-coded as in the following example). The required information is basically email sending information + a resume + a cover letter + the recipient's details.
The cover letter must actually a template. The script replaces <COMPANY> with the company name filled in the form. And <GENDER> by the appropriate "dear sir/ dear madam etc." specified in the form.
Anyway, hopefully the script will make it clearer.
(* ============ file Mailer.fs =========== *)
module Mailer
open System
open System.Net.Mail
type person = {first_name : string ; last_name: string ; email : string}
with
static member make f l e = {first_name = f ; last_name = l ; email = e}
end
let send_mail (client : #SmtpClient) sender subject body attachment_paths f dest =
let sender = new MailAddress(sender.email, sender.first_name ^ sender.last_name) in
let dest = new MailAddress(dest.email, dest.first_name ^ dest.last_name) in
let email = new MailMessage(sender, dest) in
email.ReplyTo <- sender ;
email.Subject <- subject ;
email.Body <- body ;
attachment_paths |> List.iter (fun a -> email.Attachments.Add(new Attachment(a))) ;
client.Send(email) ;
match f with
| Some f -> f dest subject body attachment_paths System.DateTime.Now
| _ -> ()
let send_mass_mail (client : #SmtpClient) sender subject body attachment_paths f dests =
dests |> List.iter (send_mail client sender subject body attachment_paths f)
(* ============ file Word.fs =========== *)
module Word
open System
open System.Runtime.InteropServices
open System.Reflection (* For Missing.Value *)
open System.Collections
open Microsoft.Office.Interop.Word
let app = new ApplicationClass()
let none = ref (box Missing.Value)
(** Open a word document and return a handle to it. *)
let open_file (file : string) = app.Documents.Open(
ref (box file), //FileName
none, //ConfirmVersions
none, //ReadOnly
none, //AddToRecentFiles
none, //PasswordDocument
none, //PasswordTemplate
none, //Revert
none, //WritePasswordDocument
none, //WritePasswordTemplate
none, //Format
none, //Enconding
none, //Visible
none, //OpenAndRepair
none, //DocumentDirection
none, //NoEncodingDialog
none, //XMLTransform
none //NoAutoRepairDialog
) :?> DocumentClass
let replace_word replace_all (to_replace : string) (to_replace_with : string) =
let selection = app.ActiveWindow.Selection in
try
ignore (selection.Find.Execute(ref (box to_replace), // Text
none, // MatchCase
none, // MatchWholeWord
none, // MatchWildCards
none, // MatchSoundsLike
none, // MatchAllWordForms
none, // Forward
ref (box WdFindWrap.wdFindContinue), // Wrap
none, // Format
ref (box to_replace_with), // ReplaceWith
(if replace_all then ref (box WdReplace.wdReplaceAll) else none), // Replace
none, // MatchKashida
none, // MatchDiatrics
none, // MatchAlefHamza
none, // MatchControl
none, // MatchPrefix
none, // MatchSuffic
none, // MatchPhrase
none, // IgnoreSpace
none)) // IgnorePunct
with
| e -> print_string e.Message
let save_as (doc : DocumentClass) name = doc.SaveAs(
ref (box name), //FileName
none, // FileFormat
none, // LockComments
none, // PassWord
none, // AddToRecentFiles
none, // WritePassword
none, // ReadOnlyRecommended
none, // EmbedTrueTypeFonts
none, // SaveNativePictureFormat
none, // SaveFormsData
none, // SaveAsAOCELetter,
none, // Encoding
none, // InsertLineBreaks
none, // AllowSubstitutions
none, // LineEnding
none // AddBiDiMarks
)
(* Show the application. Uncomment if you want to see the macro running. *)
let make_app_visible b = app.Visible <- b
let quit () =
app.ActiveWindow.Close(ref (box WdSaveOptions.wdDoNotSaveChanges),none);
app.Quit(none, none, none)
(* ============ file ApplicationStrings.fs =========== *)
module ApplicationStrings
module Labels =
begin
let title = "Send your CV"
let first_name = "First Name"
let last_name = "Last Name"
let e_mail = "E-Mail Address"
let smtp_server = "SMTP Server"
let login = "Login"
let password = "Password"
let resume = "Resume"
let cover_letter = "Cover Letter"
let browse = "Browse..."
let attachment = "Attachment"
let company = "Employer"
let company_sector = "Sector"
let employer_e_mail = "Employer E-mail"
let gender = "Dear..."
let genders = ["Dear Sir"; "Dear Madam"; "Dear Sir/Madam"]
let body = "E-mail Body"
let send = "Send"
let cancel = "Cancel"
let gender_in_cover_letter = "<GENDER>"
let company_in_cover_letter = "<COMPANY>"
let subject s = "Job opportunities at " ^ s ^ " - Spontaneous application"
end
module DefaultValues =
begin
let first_name = "Julien"
let last_name = ""
let e_mail = "xyz@yahoo.fr"
let smtp_server = "smtp.mail.yahoo.com"
let login = "xyz"
let password = "my_fantastic_unguessable_password"
let resume = "C:\Documents and settings\Julien\Bureau\Candidacy\CV_Julien.doc"
let cover_letter = "C:\Documents and settings\Julien\Bureau\Candidacy\coverLetter.doc"
let company = ""
let employer_e_mail = "recipient@company.com"
let gender = "Dear Sir"
let body = "E-mail Body"
end
(* ============ file CVSenderForm.fs =========== *)
module CVSenderForm
open System
open System.Drawing
open System.Windows.Forms
module L = ApplicationStrings.Labels
(** Base form *)
let form = new Form()
do form.Text <- L.title
do form.AutoSize <- true
do form.AutoSizeMode <- AutoSizeMode.GrowAndShrink
do form.Padding <- new Padding(5)
do form.StartPosition <- FormStartPosition.CenterScreen
(** Table layout that contains all elements *)
let layout = new TableLayoutPanel ()
let _ =
layout.Dock <- DockStyle.Fill ;
layout.AutoSize <- true ;
layout.ColumnCount <- 3
(** Helper function that adds a row to the table layout :
- element one : label
- element two : input
*)
let add_line (layout : TableLayoutPanel) sep (text : Label) (w : #Control) =
let row = layout.RowCount + 1 in
layout.RowCount <- row ;
let sep_label = new Label() in
sep_label.Text <- sep ;
let f (c : #Control) =
c.AutoSize <- true ;
c.Anchor <- Enum.combine [AnchorStyles.Top; AnchorStyles.Left; AnchorStyles.Bottom; ]
in
f sep_label;
f text;
f w ;
sep_label.TextAlign <- ContentAlignment.MiddleLeft ;
text.TextAlign <- ContentAlignment.MiddleLeft ;
text.Click.Add( fun _ -> let _ = w.Focus() in () ) ;
layout.Controls.Add(sep_label, 1, row) ;
layout.Controls.Add(text, 0, row) ;
layout.Controls.Add(w, 2, row)
(** Settings Forms components *)
let first_name = new TextBox()
let first_name_label = new Label()
do first_name_label.Text <- L.first_name
do first_name_label.Click.Add (fun _ -> let _ = first_name.Focus() in () )
do add_line layout ":" first_name_label first_name
(* ---------------- *)
let last_name = new TextBox()
let last_name_label = new Label()
do last_name_label.Text <- L.last_name
do last_name_label.Click.Add (fun _ -> let _ = last_name.Focus() in () )
do add_line layout ":" last_name_label last_name
(* ---------------- *)
let email = new TextBox()
let email_label = new Label()
do email_label.Text <- L.e_mail
do add_line layout ":" email_label email
(* ---------------- *)
let smtp_server = new TextBox()
do smtp_server.Width <- 2 * smtp_server.Width
let smtp_server_label = new Label()
do smtp_server_label.Text <- L.smtp_server
do smtp_server_label.Click.Add (fun _ -> let _ = smtp_server.Focus() in () )
do add_line layout ":" smtp_server_label smtp_server
(* ---------------- *)
let login = new TextBox()
let login_label = new Label()
do login_label.Text <- L.login
do login_label.Click.Add (fun _ -> let _ = login.Focus() in () )
do add_line layout ":" login_label login
(* ---------------- *)
let password = new TextBox()
do password.PasswordChar <- '*'
let password_label = new Label()
do password_label.Text <- L.password
do password_label.Click.Add (fun _ -> let _ = password.Focus() in () )
do add_line layout ":" password_label password
(* ---------------- *)
let cv_panel = new FlowLayoutPanel()
do cv_panel.Dock <- DockStyle.Fill
do cv_panel.FlowDirection <- FlowDirection.LeftToRight
do cv_panel.Margin <- new Padding(0)
let cv = new TextBox()
do cv.Width <- 2 * cv.Width
let cv_button = new Button()
do cv_button.Text <- L.browse
do cv_button.Click.Add( fun _ ->
let result = new OpenFileDialog() in
result.InitialDirectory <- if cv.Text.Length >0 then cv.Text else "c:\\" ;
if (result.ShowDialog() = DialogResult.OK) then
cv.Text <- result.FileName
)
do cv_panel.Controls.Add(cv)
do cv_panel.Controls.Add(cv_button)
let cv_label = new Label()
do cv_label.Text <- L.resume
do cv_label.Click.Add (fun _ -> let _ = cv_button.Focus() in () )
do add_line layout ":" cv_label cv_panel
(* ---------------- *)
let cover_letter_panel = new FlowLayoutPanel()
do cover_letter_panel.Dock <- DockStyle.Fill
do cover_letter_panel.FlowDirection <- FlowDirection.LeftToRight
do cover_letter_panel.Margin <- new Padding(0)
let cover_letter = new TextBox()
do cover_letter.Width <- 2 * cover_letter.Width
let cover_letter_button = new Button()
do cover_letter_button.Text <- L.browse
do cover_letter_button.Click.Add( fun _ ->
let result = new OpenFileDialog() in
result.InitialDirectory <- if cover_letter.Text.Length >0 then cover_letter.Text else "c:\\" ;
if (result.ShowDialog() = DialogResult.OK) then
cover_letter.Text <- result.FileName
)
do cover_letter_panel.Controls.Add(cover_letter)
do cover_letter_panel.Controls.Add(cover_letter_button)
let cover_letter_label = new Label()
do cover_letter_label.Text <- L.cover_letter
do cover_letter_label.Click.Add (fun _ -> let _ = cover_letter_button.Focus() in () )
do add_line layout ":" cover_letter_label cover_letter_panel
(* ---------------- *)
let company = new TextBox()
let company_label = new Label()
do company_label.Text <- L.company
do add_line layout ":" company_label company
(* ---------------- *)
let dest = new TextBox()
let dest_label = new Label()
do dest_label.Text <- L.employer_e_mail
do add_line layout ":" dest_label dest
(* ---------------- *)
let gender = new ComboBox()
do L.genders |> List.iter (fun g -> ignore (gender.Items.Add (g)) )
do gender.SelectedIndex <- 0
let gender_label = new Label()
do gender_label.Text <- L.gender
do add_line layout ":" gender_label gender
(* ---------------- *)
let email_body = new TextBox()
do email_body.Multiline <- true
do email_body.WordWrap <- true
do email_body.ScrollBars <- ScrollBars.Vertical
do email_body.Width <- 3 * email_body.Width
do email_body.Height <- 5 * email_body.Height
let email_body_label = new Label()
do email_body_label.Text <- L.body
do add_line layout ":" email_body_label email_body
(* ---------------- *)
let validation_panel = new FlowLayoutPanel()
do validation_panel.Dock <- DockStyle.Fill
do validation_panel.FlowDirection <- FlowDirection.LeftToRight
do validation_panel.Margin <- new Padding(0)
let ok = new Button()
do ok.Text <- L.send
let cancel = new Button()
do cancel.Text <- L.cancel
do cancel.Click.Add(fun _ -> form.Close())
do validation_panel.Controls.Add(ok)
do validation_panel.Controls.Add(cancel)
do add_line layout " " (new Label()) validation_panel
(* ---------------- *)
do form.Controls.Add(layout)
(* ============ file main.fs =========== *)
open System
open System.Net.Mail
open System.Windows.Forms
open CVSenderForm
module L = ApplicationStrings.Labels
module DV = ApplicationStrings.DefaultValues
let new_cover_letter () =
let path = cover_letter.Text in
let path = path.Replace("\\", "\\\\") in
let doc = Word.open_file path in
let replace_words lst = lst |> List.iter (fun (before, after) -> Word.replace_word true before after) in
let words =
(L.gender_in_cover_letter, gender.Text) ::
(L.company_in_cover_letter, company.Text) ::
[]
in words |> replace_words ;
let path_parts = path.Split([|'.'|], 2) in
let before, after = path_parts.(0), path_parts.(1) in
let new_path = before :: ("_" ^ company.Text.Replace(" ","") ^ ".") :: after :: [] |> String.concat "" in
Word.save_as doc new_path ;
Word.quit() ;
new_path
(* ================================================================ *)
let send_job_application () =
(** Check *)
let debug s = Console.WriteLine(s ^ "\n") in
(** Make sure everything's filled *)
if first_name.Text.Length = 0
|| last_name.Text.Length = 0
|| email.Text.Length = 0
|| login.Text.Length = 0
|| password.Text.Length = 0
|| cv.Text.Length = 0
|| cover_letter.Text.Length = 0
|| smtp_server.Text.Length = 0
|| company.Text.Length = 0
|| dest.Text.Length = 0
|| email_body.Text.Length = 0
then begin
debug "Make sure all fields are filled, and that attachments refer to files and not directories.";
failwith ""
end ;
(** Prepare the mail info *)
let login = login.Text in
let password = password.Text in
let server = smtp_server.Text in
let client = new SmtpClient(server) in
client.Credentials <- new System.Net.NetworkCredential(login, password) ;
(** Process the attachments *)
let cover_letter_path = new_cover_letter() in
let cv_path = cv.Text.Replace("\\", "<A>\\\\</A>") in
let attachments = cv_path :: cover_letter_path :: [] in
(** Sender *)
let me = Mailer.person.make first_name.Text last_name.Text email.Text in
(** Mail content *)
let subject = L.subject company.Text in
let body = gender.Text ^ "," ^Environment.NewLine ^ Environment.NewLine ^ email_body.Text in
let dest = Mailer.person.make "" "" dest.Text in
(** Send the mail *)
try
debug ("********************************************************************");
debug ("Sender : " ^ any_to_string me) ;
debug ("Login : " ^ login) ;
debug ("Password : " ^ password) ;
debug ("Dest : " ^ any_to_string dest) ;
debug ("Server : " ^ any_to_string client) ;
attachments |> List.iter (fun a -> debug ("Attachment : " ^ a)) ;
debug ("Subject : " ^ subject) ;
debug ("Body : " ^ body) ;
Mailer.send_mail client me subject body attachments None dest ;
debug ("===================================");
debug ("= MAIL SENT");
debug ("===================================");
debug ("********************************************************************");
with e ->
begin
debug e.Message ;
debug ("********************************************************************");
raise e
end
(* ================================================================ *)
(** Fill the form with the default settings *)
let prepare_form () =
first_name.Text <- DV.first_name;
last_name.Text <- DV.last_name;
email.Text <- DV.e_mail ;
smtp_server.Text <- DV.smtp_server;
login.Text <- DV.login;
password.Text <- DV.password;
cv.Text <- DV.resume;
cover_letter.Text <- DV.cover_letter;
company.Text <- DV.company ;
dest.Text <- DV.employer_e_mail ;
email_body.Text <- DV.body
(* ================================================================ *)
[<STAThread>]
let _ =
Application.EnableVisualStyles () ;
prepare_form() ;
ok.Click.Add(fun e -> try send_job_application() with _ -> ignore(MessageBox.Show("Failure sending the email. See the console.")) ) ;
Application.Run(form)
Of course, this can very easily be adapted to send emails to whatever recipient source that you have.
Notes :
- you need the Microsoft.Office.Interop.Word.dll file
- need the .Net Framework 2.0 (and so do the people using it)
- using smtp.mail.free_provider.com will fail with some internet access providers, so replace it with your actual access provider if you can...
- example cover_letter.doc : <GENDER>, I reaaaaaally love <COMPANY>, so pleassssse, hire me. I'll do everything I can to make <COMPANY> richer and happier. So once again, hire me, please!