Skip to content

Commit 5472d0c

Browse files
authored
Merge pull request #28 from interflux-electronics/feature/confirmation-emails
Event registration confirmation emails
2 parents 56bc222 + 392d7ff commit 5472d0c

12 files changed

+391
-3
lines changed

Gemfile

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ gem 'aws-sdk-s3'
4848
# For HTTP requests (native Rails does this terribly...)
4949
gem 'faraday'
5050

51+
# For printing curl requests after Faraday requests
52+
gem 'faraday_curl', groups: %i[development]
53+
5154
# For catching N+1 queries
5255
gem 'bullet', groups: %i[development test]
5356

@@ -62,4 +65,4 @@ gem 'rubocop-rails', groups: %i[development]
6265
gem 'minitest', groups: %i[test]
6366

6467
# For annotating models and fixtures with schema info.
65-
gem 'annotate', groups: %i[development]
68+
gem 'annotate', groups: %i[development]

Gemfile.lock

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ GEM
9898
faraday-net_http (>= 2.0, < 3.1)
9999
ruby2_keywords (>= 0.0.4)
100100
faraday-net_http (3.0.2)
101+
faraday_curl (0.0.2)
102+
faraday (>= 0.9.0)
101103
ffi (1.15.0)
102104
globalid (0.4.2)
103105
activesupport (>= 4.2.0)
@@ -226,6 +228,7 @@ DEPENDENCIES
226228
byebug
227229
dotenv-rails
228230
faraday
231+
faraday_curl
229232
jsonapi-serializer
230233
jwt
231234
listen

app/controllers/v1/public/event_attendees_controller.rb

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def creatable_attributes
3838
role
3939
company
4040
email
41+
locale
4142
]
4243
end
4344

app/mailers/application_mailer.rb

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
class ApplicationMailer < ActionMailer::Base
2-
default from: 'jw@floatplane.dev'
32
layout 'mailer'
43
end

app/models/application_record.rb

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
class ApplicationRecord < ActiveRecord::Base
22
self.abstract_class = true
3+
4+
private
5+
6+
def log
7+
Rails.logger
8+
end
39
end

app/models/email_attempt.rb

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# == Schema Information
2+
#
3+
# Table name: email_attempts
4+
#
5+
# id :uuid not null, primary key
6+
# bcc :string
7+
# cc :string
8+
# created_by_type :string
9+
# delivered :boolean
10+
# from :string
11+
# postmark_stream :string
12+
# postmark_template_alias :string
13+
# postmark_template_model :jsonb
14+
# provider :integer
15+
# reply_to :string
16+
# response_body :jsonb
17+
# response_status :integer
18+
# to :string
19+
# created_at :datetime not null
20+
# updated_at :datetime not null
21+
# created_by_id :uuid
22+
#
23+
# Indexes
24+
#
25+
# index_email_attempts_on_created_by (created_by_type,created_by_id)
26+
# index_email_attempts_on_response_body (response_body) USING gin
27+
#
28+
class EmailAttempt < ApplicationRecord
29+
# The record which created this EmailAttempt
30+
belongs_to :created_by, polymorphic: true, optional: true
31+
32+
# The provider which will send the email for us.
33+
# Rails cannot send emails. It only delegates them.
34+
# enum :provider, { postmark: 0, sendgrid: 1 }, scopes: true
35+
36+
after_save :send_email
37+
38+
private
39+
40+
def blocker
41+
return 'no to' if to.nil?
42+
return 'no from' if from.nil?
43+
return 'no postmark_stream' if postmark_stream.nil?
44+
return 'no postmark_template_alias' if postmark_template_alias.nil?
45+
return 'no postmark_template_model' if postmark_template_model.nil?
46+
return 'model is not a hash' unless postmark_template_model.is_a? Hash
47+
48+
# Never deliver an email twice
49+
# This also allows admins to edit and retry until delivered
50+
return 'already delivered' if delivered == true
51+
52+
nil
53+
end
54+
55+
def send_email
56+
log.info "✅ saved EmailAttempt #{id}"
57+
58+
if blocker.present?
59+
log.warn "🔥 #{blocker}"
60+
return
61+
end
62+
63+
log.info '✅ sending ...'
64+
65+
ap from
66+
ap to
67+
ap postmark_template_alias
68+
ap postmark_template_model
69+
70+
api = Postmark::Api.new(server_token: ENV['POSTMARK_SERVER_TOKEN'])
71+
72+
response = api.send_email_with_template(
73+
message_stream: postmark_stream,
74+
template_alias: postmark_template_alias,
75+
template_model: postmark_template_model,
76+
from: from,
77+
to: to,
78+
cc: cc,
79+
bcc: bcc,
80+
reply_to: reply_to
81+
)
82+
83+
if response.status == 200
84+
log.info '✅ success'
85+
else
86+
log.info '❌ fail'
87+
end
88+
89+
ap response.status
90+
ap response.body
91+
92+
self.response_status = response.status
93+
self.response_body = JSON.parse(response.body) if response.body.present?
94+
self.delivered = response.status == 200
95+
96+
save!
97+
98+
log.info '✅ done'
99+
end
100+
end

app/models/event.rb

+24
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,28 @@ class Event < ApplicationRecord
2828
has_many :event_attendees
2929

