14 February 2011

AWS SES RequestExpired

SendGrid tells me I need a reseller account, and Postmark won't let me send newsletter-style messages; so it's time to try Amazon's Simple Email Service. All I need is a big machine that takes care of delivering mail, the rest is fluff.

I'm using drewblas/aws-ses. Somewhere between ActionMailer and AWS::SES, errors are swallowed and your application fails to let you know that emails aren't getting sent. By the time I had broken the "fetch mail" button on my mail client, it was time to run rails console on the server to figure out what was going on:

$ RAILS_ENV=staging rails console
Loading staging environment (Rails 3.0.3)
irb(main):001:0> require "aws/ses"
irb(main):003:0> ses = AWS::SES::Base.new :access_key_id => "your_access_key", :secret_access_key => "not_telling_you"
=> #<AWS::SES::Base:0x7fd3607d0308 etc... >
irb(main):003:0> ses.send_email :to => ['me@my.domain'], :source => 'test@other.domain', :subject => 'Testing', :text_body => 'Yes, testing!'
AWS::SES::ResponseError: AWS::SES Response Error: RequestExpiredRequest timestamp: Mon, 14 Feb 2011 10:13:32 GMT expired.  It must be within 300 secs/ of server time.

It turns out that my server's clock was racing into the future. I like how Slicehost moves fast, but I wasn't expecting observable relativistic effects. My server was 8 whole minutes ahead of the rest of the world. If I wasn't busy building my cool new site I could have used it to game the stock market or something wicked like that ...

Anyway, thanks to Code Ghar here's the solution:

$ date
Mon Feb 14 10:16:24 UTC 2011
$ sudo ntpdate pool.ntp.org
14 Feb 10:08:55 ntpdate[25724]: step time server offset -639.622800 sec
$ date
Mon Feb 14 10:09:01 UTC 2011

Happy mailing!

03 February 2011

Paperclip, S3, and European Buckets

UPDATE @englandpost points out that newer versions of paperclip support the :s3_host_name option, see http://rubydoc.info/gems/paperclip/Paperclip/Storage/S3. Thanks @englandpost

So you have your European S3 bucket thinking how cool you can select buckets near where your customers live, you gem install paperclip aws_s3 and do the dances and the rails and the rituals and the cap production deploy, and you get

The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.

Not Fair!

The interblags reccomend you use gem install s3 instead, with appropriate monkeypatches for Paperclip::Storage::S3, but you might end up with this:

The request signature we calculated does not match the signature you provided. Check your key and signing method.

I tried and I tried, honest, s3 just wouldn't play the signature game by amazon's rules ... couldn't get anything to work, until I stumbled on http://www.mail-archive.com/heroku@googlegroups.com/msg05407.html in which the great and goodly Dan Croak recommends you put this in config/environment.rb:

AWS::S3::DEFAULT_HOST = "s3-eu-west-1.amazonaws.com"

Well lo and behold I was finally able to upload stuff, my pretty pictures are showing up in my AWS console.

But you're not done yet: you still need to generate the correct URL (my_model.my_attachment.url) for your pictures and mp3s and videos and Large Objects and whatever your pushing up to the clouds there ... Paperclip::Storage::S3 kindly hard-codes "s3.amazonaws.com" for you, and it doesn't work.

Here's the fix:

# in config/initializers/something.rb
Paperclip.interpolates(:s3_eu_url) { |attachment, style|
  "#{attachment.s3_protocol}://s3-eu-west-1.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"

# in your model
has_attached_file :image, 
  :storage => :s3,
  :s3_credentials => "#{Rails.root}/config/s3.yml",
  :path => "for/example/:id/:style.:extension",
  :url  => ":s3_eu_url"

This is a big song and dance to simply tell paperclip how to construct the s3 url. Left to itself, paperclip will replace your url setting with one of its own (":s3_path_url") if it doesn't match /^:s3.*url$/. Hence, the interpolation above is called "s3_eu_url", you can write your own for singapore or whatever far-flung place you've dumped your bucket.