Go to content

Send email with iCalendar events from your website

Published on

Let’s say your website or intranet contains information about events, seminars or courses. Your visitors can view them and subscribe online to attend the events. Wouldn’t it be nice if they receive an email that contains all the event data in the correct format for their calendaring system so they don’t have to enter all the data by hand? In this blog post I’ll explain how to generate an iCalendar file from event data managed in Hippo CMS. I assume you have already reached the Grazing Hippo level.

  1. Add the iCal4j, Commons Email, javamail and activation dependencies to your project
  2. Create a content type that has at least a title, two date fields, and a (plain text) summary field (or implement this IEventDocument).
  3. Create an HST component and a (JSP) template. In the JSP template, create a form in which the visitor can fill in his or her email address.

Now that the basics are set up, you can add the logic to your component.

To create a (ical4j) Calendar:

/**
 * Creates {@link net.fortuna.ical4j.model.Calendar} object with the {@link VEvent}
 *
 * @param eventDocument instance of {@link IEventDocument}
 * @param invitee       email address of the calendar owner
 * @return {@link net.fortuna.ical4j.model.Calendar}
 */
net.fortuna.ical4j.model.Calendar createEventCalendar(
        final IEventDocument eventDocument, final String invitee) {
    net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar();
    calendar.getProperties().add(new ProdId("-//Hippo Event Calendar//iCal4j 1.0//EN"));
    calendar.getProperties().add(Version.VERSION_2_0);
    calendar.getProperties().add(CalScale.GREGORIAN);

    VEvent vEvent = createVEvent(eventDocument, invitee);

    calendar.getComponents().add(vEvent);
    return calendar;
}

It calls our own createVEvent method which contains the logic of mapping the IEventDocument with the VEvent. It does not check if the event is an all day event. You could do that by parsing the date fields from the CMS or just add a checkbox to your event template and use that. An other issue to think about is the timezone information. Now we send an iCalendar invitation that assumes both the editor and the visitor are in the same timezone.

/**
 * Creates {@link VEvent} using values from the eventDocument
 *
 * @param eventDocument instance of {@link IEventDocument}
 * @param invitee       email address of the calendar owner
 * @return {@link VEvent}
 * @throws HstComponentException if no {@link Uid} can be generated for the event
 */
VEvent createVEvent(final IEventDocument eventDocument, final String invitee) {
    Date start = new DateTime(eventDocument.getDate().getTime());
    Date end = new DateTime(eventDocument.getEndDate().getTime());
    VEvent vEvent = new VEvent(start, end, eventDocument.getTitle());
    Uid uid = new Uid(eventDocument.getCanonicalHandleUUID());
    vEvent.getProperties().add(uid);
    // EventDocument title => vEvent summary
    // EventDocument summary => vEvent description
    if (StringUtils.isNotBlank(eventDocument.getSummary())) {
        vEvent.getProperties().add(new Description(eventDocument.getSummary()));
    }
    Organizer organizer = new Organizer(URI.create("mailto:" + invitee));
    vEvent.getProperties().add(organizer);
    return vEvent;
}

So now that we have the necessary objects, all we need to do is create send the email.

/**
 * Sends an email message with the event as (iCalendar) attachment
 *
 * @param eventDocument instance of {@link IEventDocument}
 * @param mailTo        email address that receives the invitation
 * @param mailHost      smtp host name
 * @param mailPort      smtp host port
 * @param mailFrom      from email address
 */
private void sendMail(final IEventDocument eventDocument, final String mailTo,
                      final String mailHost, final int mailPort, final String mailFrom) {
    Calendar calendar = createEventCalendar(eventDocument, mailTo);
    byte[] attachmentData = calendarAsByteArray(calendar);

    MultiPartEmail email = new MultiPartEmail();
    email.setHostName(mailHost);
    email.setSmtpPort(mailPort);

    try {
        email.addTo(mailTo);
        email.setFrom(mailFrom);
        email.setSubject(eventDocument.getTitle());
        email.setMsg(eventDocument.getSummary());
        String name = eventDocument.getName() + ".ics";
        String contentType = String.format("text/calendar; name="%s"", name);
        email.attach(new ByteArrayDataSource(attachmentData, contentType),
                name, "", EmailAttachment.ATTACHMENT);
        email.send();
    } catch (EmailException e) {
        throw new HstComponentException("Failed to send mail with event info", e);
    }
}

/**
 * Converts {@link net.fortuna.ical4j.model.Calendar} to a byte[]
 *
 * @param iCalendar {@link Calendar}
 * @return byte[] of the Calendar
 * @throws HstComponentException if the Calendar is invalid or its output can't be written
 */
private byte[] calendarAsByteArray(final Calendar iCalendar) {
    byte[] bytes;
    try {
        ByteArrayOutputStream output;
        output = new ByteArrayOutputStream();
        CalendarOutputter outputter = new CalendarOutputter();
        outputter.output(iCalendar, output);
        bytes = output.toByteArray();
    } catch (ValidationException e) {
        throw new HstComponentException("Could not validate iCalendar", e);
    } catch (IOException e) {
        throw new HstComponentException("Could not write iCalendar to stream", e);
    }
    return bytes;
}

Override BaseHstComponent#doAction to collect the mail settings and send the mail.

/**
 * Sends the event data as iCalendar attachment to the site visitor
 * <p/>
 * Necessary HST component parameters
 * <dl>
 * <dt>mailhost</dt>
 * <dd>SMTP server hostname</dd>
 * <dt>mailport</dt>
 * <dd>SMTP server port number</dd>
 * <dt>fromAddress</dt>
 * <dd>"From" address of the mail</dd>
 * </dl>
 * {@inheritDoc}
 */
@Override
public void doAction(HstRequest request, HstResponse response) throws HstComponentException {
    String invitee = request.getParameter("email");
    if (StringUtils.isBlank(invitee)) {
        return;
    }

    HippoBean document = getContentBean(request);
    if (!(document instanceof IEventDocument)) {
        return;
    }
    IEventDocument eventDocument = (IEventDocument) document;

    String mailHost = getParameter("mailhost", request);
    int mailPort = Integer.parseInt(getParameter("mailport", request));
    String mailFrom = getParameter("fromAddress", request);
    sendMail(eventDocument, invitee, mailHost, mailPort, mailFrom);

}

You now have all the logic to send the email with the iCalendar attachment, which looks like:

Screenshot of iCalendar mail in GMail