3030
alias_attribute :attendees, :event_attendees
31+
32+
def location
33+
"#{city}, #{country.name_english}"
34+
end
35+
36+
def start_to_end_date
37+
return '?' if sd.nil? && ed.nil?
38+
39+
return sd.strftime('%a %-d %b %Y') if sd.present? && ed.nil?
40+
41+
return sd.strftime('%a %-d %b %Y') if ed == sd
42+
43+
return "#{sd.strftime('%a %d')} to #{ed.strftime('%a %-d %b %Y')}" if sd.month == ed.month
44+
45+
"#{sd.strftime('%a %-d %b %Y')} to #{ed.strftime('%a %-d %b %Y')}"
46+
end
47+
48+
def sd
49+
start_date&.to_date
50+
end
51+
52+
def ed
53+
end_date&.to_date
54+
end
3155
end

app/models/event_attendee.rb

+68
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
require 'postmark/api'
2+
require 'ap'
3+
14
# == Schema Information
25
#
36
# Table name: event_attendees
@@ -7,6 +10,7 @@
710
# email :string
811
# first_name :string
912
# last_name :string
13+
# locale :string
1014
# role :string
1115
# created_at :datetime not null
1216
# updated_at :datetime not null
@@ -16,4 +20,68 @@
1620
class EventAttendee < ApplicationRecord
1721
belongs_to :event
1822
belongs_to :person, optional: true
23+
24+
after_save :send_confirmation_emails
25+
26+
private
27+
28+
def send_confirmation_emails
29+
log.info "✅ EventAttendee #{id} saved"
30+
31+
return if email.nil?
32+
33+
# TODO: Prevent this email from being sent upon every save...
34+
35+
template_locale = locale == 'fr' ? 'fr' : 'en'
36+
37+
log.info '✅ creating confirmation email'
38+
39+
EmailAttempt.create!(
40+
to: email,
41+
from: 'Interflux Electronics <robot@interflux.com>',
42+
reply_to: 'Interflux Electronics <ask@interflux.com>',
43+
# provider: :postmark,
44+
postmark_stream: 'outbound',
45+
postmark_template_alias: "interflux-event-registration-1-#{template_locale}",
46+
postmark_template_model: {
47+
first_name: first_name,
48+
last_name: last_name,
49+
event_name: event.name,
50+
event_dates: event.start_to_end_date,
51+
event_location: event.location
52+
},
53+
created_by: self
54+
)
55+
56+
log.info '✅ creating internal email'
57+
58+
EmailAttempt.create!(
59+
to: 'Steven Teliszewski <s.teliszewski@interflux.com>',
60+
cc: 'Jan Werkhoven <jw@interflux.au>',
61+
from: 'Interflux Electronics <robot@interflux.com>',
62+
reply_to: 'Interflux Electronics <ask@interflux.com>',
63+
# provider: :postmark,
64+
postmark_stream: 'outbound',
65+
postmark_template_alias: 'interflux-event-registration-2-en',
66+
postmark_template_model: {
67+
receiver: {
68+
first_name: 'Steven'
69+
},
70+
attendee: {
71+
first_name: first_name,
72+
last_name: last_name,
73+
role: role,
74+
company: company,
75+
email: email,
76+
locale: locale
77+
},
78+
event: {
79+
name: event.name,
80+
dates: event.start_to_end_date,
81+
location: event.location
82+
}
83+
},
84+
created_by: self
85+
)
86+
end
1987
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class CreateEmailAttempt < ActiveRecord::Migration[6.1]
2+
def change
3+
create_table :email_attempts, id: :uuid do |t|
4+
t.references :created_by, polymorphic: true, type: :uuid
5+
6+
t.string :from
7+
t.string :to
8+
t.string :cc
9+
t.string :bcc
10+
t.string :reply_to
11+
12+
t.integer :provider
13+
14+
t.string :postmark_stream
15+
t.string :postmark_template_alias
16+
t.jsonb :postmark_template_model, default: {}
17+
18+
# When storing JSONB
19+
# https://dev.to/kputra/rails-postgresql-jsonb-part-1-4ibg
20+
t.jsonb :response_body, default: {}
21+
t.integer :response_status
22+
t.boolean :delivered
23+
24+
t.timestamps
25+
end
26+
27+
add_index :email_attempts, :response_body, using: :gin
28+
end
29+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddHostToEventAttendee < ActiveRecord::Migration[6.1]
2+
def change
3+
add_column :event_attendees, :locale, :string
4+
end
5+
end

db/schema.rb

+23-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2024_04_07_010227) do
13+
ActiveRecord::Schema.define(version: 2024_04_18_113914) do
1414

1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "pgcrypto"
@@ -214,6 +214,27 @@
214214
t.boolean "public", default: false
215215
end
216216

217+
create_table "email_attempts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
218+
t.string "created_by_type"
219+
t.uuid "created_by_id"
220+
t.string "from"
221+
t.string "to"
222+
t.string "cc"
223+
t.string "bcc"
224+
t.string "reply_to"
225+
t.integer "provider"
226+
t.string "postmark_stream"
227+
t.string "postmark_template_alias"
228+
t.jsonb "postmark_template_model", default: {}
229+
t.jsonb "response_body", default: {}
230+
t.integer "response_status"
231+
t.boolean "delivered"
232+
t.datetime "created_at", precision: 6, null: false
233+
t.datetime "updated_at", precision: 6, null: false
234+
t.index ["created_by_type", "created_by_id"], name: "index_email_attempts_on_created_by"
235+
t.index ["response_body"], name: "index_email_attempts_on_response_body", using: :gin
236+
end
237+
217238
create_table "employees", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
218239
t.uuid "company_id"
219240
t.uuid "person_id"
@@ -233,6 +254,7 @@
233254
t.string "email"
234255
t.datetime "created_at", precision: 6, null: false
235256
t.datetime "updated_at", precision: 6, null: false
257+
t.string "locale"
236258
end
237259

238260
create_table "events", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|

0 commit comments

Comments
 (0)