Email made Easier
Sending emails from the Oracle database can be both simply deceptively braindead easy, and confoundingly perplexingly awful at the same time. Easy, because all you have to do is call one of the supplied mail packages to send an email:
UTL_MAIL.send (sender => 'sender@host.com' ,recipients => 'recipient@example.com' ,subject => 'Test Subject' ,message => 'Test Message');
APEX_MAIL.send (p_from => 'sender@host.com' ,p_to => 'recipient@example.com' ,p_subj => 'Test Subject' ,p_body => 'Test Message');
If you want more control over your emails you can use UTL_SMTP
instead; this is what I’ve been using for the past few years because I like feeling in control (doesn’t everyone?). This is the package that APEX_MAIL
is built on top of.
If you just don’t trust these high-level abstractions you can use UTL_TCP
and interact directly with the mail server. I don’t know, maybe your mail server does some weird stuff that isn’t supported by the standard packages.
If you want to send attachments, you can build this yourself in UTL_SMTP or UTL_TCP, but it’s easier with APEX_MAIL which can send BLOBs. UTL_MAIL can send attachments but only up to 32K in size (half that for binary files which become base64 encoded).
Let’s make up a checklist of features supported out of the box (i.e. without requiring you to write non-trivial code) and see how they stack up:
APEX_MAIL | UTL_MAIL | UTL_SMTP | UTL_TCP | |
---|---|---|---|---|
Attachments | Yes | Yes (<32K) | No* | No* |
Asynchronous | Yes | No | No | No |
Rate limited | Yes | No | No | No |
Anti-Spam | No* | No* | No* | No* |
SSL/TLS | Yes | No | No* | No* |
Authentication | Yes | No | No* | No* |
Features marked “No*”: these are not natively supported by the API, but generic API routines for sending arbitrary data (including RAW) can be used to build these features, if you’re really keen or you can google the code to copy-and-paste.
(Note: of course, you can add the Asynchronous and Rate limiting features to any of the UTL_* packages by writing your own code.)
Asynchronous
Calls to the API to send an email do not attempt to connect to the mail server in the same session, but record the email to be sent soon after in a separate session.
This provides two benefits:
- It allows emails to be transactional – if the calling transaction is rolled back, the email will not be sent; and
- It ensures the client process doesn’t have to wait until the mail server responds, which might be slow in times of peak load.
Anti-Spam
Sending an email within an organisation is easy; internal mail servers don’t usually filter out internal emails as spam. Sending an email across the internet at large is fraught with difficulties, which can rear their ugly heads months or years after going live. One day your server tries to send 100 emails to the same recipient in error, and all of a sudden your IP is blocked as a spammer and NO emails get sent, with no warning.
For the last two years I’ve been battling this problem, because my site allows my clients to broadcast messages to their customers and partners via email and SMS. The SMS side worked fine, but emails frequently went AWOL and occasionally the whole site would get spam blocked. Most emails to hotmail went into a black hole and I was always having to apologise to anyone complaining about not getting their emails – “You’re not using a hotmail address by any chance? ah, that’s the problem then – sorry about that. Do you have any other email address we can use?”
I added some rate-limiting code to ensure that my server trickled the emails out. My server was sending about 2,000 to 3,000 per month, but sometimes these were sent in short spikes rather than spread out over the month. My rate-limiting meant a broadcast to 200 people could take several hours to complete, which didn’t seem to bother anyone; and this stopped the “too many emails sent within the same hour” errors from the mail server (I was using my ISP’s mail server).
I managed to improve the situation a little by implementing SPF (Sender Policy Framework). But still, lots of emails went missing, or at least directly into people’s spam folders.
I looked into DKIM as well, but after a few hours reading I threw that into the “too hard basket”. I decided that I’d much prefer to outsource all this worry and frustration to someone with more expertise and experience.
Searching for an Email Gateway
I’ve been hosting my site on Amazon EC2 for a long time now with great results and low cost, and I’ve also been using Amazon S3 for hosting large files and user-uploaded content. Amazon also provides an Email Gateway solution called SES which seemed like a logical next step. This service gives 62,000 messages per month for free (when sent from an EC2 instance) and you just get charged small amounts for the data transfer (something like 12c per GB).
I started trying to build a PL/SQL API to Amazon SES but got stuck trying to authenticate using Amazon’s complicated scheme. Just to make life interesting they use a different encryption algorithm for SES than they do for S3 (for which I already had code from the Alexandria PL/SQL library). It was difficult because their examples all assumed you’ve installed the Amazon SDK.
It always rejected anything I sent, and gave no clues as to what I might be doing wrong. In the end I decided that what I was doing wrong was trying to work this low-level stuff out myself instead of reusing a solution that someone else has already worked out. A good developer is a lazy developer, so they say. So I decided to see what other email gateways are out there.
I looked at a few, but their costs were prohibitive for my teeny tiny business as they assumed I am a big marketing company sending 100,000s of emails per month and would be happy to pay $100’s in monthly subscriptions. I wanted a low-cost, pay-per-use transactional email service that would take care of the DKIM mail signing for me.
Mailgun
In the end, I stumbled upon Mailgun, a service provided by Rackspace. Their service takes care of the DKIM signing for me, do automated rate limiting (with dynamic ramp up and ramp down), it includes 10,000 free emails per month, and extra emails are charged at very low amounts per email with no monthly subscription requirement.
Other benefits I noticed was that it allows my server to send emails by two methods: (1) RESTful API and (2) SMTP. The SMTP interface meant that I was very quickly able to use the service simply by pointing my existing APEX mail settings and my custom UTL_SMTP solution directly to the Mailgun SMTP endpoint, and it worked out of the box. Immediately virtually all our emails were getting sent, even to hotmail addresses. I was able to remove my rate limiting code. Other bonuses were that I now had much better visibility of failed emails – the Mailgun online interface provides access to a detailed log including bounces, spam blocks and other problems. So far I’ve been using it for a few weeks, and of 2,410 emails attempted, 98.55% were delivered, and 1.45% dropped. The emails that were dropped were mainly due to incorrect email addresses in my system, deliberately “bad” test emails I’ve tried, or problems on the target mail servers. One email was blocked by someone’s server which was running SpamAssassin. So overall I’ve been blown away by how well this is running.
Once I had my immediate problem solved, I decided to have a closer look at the RESTful API. This provides a few intriguing features not supported by the SMTP interface, such as sending an email to multiple recipients with substitution strings in the message, and each recipient only sees their own name in the “To” field. My previous solution for this involved sending many emails; the API means that I can send the request to Mailgun just once, and Mailgun will send out all the individual emails.
Another little bonus is that Mailgun’s API also includes a souped-up email address validator. This validator doesn’t just check email addresses according to basic email address formatting, it also checks the MX records on the target domain to determine whether it’s likely to accept emails. For some domains (such as gmail.com and yahoo.com) I noticed that it even does some level of checking of the user name portion of the email address. It’s still not absolutely perfect, but it’s better than other email validation routines I’ve seen.
Note: Mailgun supports maximum message size of 25MB.
Email Validation Plugin
Mailgun also provide a jQuery plugin for their email address validator which means you can validate user-entered email addresses on the client before they even hit your server. To take advantage of this in Oracle APEX I created the Mailgun Email Validator Dynamic Plugin that you can use and adapt if you want.
PL/SQL API
If you follow me on twitter you are probably already aware that I’ve started building a PL/SQL API to make the Mailgun RESTful API accessible to Oracle developers. You can try it out for yourself by downloading from here if you want. The WIKI on Github has detailed installation instructions (it’s a little involved) and an API reference.
The API supports the following Mailgun features:
- Email validation – does the same thing as the jQuery-based plugin, but on the server
- Send email
- Attachments (CLOB or BLOB)
- Inline images
- Mailgun tags
- Custom mail headers
- Query Mailgun logs – bounces, unsubscribes, spam complaints
e.g.
MAILGUN_PKG.send_email (p_from_email => 'sender@host.com' ,p_to_email => 'recipient@example.com' ,p_subject => 'Test Subject' ,p_message => 'Test Message' );
The API Reference has a lot more detailed examples.
My implementation of the Send Email API so far supports the following features:
MAILGUN_PKG | |
---|---|
Attachments | Yes |
Asynchronous | Yes |
Rate limited | Yes* |
Anti-Spam | Yes* |
SSL/TLS | Yes, required |
Authentication | Yes |
Features marked “Yes*”: these are provided by the Mailgun service by default, they are not specific to this PL/SQL API.
I’m planning to add more features to the API as-and-when I have a use for them, or if someone asks me very nicely to build them. I’ll be pleased if you fork the project from Github and I welcome your pull requests to merge improvements in. I recommend reading through the Mailgun API Documentation for feature ideas.
If you use either of these in your project, please let me know as I’d love to hear about your experience with it.
Link: Oracle PL/SQL API for Mailgun
This article was not solicited nor paid for by Mailgun. I just liked the service so I wanted to blog about it.
UPDATE 26/4/2016: release 0.4 now uses Oracle AQ to enable asynchronous calls.
UPDATE 5/11/2016: release 0.6 adds APIs for querying your Mailgun logs. Also, added a note about UTL_MAIL’s attachment size limit.
UPDATE 4/10/2017: release 1.0 fixes some bugs and adds a few small features.
wphilltech
22 April 2016 - 12:18 am
Jeff – Thanks for this! I have been looking at similar sending / blocking of mail issues. In my case – outlook.com address are just sent to spam directly by GMAIL. Very frustrating. I will be taking a closer look at Mailgun.
Sammeer S Raawat
18 May 2020 - 1:38 pm
Jeff- Thanks for same…
the email validator prompts for user id and password everytime DA is fired.
Jeffrey Kemp
18 May 2020 - 1:41 pm
Hi Sammeer, if you notice an issue with the plugin please raise it on the Issues page. https://github.com/jeffreykemp/jk64-plugin-mailgunemailvalidator/issues
carsten
26 November 2021 - 6:29 pm
Looks like that the changed the pricing model. Now you will get “5,000 free emails per month for 3 months, then only pay for what you send”
For my 2-3 mails per month it’s to much. 😉
George
28 November 2021 - 4:48 am
Hi Carsten,
FWIW, GMail will allow you to use their SMTP service, you just need to set it up one time (to prove you arent spamming with it). The process for that is quite straightforward.
Hope that helps?
Nikolas Saridakis
27 December 2021 - 11:19 pm
Hello!
Is it possible to use this service with Oracle Cloud Autonomous Database?
E-mail delivery became paid to Europe and I can’t seem to be able to use the API on my server…
Thank you!
Nik
Jeffrey Kemp
19 January 2022 - 7:35 am
Hi Nikolas, I’m pretty sure it should work although depending on which service you’re using there may be limitations. I’d be interested to learn if you encounter any issues.